1 #include "game.h"
2 
3 namespace entities
4 {
5     using namespace game;
6 
extraentinfosize()7     int extraentinfosize() { return 0; }       // size in bytes of what the 2 methods below read/write... so it can be skipped by other games
8 
writeent(entity & e,char * buf)9     void writeent(entity &e, char *buf)   // write any additional data to disk (except for ET_ ents)
10     {
11     }
12 
readent(entity & e,char * buf,int ver)13     void readent(entity &e, char *buf, int ver)     // read from disk, and init
14     {
15         if(ver <= 30) switch(e.type)
16         {
17             case FLAG:
18             case MONSTER:
19             case TELEDEST:
20             case RESPAWNPOINT:
21             case BOX:
22             case BARREL:
23             case PLATFORM:
24             case ELEVATOR:
25                 e.attr1 = (int(e.attr1)+180)%360;
26                 break;
27         }
28         if(ver <= 31) switch(e.type)
29         {
30             case BOX:
31             case BARREL:
32             case PLATFORM:
33             case ELEVATOR:
34                 int yaw = (int(e.attr1)%360 + 360)%360 + 7;
35                 e.attr1 = yaw - yaw%15;
36                 break;
37         }
38     }
39 
40 #ifndef STANDALONE
41     vector<extentity *> ents;
42 
getents()43     vector<extentity *> &getents() { return ents; }
44 
mayattach(extentity & e)45     bool mayattach(extentity &e) { return false; }
attachent(extentity & e,extentity & a)46     bool attachent(extentity &e, extentity &a) { return false; }
47 
itemname(int i)48     const char *itemname(int i)
49     {
50         int t = ents[i]->type;
51         if(t<I_SHELLS || t>I_QUAD) return NULL;
52         return itemstats[t-I_SHELLS].name;
53     }
54 
itemicon(int i)55     int itemicon(int i)
56     {
57         int t = ents[i]->type;
58         if(t<I_SHELLS || t>I_QUAD) return -1;
59         return itemstats[t-I_SHELLS].icon;
60     }
61 
entmdlname(int type)62     const char *entmdlname(int type)
63     {
64         static const char * const entmdlnames[] =
65         {
66             NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
67             "ammo/shells", "ammo/bullets", "ammo/rockets", "ammo/rrounds", "ammo/grenades", "ammo/cartridges",
68             "health", "boost", "armor/green", "armor/yellow", "quad", "teleporter",
69             NULL, NULL,
70             "carrot",
71             NULL, NULL,
72             "checkpoint",
73             NULL, NULL,
74             NULL, NULL,
75             NULL
76         };
77         return entmdlnames[type];
78     }
79 
entmodel(const entity & e)80     const char *entmodel(const entity &e)
81     {
82         if(e.type == TELEPORT)
83         {
84             if(e.attr2 > 0) return mapmodelname(e.attr2);
85             if(e.attr2 < 0) return NULL;
86         }
87         return e.type < MAXENTTYPES ? entmdlname(e.type) : NULL;
88     }
89 
preloadentities()90     void preloadentities()
91     {
92         loopi(MAXENTTYPES)
93         {
94             switch(i)
95             {
96                 case I_SHELLS: case I_BULLETS: case I_ROCKETS: case I_ROUNDS: case I_GRENADES: case I_CARTRIDGES:
97                     if(m_noammo) continue;
98                     break;
99                 case I_HEALTH: case I_BOOST: case I_GREENARMOUR: case I_YELLOWARMOUR: case I_QUAD:
100                     if(m_noitems) continue;
101                     break;
102                 case CARROT: case RESPAWNPOINT:
103                     if(!m_classicsp) continue;
104                     break;
105             }
106             const char *mdl = entmdlname(i);
107             if(!mdl) continue;
108             preloadmodel(mdl);
109         }
110         loopv(ents)
111         {
112             extentity &e = *ents[i];
113             switch(e.type)
114             {
115                 case TELEPORT:
116                     if(e.attr2 > 0) preloadmodel(mapmodelname(e.attr2));
117                 case JUMPPAD:
118                     if(e.attr4 > 0) preloadmapsound(e.attr4);
119                     break;
120             }
121         }
122     }
123 
renderentities()124     void renderentities()
125     {
126         loopv(ents)
127         {
128             extentity &e = *ents[i];
129             int revs = 10;
130             switch(e.type)
131             {
132                 case CARROT:
133                 case RESPAWNPOINT:
134                     if(e.attr2) revs = 1;
135                     break;
136                 case TELEPORT:
137                     if(e.attr2 < 0) continue;
138                     break;
139                 default:
140                     if(!e.spawned() || e.type < I_SHELLS || e.type > I_QUAD) continue;
141             }
142             const char *mdlname = entmodel(e);
143             if(mdlname)
144             {
145                 vec p = e.o;
146                 p.z += 1+sinf(lastmillis/100.0+e.o.x+e.o.y)/20;
147                 rendermodel(&e.light, mdlname, ANIM_MAPMODEL|ANIM_LOOP, p, lastmillis/(float)revs, 0, MDL_SHADOW | MDL_CULL_VFC | MDL_CULL_DIST | MDL_CULL_OCCLUDED);
148             }
149         }
150     }
151 
addammo(int type,int & v,bool local)152     void addammo(int type, int &v, bool local)
153     {
154         itemstat &is = itemstats[type-I_SHELLS];
155         v += is.add;
156         if(v>is.max) v = is.max;
157         if(local) msgsound(is.sound);
158     }
159 
repammo(fpsent * d,int type,bool local)160     void repammo(fpsent *d, int type, bool local)
161     {
162         addammo(type, d->ammo[type-I_SHELLS+GUN_SG], local);
163     }
164 
165     // these two functions are called when the server acknowledges that you really
166     // picked up the item (in multiplayer someone may grab it before you).
167 
pickupeffects(int n,fpsent * d)168     void pickupeffects(int n, fpsent *d)
169     {
170         if(!ents.inrange(n)) return;
171         extentity *e = ents[n];
172         int type = e->type;
173         if(type<I_SHELLS || type>I_QUAD) return;
174         e->clearspawned();
175         e->clearnopickup();
176         if(!d) return;
177         itemstat &is = itemstats[type-I_SHELLS];
178         fpsent *h = followingplayer(player1);
179         if(d!=h || isthirdperson())
180         {
181             //particle_text(d->abovehead(), is.name, PART_TEXT, 2000, 0xFFC864, 4.0f, -8);
182             particle_icon(d->abovehead(), is.icon%4, is.icon/4, PART_HUD_ICON_GREY, 2000, 0xFFFFFF, 2.0f, -8);
183         }
184         playsound(itemstats[type-I_SHELLS].sound, d!=h ? &d->o : NULL, NULL, 0, 0, 0, -1, 0, 1500);
185         d->pickup(type);
186         if(d==h) switch(type)
187         {
188             case I_BOOST:
189                 conoutf(CON_GAMEINFO, "\f2you got the health boost!");
190                 playsound(S_V_BOOST, NULL, NULL, 0, 0, 0, -1, 0, 3000);
191                 break;
192 
193             case I_QUAD:
194                 conoutf(CON_GAMEINFO, "\f2you got the quad!");
195                 playsound(S_V_QUAD, NULL, NULL, 0, 0, 0, -1, 0, 3000);
196                 break;
197         }
198     }
199 
200     // these functions are called when the client touches the item
201 
teleporteffects(fpsent * d,int tp,int td,bool local)202     void teleporteffects(fpsent *d, int tp, int td, bool local)
203     {
204         if(ents.inrange(tp) && ents[tp]->type == TELEPORT)
205         {
206             extentity &e = *ents[tp];
207             if(e.attr4 >= 0)
208             {
209                 int snd = S_TELEPORT, flags = 0;
210                 if(e.attr4 > 0) { snd = e.attr4; flags = SND_MAP; }
211                 fpsent *h = followingplayer(player1);
212                 playsound(snd, d==h ? NULL : &e.o, NULL, flags);
213                 if(d!=h && ents.inrange(td) && ents[td]->type == TELEDEST) playsound(snd, &ents[td]->o, NULL, flags);
214             }
215         }
216         if(local && d->clientnum >= 0)
217         {
218             sendposition(d);
219             packetbuf p(32, ENET_PACKET_FLAG_RELIABLE);
220             putint(p, N_TELEPORT);
221             putint(p, d->clientnum);
222             putint(p, tp);
223             putint(p, td);
224             sendclientpacket(p.finalize(), 0);
225             flushclient();
226         }
227     }
228 
jumppadeffects(fpsent * d,int jp,bool local)229     void jumppadeffects(fpsent *d, int jp, bool local)
230     {
231         if(ents.inrange(jp) && ents[jp]->type == JUMPPAD)
232         {
233             extentity &e = *ents[jp];
234             if(e.attr4 >= 0)
235             {
236                 int snd = S_JUMPPAD, flags = 0;
237                 if(e.attr4 > 0) { snd = e.attr4; flags = SND_MAP; }
238                 playsound(snd, d == followingplayer(player1) ? NULL : &e.o, NULL, flags);
239             }
240         }
241         if(local && d->clientnum >= 0)
242         {
243             sendposition(d);
244             packetbuf p(16, ENET_PACKET_FLAG_RELIABLE);
245             putint(p, N_JUMPPAD);
246             putint(p, d->clientnum);
247             putint(p, jp);
248             sendclientpacket(p.finalize(), 0);
249             flushclient();
250         }
251     }
252 
teleport(int n,fpsent * d)253     void teleport(int n, fpsent *d)     // also used by monsters
254     {
255         int e = -1, tag = ents[n]->attr1, beenhere = -1;
256         for(;;)
257         {
258             e = findentity(TELEDEST, e+1);
259             if(e==beenhere || e<0) { conoutf(CON_WARN, "no teleport destination for tag %d", tag); return; }
260             if(beenhere<0) beenhere = e;
261             if(ents[e]->attr2==tag)
262             {
263                 teleporteffects(d, n, e, true);
264                 d->o = ents[e]->o;
265                 d->yaw = ents[e]->attr1;
266                 if(ents[e]->attr3 > 0)
267                 {
268                     vec dir;
269                     vecfromyawpitch(d->yaw, 0, 1, 0, dir);
270                     float speed = d->vel.magnitude2();
271                     d->vel.x = dir.x*speed;
272                     d->vel.y = dir.y*speed;
273                 }
274                 else d->vel = vec(0, 0, 0);
275                 entinmap(d);
276                 updatedynentcache(d);
277                 ai::inferwaypoints(d, ents[n]->o, ents[e]->o, 16.f);
278                 break;
279             }
280         }
281     }
282 
trypickup(int n,fpsent * d)283     void trypickup(int n, fpsent *d)
284     {
285         extentity *e = ents[n];
286         switch(e->type)
287         {
288             default:
289                 if(d->canpickup(e->type))
290                 {
291                     addmsg(N_ITEMPICKUP, "rci", d, n);
292                     e->setnopickup(); // even if someone else gets it first
293                 }
294                 break;
295 
296             case TELEPORT:
297             {
298                 if(d->lastpickup==e->type && lastmillis-d->lastpickupmillis<500) break;
299                 if(e->attr3 > 0)
300                 {
301                     defformatstring(hookname, "can_teleport_%d", e->attr3);
302                     if(!execidentbool(hookname, true)) break;
303                 }
304                 d->lastpickup = e->type;
305                 d->lastpickupmillis = lastmillis;
306                 teleport(n, d);
307                 break;
308             }
309 
310             case RESPAWNPOINT:
311                 if(!m_classicsp || d!=player1 || n==respawnent) break;
312                 respawnent = n;
313                 conoutf(CON_GAMEINFO, "\f2respawn point set!");
314                 playsound(S_V_RESPAWNPOINT);
315                 break;
316 
317             case JUMPPAD:
318             {
319                 if(d->lastpickup==e->type && lastmillis-d->lastpickupmillis<300) break;
320                 d->lastpickup = e->type;
321                 d->lastpickupmillis = lastmillis;
322                 jumppadeffects(d, n, true);
323                 vec v((int)(char)e->attr3*10.0f, (int)(char)e->attr2*10.0f, e->attr1*12.5f);
324                 if(d->ai) d->ai->becareful = true;
325 				d->falling = vec(0, 0, 0);
326 				d->physstate = PHYS_FALL;
327                 d->timeinair = 1;
328                 d->vel = v;
329                 break;
330             }
331         }
332     }
333 
checkitems(fpsent * d)334     void checkitems(fpsent *d)
335     {
336         if(d->state!=CS_ALIVE) return;
337         vec o = d->feetpos();
338         loopv(ents)
339         {
340             extentity &e = *ents[i];
341             if(e.type==NOTUSED) continue;
342             if((!e.spawned() || e.nopickup()) && e.type!=TELEPORT && e.type!=JUMPPAD && e.type!=RESPAWNPOINT) continue;
343             float dist = e.o.dist(o);
344             if(dist<(e.type==TELEPORT ? 16 : 12)) trypickup(i, d);
345         }
346     }
347 
checkquad(int time,fpsent * d)348     void checkquad(int time, fpsent *d)
349     {
350         if(d->quadmillis && (d->quadmillis -= time)<=0)
351         {
352             d->quadmillis = 0;
353             fpsent *h = followingplayer(player1);
354             playsound(S_PUPOUT, d==h ? NULL : &d->o);
355             if(d==h) conoutf(CON_GAMEINFO, "\f2quad damage is over");
356         }
357     }
358 
putitems(packetbuf & p)359     void putitems(packetbuf &p)            // puts items in network stream and also spawns them locally
360     {
361         putint(p, N_ITEMLIST);
362         loopv(ents) if(ents[i]->type>=I_SHELLS && ents[i]->type<=I_QUAD && (!m_noammo || ents[i]->type<I_SHELLS || ents[i]->type>I_CARTRIDGES))
363         {
364             putint(p, i);
365             putint(p, ents[i]->type);
366         }
367         putint(p, -1);
368     }
369 
resetspawns()370     void resetspawns() { loopv(ents) { extentity *e = ents[i]; e->clearspawned(); e->clearnopickup(); } }
371 
spawnitems(bool force)372     void spawnitems(bool force)
373     {
374         if(m_noitems) return;
375         loopv(ents)
376         {
377             extentity *e = ents[i];
378             if(e->type>=I_SHELLS && e->type<=I_QUAD && (!m_noammo || e->type<I_SHELLS || e->type>I_CARTRIDGES))
379             {
380                 e->setspawned(force || m_sp || !server::delayspawn(e->type));
381                 e->clearnopickup();
382             }
383         }
384     }
385 
setspawn(int i,bool on)386     void setspawn(int i, bool on) { if(ents.inrange(i)) { extentity *e = ents[i]; e->setspawned(on); e->clearnopickup(); } }
387 
newentity()388     extentity *newentity() { return new fpsentity(); }
deleteentity(extentity * e)389     void deleteentity(extentity *e) { delete (fpsentity *)e; }
390 
clearents()391     void clearents()
392     {
393         while(ents.length()) deleteentity(ents.pop());
394     }
395 
396     enum
397     {
398         TRIG_COLLIDE    = 1<<0,
399         TRIG_TOGGLE     = 1<<1,
400         TRIG_ONCE       = 0<<2,
401         TRIG_MANY       = 1<<2,
402         TRIG_DISAPPEAR  = 1<<3,
403         TRIG_AUTO_RESET = 1<<4,
404         TRIG_RUMBLE     = 1<<5,
405         TRIG_LOCKED     = 1<<6,
406         TRIG_ENDSP      = 1<<7
407     };
408 
409     static const int NUMTRIGGERTYPES = 32;
410 
411     static const int triggertypes[NUMTRIGGERTYPES] =
412     {
413         -1,
414         TRIG_ONCE,                    // 1
415         TRIG_RUMBLE,                  // 2
416         TRIG_TOGGLE,                  // 3
417         TRIG_TOGGLE | TRIG_RUMBLE,    // 4
418         TRIG_MANY,                    // 5
419         TRIG_MANY | TRIG_RUMBLE,      // 6
420         TRIG_MANY | TRIG_TOGGLE,      // 7
421         TRIG_MANY | TRIG_TOGGLE | TRIG_RUMBLE,    // 8
422         TRIG_COLLIDE | TRIG_TOGGLE | TRIG_RUMBLE, // 9
423         TRIG_COLLIDE | TRIG_TOGGLE | TRIG_AUTO_RESET | TRIG_RUMBLE, // 10
424         TRIG_COLLIDE | TRIG_TOGGLE | TRIG_LOCKED | TRIG_RUMBLE,     // 11
425         TRIG_DISAPPEAR,               // 12
426         TRIG_DISAPPEAR | TRIG_RUMBLE, // 13
427         TRIG_DISAPPEAR | TRIG_COLLIDE | TRIG_LOCKED, // 14
428         -1 /* reserved 15 */,
429         -1 /* reserved 16 */,
430         -1 /* reserved 17 */,
431         -1 /* reserved 18 */,
432         -1 /* reserved 19 */,
433         -1 /* reserved 20 */,
434         -1 /* reserved 21 */,
435         -1 /* reserved 22 */,
436         -1 /* reserved 23 */,
437         -1 /* reserved 24 */,
438         -1 /* reserved 25 */,
439         -1 /* reserved 26 */,
440         -1 /* reserved 27 */,
441         -1 /* reserved 28 */,
442         TRIG_DISAPPEAR | TRIG_RUMBLE | TRIG_ENDSP, // 29
443         -1 /* reserved 30 */,
444         -1 /* reserved 31 */,
445     };
446 
447     #define validtrigger(type) (triggertypes[(type) & (NUMTRIGGERTYPES-1)]>=0)
448     #define checktriggertype(type, flag) (triggertypes[(type) & (NUMTRIGGERTYPES-1)] & (flag))
449 
cleartriggerflags(extentity & e)450     static inline void cleartriggerflags(extentity &e)
451     {
452         e.flags &= ~(EF_ANIM | EF_NOVIS | EF_NOSHADOW | EF_NOCOLLIDE);
453     }
454 
setuptriggerflags(fpsentity & e)455     static inline void setuptriggerflags(fpsentity &e)
456     {
457         cleartriggerflags(e);
458         e.flags |= EF_ANIM;
459         if(checktriggertype(e.attr3, TRIG_COLLIDE|TRIG_DISAPPEAR)) e.flags |= EF_NOSHADOW;
460         if(!checktriggertype(e.attr3, TRIG_COLLIDE)) e.flags |= EF_NOCOLLIDE;
461         switch(e.triggerstate)
462         {
463             case TRIGGERING:
464                 if(checktriggertype(e.attr3, TRIG_COLLIDE) && lastmillis-e.lasttrigger >= 500) e.flags |= EF_NOCOLLIDE;
465                 break;
466             case TRIGGERED:
467                 if(checktriggertype(e.attr3, TRIG_COLLIDE)) e.flags |= EF_NOCOLLIDE;
468                 break;
469             case TRIGGER_DISAPPEARED:
470                 e.flags |= EF_NOVIS | EF_NOCOLLIDE;
471                 break;
472         }
473     }
474 
resettriggers()475     void resettriggers()
476     {
477         loopv(ents)
478         {
479             fpsentity &e = *(fpsentity *)ents[i];
480             if(e.type != ET_MAPMODEL || !validtrigger(e.attr3)) continue;
481             e.triggerstate = TRIGGER_RESET;
482             e.lasttrigger = 0;
483             setuptriggerflags(e);
484         }
485     }
486 
unlocktriggers(int tag,int oldstate=TRIGGER_RESET,int newstate=TRIGGERING)487     void unlocktriggers(int tag, int oldstate = TRIGGER_RESET, int newstate = TRIGGERING)
488     {
489         loopv(ents)
490         {
491             fpsentity &e = *(fpsentity *)ents[i];
492             if(e.type != ET_MAPMODEL || !validtrigger(e.attr3)) continue;
493             if(e.attr4 == tag && e.triggerstate == oldstate && checktriggertype(e.attr3, TRIG_LOCKED))
494             {
495                 if(newstate == TRIGGER_RESETTING && checktriggertype(e.attr3, TRIG_COLLIDE) && overlapsdynent(e.o, 20)) continue;
496                 e.triggerstate = newstate;
497                 e.lasttrigger = lastmillis;
498                 if(checktriggertype(e.attr3, TRIG_RUMBLE)) playsound(S_RUMBLE, &e.o);
499             }
500         }
501     }
502 
503     ICOMMAND(trigger, "ii", (int *tag, int *state),
504     {
505         if(*state) unlocktriggers(*tag);
506         else unlocktriggers(*tag, TRIGGERED, TRIGGER_RESETTING);
507     });
508 
509     VAR(triggerstate, -1, 0, 1);
510 
doleveltrigger(int trigger,int state)511     void doleveltrigger(int trigger, int state)
512     {
513         defformatstring(aliasname, "level_trigger_%d", trigger);
514         if(identexists(aliasname))
515         {
516             triggerstate = state;
517             execident(aliasname);
518         }
519     }
520 
checktriggers()521     void checktriggers()
522     {
523         if(player1->state != CS_ALIVE) return;
524         vec o = player1->feetpos();
525         loopv(ents)
526         {
527             fpsentity &e = *(fpsentity *)ents[i];
528             if(e.type != ET_MAPMODEL || !validtrigger(e.attr3)) continue;
529             switch(e.triggerstate)
530             {
531                 case TRIGGERING:
532                 case TRIGGER_RESETTING:
533                     if(lastmillis-e.lasttrigger>=1000)
534                     {
535                         if(e.attr4)
536                         {
537                             if(e.triggerstate == TRIGGERING) unlocktriggers(e.attr4);
538                             else unlocktriggers(e.attr4, TRIGGERED, TRIGGER_RESETTING);
539                         }
540                         if(checktriggertype(e.attr3, TRIG_DISAPPEAR)) e.triggerstate = TRIGGER_DISAPPEARED;
541                         else if(e.triggerstate==TRIGGERING && checktriggertype(e.attr3, TRIG_TOGGLE)) e.triggerstate = TRIGGERED;
542                         else e.triggerstate = TRIGGER_RESET;
543                     }
544                     setuptriggerflags(e);
545                     break;
546                 case TRIGGER_RESET:
547                     if(e.lasttrigger)
548                     {
549                         if(checktriggertype(e.attr3, TRIG_AUTO_RESET|TRIG_MANY|TRIG_LOCKED) && e.o.dist(o)-player1->radius>=(checktriggertype(e.attr3, TRIG_COLLIDE) ? 20 : 12))
550                             e.lasttrigger = 0;
551                         break;
552                     }
553                     else if(e.o.dist(o)-player1->radius>=(checktriggertype(e.attr3, TRIG_COLLIDE) ? 20 : 12)) break;
554                     else if(checktriggertype(e.attr3, TRIG_LOCKED))
555                     {
556                         if(!e.attr4) break;
557                         doleveltrigger(e.attr4, -1);
558                         e.lasttrigger = lastmillis;
559                         break;
560                     }
561                     e.triggerstate = TRIGGERING;
562                     e.lasttrigger = lastmillis;
563                     setuptriggerflags(e);
564                     if(checktriggertype(e.attr3, TRIG_RUMBLE)) playsound(S_RUMBLE, &e.o);
565                     if(checktriggertype(e.attr3, TRIG_ENDSP)) endsp(false);
566                     if(e.attr4) doleveltrigger(e.attr4, 1);
567                     break;
568                 case TRIGGERED:
569                     if(e.o.dist(o)-player1->radius<(checktriggertype(e.attr3, TRIG_COLLIDE) ? 20 : 12))
570                     {
571                         if(e.lasttrigger) break;
572                     }
573                     else if(checktriggertype(e.attr3, TRIG_AUTO_RESET))
574                     {
575                         if(lastmillis-e.lasttrigger<6000) break;
576                     }
577                     else if(checktriggertype(e.attr3, TRIG_MANY))
578                     {
579                         e.lasttrigger = 0;
580                         break;
581                     }
582                     else break;
583                     if(checktriggertype(e.attr3, TRIG_COLLIDE) && overlapsdynent(e.o, 20)) break;
584                     e.triggerstate = TRIGGER_RESETTING;
585                     e.lasttrigger = lastmillis;
586                     setuptriggerflags(e);
587                     if(checktriggertype(e.attr3, TRIG_RUMBLE)) playsound(S_RUMBLE, &e.o);
588                     if(checktriggertype(e.attr3, TRIG_ENDSP)) endsp(false);
589                     if(e.attr4) doleveltrigger(e.attr4, 0);
590                     break;
591             }
592         }
593     }
594 
animatemapmodel(const extentity & e,int & anim,int & basetime)595     void animatemapmodel(const extentity &e, int &anim, int &basetime)
596     {
597         const fpsentity &f = (const fpsentity &)e;
598         if(validtrigger(f.attr3)) switch(f.triggerstate)
599         {
600             case TRIGGER_RESET: anim = ANIM_TRIGGER|ANIM_START; break;
601             case TRIGGERING: anim = ANIM_TRIGGER; basetime = f.lasttrigger; break;
602             case TRIGGERED: anim = ANIM_TRIGGER|ANIM_END; break;
603             case TRIGGER_RESETTING: anim = ANIM_TRIGGER|ANIM_REVERSE; basetime = f.lasttrigger; break;
604         }
605     }
606 
fixentity(extentity & e)607     void fixentity(extentity &e)
608     {
609         switch(e.type)
610         {
611             case FLAG:
612             case BOX:
613             case BARREL:
614             case PLATFORM:
615             case ELEVATOR:
616                 e.attr5 = e.attr4;
617                 e.attr4 = e.attr3;
618             case TELEDEST:
619                 e.attr3 = e.attr2;
620             case MONSTER:
621                 e.attr2 = e.attr1;
622             case RESPAWNPOINT:
623                 e.attr1 = (int)player1->yaw;
624                 break;
625         }
626     }
627 
entradius(extentity & e,bool color)628     void entradius(extentity &e, bool color)
629     {
630         switch(e.type)
631         {
632             case TELEPORT:
633                 loopv(ents) if(ents[i]->type == TELEDEST && e.attr1==ents[i]->attr2)
634                 {
635                     renderentarrow(e, vec(ents[i]->o).sub(e.o).normalize(), e.o.dist(ents[i]->o));
636                     break;
637                 }
638                 break;
639 
640             case JUMPPAD:
641                 renderentarrow(e, vec((int)(char)e.attr3*10.0f, (int)(char)e.attr2*10.0f, e.attr1*12.5f).normalize(), 4);
642                 break;
643 
644             case FLAG:
645             case MONSTER:
646             case TELEDEST:
647             case RESPAWNPOINT:
648             case BOX:
649             case BARREL:
650             case PLATFORM:
651             case ELEVATOR:
652             {
653                 vec dir;
654                 vecfromyawpitch(e.attr1, 0, 1, 0, dir);
655                 renderentarrow(e, dir, 4);
656                 break;
657             }
658             case MAPMODEL:
659                 if(validtrigger(e.attr3)) renderentring(e, checktriggertype(e.attr3, TRIG_COLLIDE) ? 20 : 12);
660                 break;
661         }
662     }
663 
printent(extentity & e,char * buf,int len)664     bool printent(extentity &e, char *buf, int len)
665     {
666         return false;
667     }
668 
entnameinfo(entity & e)669     const char *entnameinfo(entity &e) { return ""; }
entname(int i)670     const char *entname(int i)
671     {
672         static const char * const entnames[] =
673         {
674             "none?", "light", "mapmodel", "playerstart", "envmap", "particles", "sound", "spotlight",
675             "shells", "bullets", "rockets", "riflerounds", "grenades", "cartridges",
676             "health", "healthboost", "greenarmour", "yellowarmour", "quaddamage",
677             "teleport", "teledest",
678             "monster", "carrot", "jumppad",
679             "base", "respawnpoint",
680             "box", "barrel",
681             "platform", "elevator",
682             "flag",
683             "", "", "", "",
684         };
685         return i>=0 && size_t(i)<sizeof(entnames)/sizeof(entnames[0]) ? entnames[i] : "";
686     }
687 
editent(int i,bool local)688     void editent(int i, bool local)
689     {
690         extentity &e = *ents[i];
691         if(e.type == ET_MAPMODEL && validtrigger(e.attr3))
692         {
693             fpsentity &f = (fpsentity &)e;
694             f.triggerstate = TRIGGER_RESET;
695             f.lasttrigger = 0;
696             setuptriggerflags(f);
697         }
698         else cleartriggerflags(e);
699         if(local) addmsg(N_EDITENT, "rii3ii5", i, (int)(e.o.x*DMF), (int)(e.o.y*DMF), (int)(e.o.z*DMF), e.type, e.attr1, e.attr2, e.attr3, e.attr4, e.attr5);
700     }
701 
dropheight(entity & e)702     float dropheight(entity &e)
703     {
704         if(e.type==BASE || e.type==FLAG) return 0.0f;
705         return 4.0f;
706     }
707 #endif
708 }
709 
710