#include "game.h" namespace entities { using namespace game; int extraentinfosize() { return 0; } // size in bytes of what the 2 methods below read/write... so it can be skipped by other games void writeent(entity &e, char *buf) // write any additional data to disk (except for ET_ ents) { } void readent(entity &e, char *buf, int ver) // read from disk, and init { if(ver <= 30) switch(e.type) { case FLAG: case MONSTER: case TELEDEST: case RESPAWNPOINT: case BOX: case BARREL: case PLATFORM: case ELEVATOR: e.attr1 = (int(e.attr1)+180)%360; break; } if(ver <= 31) switch(e.type) { case BOX: case BARREL: case PLATFORM: case ELEVATOR: int yaw = (int(e.attr1)%360 + 360)%360 + 7; e.attr1 = yaw - yaw%15; break; } } #ifndef STANDALONE vector ents; vector &getents() { return ents; } bool mayattach(extentity &e) { return false; } bool attachent(extentity &e, extentity &a) { return false; } const char *itemname(int i) { int t = ents[i]->type; if(tI_QUAD) return NULL; return itemstats[t-I_SHELLS].name; } int itemicon(int i) { int t = ents[i]->type; if(tI_QUAD) return -1; return itemstats[t-I_SHELLS].icon; } const char *entmdlname(int type) { static const char * const entmdlnames[] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "ammo/shells", "ammo/bullets", "ammo/rockets", "ammo/rrounds", "ammo/grenades", "ammo/cartridges", "health", "boost", "armor/green", "armor/yellow", "quad", "teleporter", NULL, NULL, "carrot", NULL, NULL, "checkpoint", NULL, NULL, NULL, NULL, NULL }; return entmdlnames[type]; } const char *entmodel(const entity &e) { if(e.type == TELEPORT) { if(e.attr2 > 0) return mapmodelname(e.attr2); if(e.attr2 < 0) return NULL; } return e.type < MAXENTTYPES ? entmdlname(e.type) : NULL; } void preloadentities() { loopi(MAXENTTYPES) { switch(i) { case I_SHELLS: case I_BULLETS: case I_ROCKETS: case I_ROUNDS: case I_GRENADES: case I_CARTRIDGES: if(m_noammo) continue; break; case I_HEALTH: case I_BOOST: case I_GREENARMOUR: case I_YELLOWARMOUR: case I_QUAD: if(m_noitems) continue; break; case CARROT: case RESPAWNPOINT: if(!m_classicsp) continue; break; } const char *mdl = entmdlname(i); if(!mdl) continue; preloadmodel(mdl); } loopv(ents) { extentity &e = *ents[i]; switch(e.type) { case TELEPORT: if(e.attr2 > 0) preloadmodel(mapmodelname(e.attr2)); case JUMPPAD: if(e.attr4 > 0) preloadmapsound(e.attr4); break; } } } void renderentities() { loopv(ents) { extentity &e = *ents[i]; int revs = 10; switch(e.type) { case CARROT: case RESPAWNPOINT: if(e.attr2) revs = 1; break; case TELEPORT: if(e.attr2 < 0) continue; break; default: if(!e.spawned() || e.type < I_SHELLS || e.type > I_QUAD) continue; } const char *mdlname = entmodel(e); if(mdlname) { vec p = e.o; p.z += 1+sinf(lastmillis/100.0+e.o.x+e.o.y)/20; rendermodel(&e.light, mdlname, ANIM_MAPMODEL|ANIM_LOOP, p, lastmillis/(float)revs, 0, MDL_SHADOW | MDL_CULL_VFC | MDL_CULL_DIST | MDL_CULL_OCCLUDED); } } } void addammo(int type, int &v, bool local) { itemstat &is = itemstats[type-I_SHELLS]; v += is.add; if(v>is.max) v = is.max; if(local) msgsound(is.sound); } void repammo(fpsent *d, int type, bool local) { addammo(type, d->ammo[type-I_SHELLS+GUN_SG], local); } // these two functions are called when the server acknowledges that you really // picked up the item (in multiplayer someone may grab it before you). void pickupeffects(int n, fpsent *d) { if(!ents.inrange(n)) return; extentity *e = ents[n]; int type = e->type; if(typeI_QUAD) return; e->clearspawned(); e->clearnopickup(); if(!d) return; itemstat &is = itemstats[type-I_SHELLS]; fpsent *h = followingplayer(player1); if(d!=h || isthirdperson()) { //particle_text(d->abovehead(), is.name, PART_TEXT, 2000, 0xFFC864, 4.0f, -8); particle_icon(d->abovehead(), is.icon%4, is.icon/4, PART_HUD_ICON_GREY, 2000, 0xFFFFFF, 2.0f, -8); } playsound(itemstats[type-I_SHELLS].sound, d!=h ? &d->o : NULL, NULL, 0, 0, 0, -1, 0, 1500); d->pickup(type); if(d==h) switch(type) { case I_BOOST: conoutf(CON_GAMEINFO, "\f2you got the health boost!"); playsound(S_V_BOOST, NULL, NULL, 0, 0, 0, -1, 0, 3000); break; case I_QUAD: conoutf(CON_GAMEINFO, "\f2you got the quad!"); playsound(S_V_QUAD, NULL, NULL, 0, 0, 0, -1, 0, 3000); break; } } // these functions are called when the client touches the item void teleporteffects(fpsent *d, int tp, int td, bool local) { if(ents.inrange(tp) && ents[tp]->type == TELEPORT) { extentity &e = *ents[tp]; if(e.attr4 >= 0) { int snd = S_TELEPORT, flags = 0; if(e.attr4 > 0) { snd = e.attr4; flags = SND_MAP; } fpsent *h = followingplayer(player1); playsound(snd, d==h ? NULL : &e.o, NULL, flags); if(d!=h && ents.inrange(td) && ents[td]->type == TELEDEST) playsound(snd, &ents[td]->o, NULL, flags); } } if(local && d->clientnum >= 0) { sendposition(d); packetbuf p(32, ENET_PACKET_FLAG_RELIABLE); putint(p, N_TELEPORT); putint(p, d->clientnum); putint(p, tp); putint(p, td); sendclientpacket(p.finalize(), 0); flushclient(); } } void jumppadeffects(fpsent *d, int jp, bool local) { if(ents.inrange(jp) && ents[jp]->type == JUMPPAD) { extentity &e = *ents[jp]; if(e.attr4 >= 0) { int snd = S_JUMPPAD, flags = 0; if(e.attr4 > 0) { snd = e.attr4; flags = SND_MAP; } playsound(snd, d == followingplayer(player1) ? NULL : &e.o, NULL, flags); } } if(local && d->clientnum >= 0) { sendposition(d); packetbuf p(16, ENET_PACKET_FLAG_RELIABLE); putint(p, N_JUMPPAD); putint(p, d->clientnum); putint(p, jp); sendclientpacket(p.finalize(), 0); flushclient(); } } void teleport(int n, fpsent *d) // also used by monsters { int e = -1, tag = ents[n]->attr1, beenhere = -1; for(;;) { e = findentity(TELEDEST, e+1); if(e==beenhere || e<0) { conoutf(CON_WARN, "no teleport destination for tag %d", tag); return; } if(beenhere<0) beenhere = e; if(ents[e]->attr2==tag) { teleporteffects(d, n, e, true); d->o = ents[e]->o; d->yaw = ents[e]->attr1; if(ents[e]->attr3 > 0) { vec dir; vecfromyawpitch(d->yaw, 0, 1, 0, dir); float speed = d->vel.magnitude2(); d->vel.x = dir.x*speed; d->vel.y = dir.y*speed; } else d->vel = vec(0, 0, 0); entinmap(d); updatedynentcache(d); ai::inferwaypoints(d, ents[n]->o, ents[e]->o, 16.f); break; } } } void trypickup(int n, fpsent *d) { extentity *e = ents[n]; switch(e->type) { default: if(d->canpickup(e->type)) { addmsg(N_ITEMPICKUP, "rci", d, n); e->setnopickup(); // even if someone else gets it first } break; case TELEPORT: { if(d->lastpickup==e->type && lastmillis-d->lastpickupmillis<500) break; if(e->attr3 > 0) { defformatstring(hookname, "can_teleport_%d", e->attr3); if(!execidentbool(hookname, true)) break; } d->lastpickup = e->type; d->lastpickupmillis = lastmillis; teleport(n, d); break; } case RESPAWNPOINT: if(!m_classicsp || d!=player1 || n==respawnent) break; respawnent = n; conoutf(CON_GAMEINFO, "\f2respawn point set!"); playsound(S_V_RESPAWNPOINT); break; case JUMPPAD: { if(d->lastpickup==e->type && lastmillis-d->lastpickupmillis<300) break; d->lastpickup = e->type; d->lastpickupmillis = lastmillis; jumppadeffects(d, n, true); vec v((int)(char)e->attr3*10.0f, (int)(char)e->attr2*10.0f, e->attr1*12.5f); if(d->ai) d->ai->becareful = true; d->falling = vec(0, 0, 0); d->physstate = PHYS_FALL; d->timeinair = 1; d->vel = v; break; } } } void checkitems(fpsent *d) { if(d->state!=CS_ALIVE) return; vec o = d->feetpos(); loopv(ents) { extentity &e = *ents[i]; if(e.type==NOTUSED) continue; if((!e.spawned() || e.nopickup()) && e.type!=TELEPORT && e.type!=JUMPPAD && e.type!=RESPAWNPOINT) continue; float dist = e.o.dist(o); if(dist<(e.type==TELEPORT ? 16 : 12)) trypickup(i, d); } } void checkquad(int time, fpsent *d) { if(d->quadmillis && (d->quadmillis -= time)<=0) { d->quadmillis = 0; fpsent *h = followingplayer(player1); playsound(S_PUPOUT, d==h ? NULL : &d->o); if(d==h) conoutf(CON_GAMEINFO, "\f2quad damage is over"); } } void putitems(packetbuf &p) // puts items in network stream and also spawns them locally { putint(p, N_ITEMLIST); loopv(ents) if(ents[i]->type>=I_SHELLS && ents[i]->type<=I_QUAD && (!m_noammo || ents[i]->typetype>I_CARTRIDGES)) { putint(p, i); putint(p, ents[i]->type); } putint(p, -1); } void resetspawns() { loopv(ents) { extentity *e = ents[i]; e->clearspawned(); e->clearnopickup(); } } void spawnitems(bool force) { if(m_noitems) return; loopv(ents) { extentity *e = ents[i]; if(e->type>=I_SHELLS && e->type<=I_QUAD && (!m_noammo || e->typetype>I_CARTRIDGES)) { e->setspawned(force || m_sp || !server::delayspawn(e->type)); e->clearnopickup(); } } } void setspawn(int i, bool on) { if(ents.inrange(i)) { extentity *e = ents[i]; e->setspawned(on); e->clearnopickup(); } } extentity *newentity() { return new fpsentity(); } void deleteentity(extentity *e) { delete (fpsentity *)e; } void clearents() { while(ents.length()) deleteentity(ents.pop()); } enum { TRIG_COLLIDE = 1<<0, TRIG_TOGGLE = 1<<1, TRIG_ONCE = 0<<2, TRIG_MANY = 1<<2, TRIG_DISAPPEAR = 1<<3, TRIG_AUTO_RESET = 1<<4, TRIG_RUMBLE = 1<<5, TRIG_LOCKED = 1<<6, TRIG_ENDSP = 1<<7 }; static const int NUMTRIGGERTYPES = 32; static const int triggertypes[NUMTRIGGERTYPES] = { -1, TRIG_ONCE, // 1 TRIG_RUMBLE, // 2 TRIG_TOGGLE, // 3 TRIG_TOGGLE | TRIG_RUMBLE, // 4 TRIG_MANY, // 5 TRIG_MANY | TRIG_RUMBLE, // 6 TRIG_MANY | TRIG_TOGGLE, // 7 TRIG_MANY | TRIG_TOGGLE | TRIG_RUMBLE, // 8 TRIG_COLLIDE | TRIG_TOGGLE | TRIG_RUMBLE, // 9 TRIG_COLLIDE | TRIG_TOGGLE | TRIG_AUTO_RESET | TRIG_RUMBLE, // 10 TRIG_COLLIDE | TRIG_TOGGLE | TRIG_LOCKED | TRIG_RUMBLE, // 11 TRIG_DISAPPEAR, // 12 TRIG_DISAPPEAR | TRIG_RUMBLE, // 13 TRIG_DISAPPEAR | TRIG_COLLIDE | TRIG_LOCKED, // 14 -1 /* reserved 15 */, -1 /* reserved 16 */, -1 /* reserved 17 */, -1 /* reserved 18 */, -1 /* reserved 19 */, -1 /* reserved 20 */, -1 /* reserved 21 */, -1 /* reserved 22 */, -1 /* reserved 23 */, -1 /* reserved 24 */, -1 /* reserved 25 */, -1 /* reserved 26 */, -1 /* reserved 27 */, -1 /* reserved 28 */, TRIG_DISAPPEAR | TRIG_RUMBLE | TRIG_ENDSP, // 29 -1 /* reserved 30 */, -1 /* reserved 31 */, }; #define validtrigger(type) (triggertypes[(type) & (NUMTRIGGERTYPES-1)]>=0) #define checktriggertype(type, flag) (triggertypes[(type) & (NUMTRIGGERTYPES-1)] & (flag)) static inline void cleartriggerflags(extentity &e) { e.flags &= ~(EF_ANIM | EF_NOVIS | EF_NOSHADOW | EF_NOCOLLIDE); } static inline void setuptriggerflags(fpsentity &e) { cleartriggerflags(e); e.flags |= EF_ANIM; if(checktriggertype(e.attr3, TRIG_COLLIDE|TRIG_DISAPPEAR)) e.flags |= EF_NOSHADOW; if(!checktriggertype(e.attr3, TRIG_COLLIDE)) e.flags |= EF_NOCOLLIDE; switch(e.triggerstate) { case TRIGGERING: if(checktriggertype(e.attr3, TRIG_COLLIDE) && lastmillis-e.lasttrigger >= 500) e.flags |= EF_NOCOLLIDE; break; case TRIGGERED: if(checktriggertype(e.attr3, TRIG_COLLIDE)) e.flags |= EF_NOCOLLIDE; break; case TRIGGER_DISAPPEARED: e.flags |= EF_NOVIS | EF_NOCOLLIDE; break; } } void resettriggers() { loopv(ents) { fpsentity &e = *(fpsentity *)ents[i]; if(e.type != ET_MAPMODEL || !validtrigger(e.attr3)) continue; e.triggerstate = TRIGGER_RESET; e.lasttrigger = 0; setuptriggerflags(e); } } void unlocktriggers(int tag, int oldstate = TRIGGER_RESET, int newstate = TRIGGERING) { loopv(ents) { fpsentity &e = *(fpsentity *)ents[i]; if(e.type != ET_MAPMODEL || !validtrigger(e.attr3)) continue; if(e.attr4 == tag && e.triggerstate == oldstate && checktriggertype(e.attr3, TRIG_LOCKED)) { if(newstate == TRIGGER_RESETTING && checktriggertype(e.attr3, TRIG_COLLIDE) && overlapsdynent(e.o, 20)) continue; e.triggerstate = newstate; e.lasttrigger = lastmillis; if(checktriggertype(e.attr3, TRIG_RUMBLE)) playsound(S_RUMBLE, &e.o); } } } ICOMMAND(trigger, "ii", (int *tag, int *state), { if(*state) unlocktriggers(*tag); else unlocktriggers(*tag, TRIGGERED, TRIGGER_RESETTING); }); VAR(triggerstate, -1, 0, 1); void doleveltrigger(int trigger, int state) { defformatstring(aliasname, "level_trigger_%d", trigger); if(identexists(aliasname)) { triggerstate = state; execident(aliasname); } } void checktriggers() { if(player1->state != CS_ALIVE) return; vec o = player1->feetpos(); loopv(ents) { fpsentity &e = *(fpsentity *)ents[i]; if(e.type != ET_MAPMODEL || !validtrigger(e.attr3)) continue; switch(e.triggerstate) { case TRIGGERING: case TRIGGER_RESETTING: if(lastmillis-e.lasttrigger>=1000) { if(e.attr4) { if(e.triggerstate == TRIGGERING) unlocktriggers(e.attr4); else unlocktriggers(e.attr4, TRIGGERED, TRIGGER_RESETTING); } if(checktriggertype(e.attr3, TRIG_DISAPPEAR)) e.triggerstate = TRIGGER_DISAPPEARED; else if(e.triggerstate==TRIGGERING && checktriggertype(e.attr3, TRIG_TOGGLE)) e.triggerstate = TRIGGERED; else e.triggerstate = TRIGGER_RESET; } setuptriggerflags(e); break; case TRIGGER_RESET: if(e.lasttrigger) { if(checktriggertype(e.attr3, TRIG_AUTO_RESET|TRIG_MANY|TRIG_LOCKED) && e.o.dist(o)-player1->radius>=(checktriggertype(e.attr3, TRIG_COLLIDE) ? 20 : 12)) e.lasttrigger = 0; break; } else if(e.o.dist(o)-player1->radius>=(checktriggertype(e.attr3, TRIG_COLLIDE) ? 20 : 12)) break; else if(checktriggertype(e.attr3, TRIG_LOCKED)) { if(!e.attr4) break; doleveltrigger(e.attr4, -1); e.lasttrigger = lastmillis; break; } e.triggerstate = TRIGGERING; e.lasttrigger = lastmillis; setuptriggerflags(e); if(checktriggertype(e.attr3, TRIG_RUMBLE)) playsound(S_RUMBLE, &e.o); if(checktriggertype(e.attr3, TRIG_ENDSP)) endsp(false); if(e.attr4) doleveltrigger(e.attr4, 1); break; case TRIGGERED: if(e.o.dist(o)-player1->radius<(checktriggertype(e.attr3, TRIG_COLLIDE) ? 20 : 12)) { if(e.lasttrigger) break; } else if(checktriggertype(e.attr3, TRIG_AUTO_RESET)) { if(lastmillis-e.lasttrigger<6000) break; } else if(checktriggertype(e.attr3, TRIG_MANY)) { e.lasttrigger = 0; break; } else break; if(checktriggertype(e.attr3, TRIG_COLLIDE) && overlapsdynent(e.o, 20)) break; e.triggerstate = TRIGGER_RESETTING; e.lasttrigger = lastmillis; setuptriggerflags(e); if(checktriggertype(e.attr3, TRIG_RUMBLE)) playsound(S_RUMBLE, &e.o); if(checktriggertype(e.attr3, TRIG_ENDSP)) endsp(false); if(e.attr4) doleveltrigger(e.attr4, 0); break; } } } void animatemapmodel(const extentity &e, int &anim, int &basetime) { const fpsentity &f = (const fpsentity &)e; if(validtrigger(f.attr3)) switch(f.triggerstate) { case TRIGGER_RESET: anim = ANIM_TRIGGER|ANIM_START; break; case TRIGGERING: anim = ANIM_TRIGGER; basetime = f.lasttrigger; break; case TRIGGERED: anim = ANIM_TRIGGER|ANIM_END; break; case TRIGGER_RESETTING: anim = ANIM_TRIGGER|ANIM_REVERSE; basetime = f.lasttrigger; break; } } void fixentity(extentity &e) { switch(e.type) { case FLAG: case BOX: case BARREL: case PLATFORM: case ELEVATOR: e.attr5 = e.attr4; e.attr4 = e.attr3; case TELEDEST: e.attr3 = e.attr2; case MONSTER: e.attr2 = e.attr1; case RESPAWNPOINT: e.attr1 = (int)player1->yaw; break; } } void entradius(extentity &e, bool color) { switch(e.type) { case TELEPORT: loopv(ents) if(ents[i]->type == TELEDEST && e.attr1==ents[i]->attr2) { renderentarrow(e, vec(ents[i]->o).sub(e.o).normalize(), e.o.dist(ents[i]->o)); break; } break; case JUMPPAD: renderentarrow(e, vec((int)(char)e.attr3*10.0f, (int)(char)e.attr2*10.0f, e.attr1*12.5f).normalize(), 4); break; case FLAG: case MONSTER: case TELEDEST: case RESPAWNPOINT: case BOX: case BARREL: case PLATFORM: case ELEVATOR: { vec dir; vecfromyawpitch(e.attr1, 0, 1, 0, dir); renderentarrow(e, dir, 4); break; } case MAPMODEL: if(validtrigger(e.attr3)) renderentring(e, checktriggertype(e.attr3, TRIG_COLLIDE) ? 20 : 12); break; } } bool printent(extentity &e, char *buf, int len) { return false; } const char *entnameinfo(entity &e) { return ""; } const char *entname(int i) { static const char * const entnames[] = { "none?", "light", "mapmodel", "playerstart", "envmap", "particles", "sound", "spotlight", "shells", "bullets", "rockets", "riflerounds", "grenades", "cartridges", "health", "healthboost", "greenarmour", "yellowarmour", "quaddamage", "teleport", "teledest", "monster", "carrot", "jumppad", "base", "respawnpoint", "box", "barrel", "platform", "elevator", "flag", "", "", "", "", }; return i>=0 && size_t(i)