remove yet another massive chunk of game/network code
parent
b03c776b81
commit
bebef4e338
|
@ -45,7 +45,6 @@ client_src = [
|
|||
'../game/game.cc',
|
||||
'../game/render.cc',
|
||||
'../game/server.cc',
|
||||
'../game/weapon.cc'
|
||||
]
|
||||
|
||||
threads_dep = dependency('threads')
|
||||
|
|
|
@ -1056,13 +1056,14 @@ struct animmodel : model
|
|||
|
||||
void setanim(int animpart, int num, int frame, int range, float speed, int priority = 0)
|
||||
{
|
||||
if(animpart<0 || animpart>=MAXANIMPARTS || num<0 || num>=game::numanims()) return;
|
||||
const int NUM_ANIMS = 0; // FIXME
|
||||
if(animpart<0 || animpart>=MAXANIMPARTS || num<0 || num>=NUM_ANIMS) return;
|
||||
if(frame<0 || range<=0 || !meshes || !meshes->hasframes(frame, range))
|
||||
{
|
||||
conoutf("invalid frame %d, range %d in model %s", frame, range, model->name);
|
||||
return;
|
||||
}
|
||||
if(!anims[animpart]) anims[animpart] = new vector<animspec>[game::numanims()];
|
||||
if(!anims[animpart]) anims[animpart] = new vector<animspec>[NUM_ANIMS];
|
||||
animspec &spec = anims[animpart][num].add();
|
||||
spec.frame = frame;
|
||||
spec.range = range;
|
||||
|
|
|
@ -91,12 +91,12 @@ void removetrackeddynlights(physent *owner)
|
|||
void updatedynlights()
|
||||
{
|
||||
cleardynlights();
|
||||
game::adddynlights();
|
||||
//game::adddynlights();
|
||||
|
||||
loopv(dynlights)
|
||||
{
|
||||
dynlight &d = dynlights[i];
|
||||
if(d.owner) game::dynlighttrack(d.owner, d.o, d.hud);
|
||||
//if(d.owner) game::dynlighttrack(d.owner, d.o, d.hud);
|
||||
d.calcradius();
|
||||
d.calccolor();
|
||||
}
|
||||
|
|
|
@ -1579,7 +1579,7 @@ bool bounce(physent *d, float secs, float elasticity, float waterfric, float gra
|
|||
}
|
||||
else if(collideplayer) break;
|
||||
d->o = old;
|
||||
game::bounced(d, collidewall);
|
||||
//game::bounced(d, collidewall);
|
||||
float c = collidewall.dot(d->vel),
|
||||
k = 1.0f + (1.0f-elasticity)*c/d->vel.magnitude();
|
||||
d->vel.mul(k);
|
||||
|
|
|
@ -1076,7 +1076,7 @@ bool matchanim(const char *name, const char *pattern)
|
|||
ICOMMAND(findanims, "s", (char *name),
|
||||
{
|
||||
vector<int> anims;
|
||||
game::findanims(name, anims);
|
||||
//game::findanims(name, anims);
|
||||
vector<char> buf;
|
||||
string num;
|
||||
loopv(anims)
|
||||
|
|
|
@ -199,7 +199,7 @@ struct partrenderer
|
|||
{
|
||||
o = p->o;
|
||||
d = p->d;
|
||||
if(type&PT_TRACK && p->owner) game::particletrack(p->owner, o, d);
|
||||
//if(type&PT_TRACK && p->owner) game::particletrack(p->owner, o, d);
|
||||
if(p->fade <= 5)
|
||||
{
|
||||
ts = 1;
|
||||
|
|
|
@ -1790,7 +1790,7 @@ template<class MDL> struct skelcommands : modelcommands<MDL, struct MDL::skelmes
|
|||
if(!MDL::loading || MDL::loading->parts.empty()) { conoutf("not loading an %s", MDL::formatname()); return; }
|
||||
|
||||
vector<int> anims;
|
||||
game::findanims(anim, anims);
|
||||
//game::findanims(anim, anims);
|
||||
if(anims.empty()) conoutf("could not find animation %s", anim);
|
||||
else
|
||||
{
|
||||
|
|
|
@ -2878,7 +2878,7 @@ namespace UI
|
|||
else
|
||||
{
|
||||
vector<int> anims;
|
||||
game::findanims(animspec, anims);
|
||||
//game::findanims(animspec, anims);
|
||||
if(anims.length()) anim = anims[0];
|
||||
}
|
||||
}
|
||||
|
@ -2936,7 +2936,7 @@ namespace UI
|
|||
int sx1, sy1, sx2, sy2;
|
||||
window->calcscissor(sx, sy, sx+w, sy+h, sx1, sy1, sx2, sy2, false);
|
||||
modelpreview::start(sx1, sy1, sx2-sx1, sy2-sy1, false, clipstack.length() > 0);
|
||||
game::renderplayerpreview(model, color, team, weapon);
|
||||
game::renderplayerpreview(model, color, weapon);
|
||||
if(clipstack.length()) clipstack.last().scissor();
|
||||
modelpreview::end();
|
||||
}
|
||||
|
|
|
@ -501,7 +501,7 @@ template<class MDL> struct vertcommands : modelcommands<MDL, struct MDL::vertmes
|
|||
{
|
||||
if(!MDL::loading || MDL::loading->parts.empty()) { conoutf("not loading an %s", MDL::formatname()); return; }
|
||||
vector<int> anims;
|
||||
game::findanims(anim, anims);
|
||||
//game::findanims(anim, anims);
|
||||
if(anims.empty()) conoutf("could not find animation %s", anim);
|
||||
else loopv(anims)
|
||||
{
|
||||
|
|
|
@ -35,93 +35,6 @@ namespace game
|
|||
settexture("media/interface/radar/radar.png", 3);
|
||||
}
|
||||
|
||||
void drawradar(float x, float y, float s)
|
||||
{
|
||||
gle::defvertex(2);
|
||||
gle::deftexcoord0();
|
||||
gle::begin(GL_TRIANGLE_STRIP);
|
||||
gle::attribf(x, y); gle::attribf(0, 0);
|
||||
gle::attribf(x+s, y); gle::attribf(1, 0);
|
||||
gle::attribf(x, y+s); gle::attribf(0, 1);
|
||||
gle::attribf(x+s, y+s); gle::attribf(1, 1);
|
||||
gle::end();
|
||||
}
|
||||
|
||||
void drawteammate(gameent *d, float x, float y, float s, gameent *o, float scale, float blipsize = 1)
|
||||
{
|
||||
vec dir = d->o;
|
||||
dir.sub(o->o).div(scale);
|
||||
float dist = dir.magnitude2(), maxdist = 1 - 0.05f - 0.05f;
|
||||
if(dist >= maxdist) dir.mul(maxdist/dist);
|
||||
dir.rotate_around_z(-camera1->yaw*RAD);
|
||||
float bs = 0.06f*blipsize*s,
|
||||
bx = x + s*0.5f*(1.0f + dir.x),
|
||||
by = y + s*0.5f*(1.0f + dir.y);
|
||||
vec v(-0.5f, -0.5f, 0);
|
||||
v.rotate_around_z((90+o->yaw-camera1->yaw)*RAD);
|
||||
gle::attribf(bx + bs*v.x, by + bs*v.y); gle::attribf(0, 0);
|
||||
gle::attribf(bx + bs*v.y, by - bs*v.x); gle::attribf(1, 0);
|
||||
gle::attribf(bx - bs*v.x, by - bs*v.y); gle::attribf(1, 1);
|
||||
gle::attribf(bx - bs*v.y, by + bs*v.x); gle::attribf(0, 1);
|
||||
}
|
||||
|
||||
void setbliptex(int team, const char *type = "")
|
||||
{
|
||||
defformatstring(blipname, "media/interface/radar/blip%s%s.png", teamblipcolor[validteam(team) ? team : 0], type);
|
||||
settexture(blipname, 3);
|
||||
}
|
||||
|
||||
void drawplayerblip(gameent *d, float x, float y, float s, float blipsize = 1)
|
||||
{
|
||||
if(d->state != CS_ALIVE && d->state != CS_DEAD) return;
|
||||
float scale = calcradarscale();
|
||||
setbliptex(d->team, d->state == CS_DEAD ? "_dead" : "_alive");
|
||||
gle::defvertex(2);
|
||||
gle::deftexcoord0();
|
||||
gle::begin(GL_QUADS);
|
||||
drawteammate(d, x, y, s, d, scale, blipsize);
|
||||
gle::end();
|
||||
}
|
||||
|
||||
void drawteammates(gameent *d, float x, float y, float s)
|
||||
{
|
||||
if(!radarteammates) return;
|
||||
float scale = calcradarscale();
|
||||
int alive = 0, dead = 0;
|
||||
loopv(players)
|
||||
{
|
||||
gameent *o = players[i];
|
||||
if(o != d && o->state == CS_ALIVE && o->team == d->team)
|
||||
{
|
||||
if(!alive++)
|
||||
{
|
||||
setbliptex(d->team, "_alive");
|
||||
gle::defvertex(2);
|
||||
gle::deftexcoord0();
|
||||
gle::begin(GL_QUADS);
|
||||
}
|
||||
drawteammate(d, x, y, s, o, scale);
|
||||
}
|
||||
}
|
||||
if(alive) gle::end();
|
||||
loopv(players)
|
||||
{
|
||||
gameent *o = players[i];
|
||||
if(o != d && o->state == CS_DEAD && o->team == d->team)
|
||||
{
|
||||
if(!dead++)
|
||||
{
|
||||
setbliptex(d->team, "_dead");
|
||||
gle::defvertex(2);
|
||||
gle::deftexcoord0();
|
||||
gle::begin(GL_QUADS);
|
||||
}
|
||||
drawteammate(d, x, y, s, o, scale);
|
||||
}
|
||||
}
|
||||
if(dead) gle::end();
|
||||
}
|
||||
|
||||
clientmode *cmode = NULL;
|
||||
|
||||
void setclientmode()
|
||||
|
@ -166,7 +79,7 @@ namespace game
|
|||
addmsg(N_EDITMODE, "ri", on ? 1 : 0);
|
||||
if(player1->state==CS_DEAD) deathstate(player1, true);
|
||||
disablezoom();
|
||||
player1->suicided = player1->respawned = -2;
|
||||
player1->respawned = -2;
|
||||
checkfollow();
|
||||
}
|
||||
|
||||
|
@ -508,15 +421,11 @@ namespace game
|
|||
}
|
||||
}
|
||||
|
||||
VARP(teamcolorchat, 0, 1, 1);
|
||||
const char *chatcolorname(gameent *d) { return teamcolorchat ? teamcolorname(d, NULL) : colorname(d); }
|
||||
const char *chatcolorname(gameent *d) { return colorname(d); }
|
||||
|
||||
void toserver(char *text) { conoutf(CON_CHAT, "%s:%s %s", chatcolorname(player1), teamtextcode[0], text); addmsg(N_TEXT, "rcs", player1, text); }
|
||||
void toserver(char *text) { conoutf(CON_CHAT, "%s: %s", chatcolorname(player1), text); addmsg(N_TEXT, "rcs", player1, text); }
|
||||
COMMANDN(say, toserver, "C");
|
||||
|
||||
void sayteam(char *text) { if(!m_teammode || !validteam(player1->team)) return; conoutf(CON_TEAMCHAT, "%s:%s %s", chatcolorname(player1), teamtextcode[player1->team], text); addmsg(N_SAYTEAM, "rcs", player1, text); }
|
||||
COMMAND(sayteam, "C");
|
||||
|
||||
ICOMMAND(servcmd, "C", (char *cmd), addmsg(N_SERVCMD, "rs", cmd));
|
||||
|
||||
static void sendposition(gameent *d, packetbuf &q)
|
||||
|
@ -761,24 +670,6 @@ namespace game
|
|||
break;
|
||||
}
|
||||
|
||||
case N_TELEPORT:
|
||||
{
|
||||
int cn = getint(p), tp = getint(p), td = getint(p);
|
||||
gameent *d = getclient(cn);
|
||||
if(!d || d->lifesequence < 0 || d->state==CS_DEAD) continue;
|
||||
entities::teleporteffects(d, tp, td, false);
|
||||
break;
|
||||
}
|
||||
|
||||
case N_JUMPPAD:
|
||||
{
|
||||
int cn = getint(p), jp = getint(p);
|
||||
gameent *d = getclient(cn);
|
||||
if(!d || d->lifesequence < 0 || d->state==CS_DEAD) continue;
|
||||
entities::jumppadeffects(d, jp, false);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
neterr("type");
|
||||
return;
|
||||
|
@ -792,24 +683,8 @@ namespace game
|
|||
{
|
||||
if(d==player1) getint(p);
|
||||
else d->state = getint(p);
|
||||
d->frags = getint(p);
|
||||
d->flags = getint(p);
|
||||
d->deaths = getint(p);
|
||||
}
|
||||
d->lifesequence = getint(p);
|
||||
d->health = getint(p);
|
||||
d->maxhealth = getint(p);
|
||||
if(resume && d==player1)
|
||||
{
|
||||
getint(p);
|
||||
loopi(NUMGUNS) getint(p);
|
||||
}
|
||||
else
|
||||
{
|
||||
int gun = getint(p);
|
||||
d->gunselect = clamp(gun, 0, NUMGUNS-1);
|
||||
loopi(NUMGUNS) d->ammo[i] = getint(p);
|
||||
}
|
||||
}
|
||||
|
||||
void parsemessages(int cn, gameent *d, ucharbuf &p)
|
||||
|
@ -847,7 +722,6 @@ namespace game
|
|||
int cn = getint(p);
|
||||
gameent *a = cn >= 0 ? getclient(cn) : NULL;
|
||||
gamepaused = val;
|
||||
player1->attacking = ACT_IDLE;
|
||||
if(a) conoutf("%s %s the game", colorname(a), val ? "paused" : "resumed");
|
||||
else conoutf("game is %s", val ? "paused" : "resumed");
|
||||
break;
|
||||
|
@ -883,21 +757,7 @@ namespace game
|
|||
filtertext(text, text, true, true);
|
||||
if(d->state!=CS_DEAD && d->state!=CS_SPECTATOR)
|
||||
particle_textcopy(d->abovehead(), text, PART_TEXT, 2000, 0x32FF64, 4.0f, -8);
|
||||
conoutf(CON_CHAT, "%s:%s %s", chatcolorname(d), teamtextcode[0], text);
|
||||
break;
|
||||
}
|
||||
|
||||
case N_SAYTEAM:
|
||||
{
|
||||
int tcn = getint(p);
|
||||
gameent *t = getclient(tcn);
|
||||
getstring(text, p);
|
||||
filtertext(text, text, true, true);
|
||||
if(!t) break;
|
||||
int team = validteam(t->team) ? t->team : 0;
|
||||
if(t->state!=CS_DEAD && t->state!=CS_SPECTATOR)
|
||||
particle_textcopy(t->abovehead(), text, PART_TEXT, 2000, teamtextcolor[team], 4.0f, -8);
|
||||
conoutf(CON_TEAMCHAT, "%s:%s %s", chatcolorname(t), teamtextcode[team], text);
|
||||
conoutf(CON_CHAT, "%s: %s", chatcolorname(d), text);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -934,10 +794,8 @@ namespace game
|
|||
if(needclipboard >= 0) needclipboard++;
|
||||
}
|
||||
copystring(d->name, text, MAXNAMELEN+1);
|
||||
d->team = getint(p);
|
||||
if(!validteam(d->team)) d->team = 0;
|
||||
d->playermodel = getint(p);
|
||||
d->playercolor = getint(p);
|
||||
d->playermodel = 0;
|
||||
d->playercolor = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -963,7 +821,6 @@ namespace game
|
|||
{
|
||||
if(d)
|
||||
{
|
||||
if(d->state==CS_DEAD && d->lastpain) saveragdoll(d);
|
||||
d->respawn();
|
||||
}
|
||||
parsestate(d, p);
|
||||
|
@ -979,7 +836,6 @@ namespace game
|
|||
int scn = getint(p);
|
||||
gameent *s = getclient(scn);
|
||||
if(!s) { parsestate(NULL, p); break; }
|
||||
if(s->state==CS_DEAD && s->lastpain) saveragdoll(s);
|
||||
if(s==player1)
|
||||
{
|
||||
if(editmode) toggleedit();
|
||||
|
@ -988,37 +844,14 @@ namespace game
|
|||
parsestate(s, p);
|
||||
s->state = CS_ALIVE;
|
||||
if(cmode) cmode->pickspawn(s);
|
||||
else findplayerspawn(s, -1, m_teammode ? s->team : 0);
|
||||
else findplayerspawn(s, -1, 0);
|
||||
if(s == player1)
|
||||
{
|
||||
lasthit = 0;
|
||||
}
|
||||
if(cmode) cmode->respawned(s);
|
||||
checkfollow();
|
||||
addmsg(N_SPAWN, "rcii", s, s->lifesequence, s->gunselect);
|
||||
break;
|
||||
}
|
||||
|
||||
case N_HITPUSH:
|
||||
{
|
||||
int tcn = getint(p), atk = getint(p), damage = getint(p);
|
||||
gameent *target = getclient(tcn);
|
||||
vec dir;
|
||||
loopk(3) dir[k] = getint(p)/DNF;
|
||||
if(!target || !validatk(atk)) break;
|
||||
target->hitpush(damage * (target->health<=0 ? deadpush : 1), dir, NULL, atk);
|
||||
break;
|
||||
}
|
||||
|
||||
case N_DIED:
|
||||
{
|
||||
int vcn = getint(p), acn = getint(p), frags = getint(p), tfrags = getint(p);
|
||||
gameent *victim = getclient(vcn),
|
||||
*actor = getclient(acn);
|
||||
if(!actor) break;
|
||||
actor->frags = frags;
|
||||
if(!victim) break;
|
||||
killed(victim, actor);
|
||||
addmsg(N_SPAWN, "rci", s, s->lifesequence);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -1179,10 +1012,6 @@ namespace game
|
|||
d->ping = getint(p);
|
||||
break;
|
||||
|
||||
case N_TIMEUP:
|
||||
timeupdate(getint(p));
|
||||
break;
|
||||
|
||||
case N_SERVMSG:
|
||||
getstring(text, p);
|
||||
conoutf("%s", text);
|
||||
|
|
|
@ -25,294 +25,47 @@ namespace entities
|
|||
const char *itemname(int i)
|
||||
{
|
||||
return NULL;
|
||||
#if 0
|
||||
int t = ents[i]->type;
|
||||
if(!validitem(t)) return NULL;
|
||||
return itemstats[t-I_FIRST].name;
|
||||
#endif
|
||||
}
|
||||
|
||||
int itemicon(int i)
|
||||
{
|
||||
return -1;
|
||||
#if 0
|
||||
int t = ents[i]->type;
|
||||
if(!validitem(t)) return -1;
|
||||
return itemstats[t-I_FIRST].icon;
|
||||
#endif
|
||||
}
|
||||
|
||||
const char *entmdlname(int type)
|
||||
{
|
||||
static const char * const entmdlnames[MAXENTTYPES] =
|
||||
{
|
||||
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
|
||||
"game/teleport", NULL, NULL,
|
||||
NULL
|
||||
};
|
||||
return entmdlnames[type];
|
||||
return NULL;
|
||||
}
|
||||
|
||||
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;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void preloadentities()
|
||||
{
|
||||
loopi(MAXENTTYPES)
|
||||
{
|
||||
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 TELEPORT:
|
||||
if(e.attr2 < 0) continue;
|
||||
break;
|
||||
default:
|
||||
if(!e.spawned() || !validitem(e.type)) continue;
|
||||
break;
|
||||
}
|
||||
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(mdlname, ANIM_MAPMODEL|ANIM_LOOP, p, lastmillis/(float)revs, 0, 0, MDL_CULL_VFC | MDL_CULL_DIST | MDL_CULL_OCCLUDED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void addammo(int type, int &v, bool local)
|
||||
{
|
||||
#if 0
|
||||
itemstat &is = itemstats[type-I_FIRST];
|
||||
v += is.add;
|
||||
if(v>is.max) v = is.max;
|
||||
//if(local) msgsound(is.sound);
|
||||
#endif
|
||||
}
|
||||
|
||||
// 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, gameent *d)
|
||||
{
|
||||
#if 0
|
||||
if(!ents.inrange(n)) return;
|
||||
extentity *e = ents[n];
|
||||
int type = e->type;
|
||||
if(!validitem(type)) return;
|
||||
e->clearspawned();
|
||||
e->clearnopickup();
|
||||
if(!d) return;
|
||||
itemstat &is = itemstats[type-I_FIRST];
|
||||
gameent *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_FIRST].sound, d!=h ? &d->o : NULL, NULL, 0, 0, 0, -1, 0, 1500);
|
||||
d->pickup(type);
|
||||
if(d==h) switch(type)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// these functions are called when the client touches the item
|
||||
|
||||
void teleporteffects(gameent *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; }
|
||||
gameent *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(gameent *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, gameent *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);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VARR(teleteam, 0, 1, 1);
|
||||
|
||||
void trypickup(int n, gameent *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(!teleteam && m_teammode) 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 JUMPPAD:
|
||||
{
|
||||
if(d->lastpickup==e->type && lastmillis-d->lastpickupmillis<300) break;
|
||||
d->lastpickup = e->type;
|
||||
d->lastpickupmillis = lastmillis;
|
||||
jumppadeffects(d, n, true);
|
||||
d->falling = vec(0, 0, 0);
|
||||
d->physstate = PHYS_FALL;
|
||||
d->timeinair = 1;
|
||||
d->vel = vec(e->attr3*10.0f, e->attr2*10.0f, e->attr1*12.5f);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void checkitems(gameent *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) continue;
|
||||
float dist = e.o.dist(o);
|
||||
if(dist<(e.type==TELEPORT ? 16 : 12)) trypickup(i, d);
|
||||
}
|
||||
}
|
||||
|
||||
void putitems(packetbuf &p) // puts items in network stream and also spawns them locally
|
||||
{
|
||||
putint(p, N_ITEMLIST);
|
||||
loopv(ents) if(validitem(ents[i]->type))
|
||||
{
|
||||
putint(p, i);
|
||||
putint(p, ents[i]->type);
|
||||
}
|
||||
putint(p, -1);
|
||||
}
|
||||
|
||||
void resetspawns() { loopv(ents) { extentity *e = ents[i]; e->clearspawned(); e->clearnopickup(); } }
|
||||
void resetspawns() { }
|
||||
|
||||
void spawnitems(bool force)
|
||||
{
|
||||
loopv(ents)
|
||||
{
|
||||
extentity *e = ents[i];
|
||||
if(validitem(e->type))
|
||||
{
|
||||
e->setspawned(force || !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(); } }
|
||||
|
@ -331,44 +84,10 @@ namespace entities
|
|||
|
||||
void fixentity(extentity &e)
|
||||
{
|
||||
switch(e.type)
|
||||
{
|
||||
case FLAG:
|
||||
e.attr5 = e.attr4;
|
||||
e.attr4 = e.attr3;
|
||||
case TELEDEST:
|
||||
e.attr3 = e.attr2;
|
||||
e.attr2 = e.attr1;
|
||||
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 TELEDEST:
|
||||
{
|
||||
vec dir;
|
||||
vecfromyawpitch(e.attr1, 0, 1, 0, dir);
|
||||
renderentarrow(e, dir, 4);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool printent(extentity &e, char *buf, int len)
|
||||
|
@ -382,8 +101,6 @@ namespace entities
|
|||
static const char * const entnames[MAXENTTYPES] =
|
||||
{
|
||||
"none?", "light", "mapmodel", "playerstart", "envmap", "particles", "sound", "spotlight", "decal",
|
||||
"teleport", "teledest", "jumppad",
|
||||
"flag"
|
||||
};
|
||||
return i>=0 && size_t(i)<sizeof(entnames)/sizeof(entnames[0]) ? entnames[i] : "";
|
||||
}
|
||||
|
@ -397,7 +114,6 @@ namespace entities
|
|||
|
||||
float dropheight(entity &e)
|
||||
{
|
||||
if(e.type==FLAG) return 0.0f;
|
||||
return 4.0f;
|
||||
}
|
||||
#endif
|
||||
|
|
240
src/game/game.cc
240
src/game/game.cc
|
@ -2,7 +2,6 @@
|
|||
|
||||
namespace game
|
||||
{
|
||||
bool intermission = false;
|
||||
int maptime = 0, maprealtime = 0, maplimit = -1;
|
||||
int lasthit = 0, lastspawnattempt = 0;
|
||||
|
||||
|
@ -80,8 +79,6 @@ namespace game
|
|||
|
||||
void resetgamestate()
|
||||
{
|
||||
clearprojectiles();
|
||||
clearbouncers();
|
||||
}
|
||||
|
||||
gameent *spawnstate(gameent *d) // reset player state not persistent accross spawns
|
||||
|
@ -101,7 +98,6 @@ namespace game
|
|||
|
||||
gameent *pointatplayer()
|
||||
{
|
||||
loopv(players) if(players[i] != player1 && intersect(players[i], player1->o, worldpos)) return players[i];
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
@ -173,32 +169,6 @@ namespace game
|
|||
|
||||
void otherplayers(int curtime)
|
||||
{
|
||||
loopv(players)
|
||||
{
|
||||
gameent *d = players[i];
|
||||
if(d == player1) continue;
|
||||
|
||||
if(d->state==CS_DEAD && d->ragdoll) moveragdoll(d);
|
||||
else if(!intermission)
|
||||
{
|
||||
if(lastmillis - d->lastaction >= d->gunwait) d->gunwait = 0;
|
||||
}
|
||||
|
||||
const int lagtime = totalmillis-d->lastupdate;
|
||||
if(!lagtime || intermission) continue;
|
||||
else if(lagtime>1000 && d->state==CS_ALIVE)
|
||||
{
|
||||
d->state = CS_LAGGED;
|
||||
continue;
|
||||
}
|
||||
if(d->state==CS_ALIVE || d->state==CS_EDITING)
|
||||
{
|
||||
crouchplayer(d, 10, false);
|
||||
if(smoothmove && d->smoothmillis>0) predictplayer(d, true);
|
||||
else moveplayer(d, 1, false);
|
||||
}
|
||||
else if(d->state==CS_DEAD && !d->ragdoll && lastmillis-d->lastpain<2000) moveplayer(d, 1, true);
|
||||
}
|
||||
}
|
||||
|
||||
void updateworld() // main game update loop
|
||||
|
@ -207,30 +177,15 @@ namespace game
|
|||
if(!curtime) { gets2c(); if(player1->clientnum>=0) c2sinfo(); return; }
|
||||
|
||||
physicsframe();
|
||||
updateweapons(curtime);
|
||||
otherplayers(curtime);
|
||||
moveragdolls();
|
||||
gets2c();
|
||||
if(connected)
|
||||
{
|
||||
if(player1->state == CS_DEAD)
|
||||
{
|
||||
if(player1->ragdoll) moveragdoll(player1);
|
||||
else if(lastmillis-player1->lastpain<2000)
|
||||
{
|
||||
player1->move = player1->strafe = 0;
|
||||
moveplayer(player1, 10, true);
|
||||
}
|
||||
}
|
||||
else if(!intermission)
|
||||
{
|
||||
if(player1->ragdoll) cleanragdoll(player1);
|
||||
crouchplayer(player1, 10, true);
|
||||
moveplayer(player1, 10, true);
|
||||
swayhudgun(curtime);
|
||||
entities::checkitems(player1);
|
||||
if(cmode) cmode->checkitems(player1);
|
||||
}
|
||||
crouchplayer(player1, 10, true);
|
||||
moveplayer(player1, 10, true);
|
||||
entities::checkitems(player1);
|
||||
if(cmode) cmode->checkitems(player1);
|
||||
}
|
||||
if(player1->clientnum>=0) c2sinfo(); // do this last, to reduce the effective frame lag
|
||||
}
|
||||
|
@ -238,7 +193,7 @@ namespace game
|
|||
void spawnplayer(gameent *d) // place at random spawn
|
||||
{
|
||||
if(cmode) cmode->pickspawn(d);
|
||||
else findplayerspawn(d, -1, m_teammode ? d->team : 0);
|
||||
else findplayerspawn(d, -1, 0);
|
||||
spawnstate(d);
|
||||
if(d==player1)
|
||||
{
|
||||
|
@ -255,15 +210,6 @@ namespace game
|
|||
{
|
||||
if(player1->state==CS_DEAD)
|
||||
{
|
||||
player1->attacking = ACT_IDLE;
|
||||
int wait = cmode ? cmode->respawnwait(player1) : 0;
|
||||
if(wait>0)
|
||||
{
|
||||
lastspawnattempt = lastmillis;
|
||||
//conoutf(CON_GAMEINFO, "\f2you must wait %d second%s before respawn!", wait, wait!=1 ? "s" : "");
|
||||
return;
|
||||
}
|
||||
if(lastmillis < player1->lastpain + spawnwait) return;
|
||||
respawnself();
|
||||
}
|
||||
}
|
||||
|
@ -271,87 +217,36 @@ namespace game
|
|||
|
||||
// inputs
|
||||
|
||||
VARP(attackspawn, 0, 1, 1);
|
||||
|
||||
void doaction(int act)
|
||||
{
|
||||
if(!connected || intermission) return;
|
||||
if((player1->attacking = act) && attackspawn) respawn();
|
||||
}
|
||||
|
||||
ICOMMAND(shoot, "D", (int *down), doaction(*down ? ACT_SHOOT : ACT_IDLE));
|
||||
ICOMMAND(melee, "D", (int *down), doaction(*down ? ACT_MELEE : ACT_IDLE));
|
||||
|
||||
VARP(jumpspawn, 0, 1, 1);
|
||||
|
||||
bool canjump()
|
||||
{
|
||||
if(!connected || intermission) return false;
|
||||
if(!connected) return false;
|
||||
if(jumpspawn) respawn();
|
||||
return player1->state!=CS_DEAD;
|
||||
}
|
||||
|
||||
bool cancrouch()
|
||||
{
|
||||
if(!connected || intermission) return false;
|
||||
if(!connected) return false;
|
||||
return player1->state!=CS_DEAD;
|
||||
}
|
||||
|
||||
bool allowmove(physent *d)
|
||||
{
|
||||
if(d->type!=ENT_PLAYER) return true;
|
||||
return !((gameent *)d)->lasttaunt || lastmillis-((gameent *)d)->lasttaunt>=1000;
|
||||
return true;
|
||||
}
|
||||
|
||||
void taunt()
|
||||
{
|
||||
if(player1->state!=CS_ALIVE || player1->physstate<PHYS_SLOPE) return;
|
||||
if(lastmillis-player1->lasttaunt<1000) return;
|
||||
player1->lasttaunt = lastmillis;
|
||||
addmsg(N_TAUNT, "rc", player1);
|
||||
}
|
||||
COMMAND(taunt, "");
|
||||
|
||||
VARP(hitsound, 0, 0, 1);
|
||||
|
||||
void damaged(int damage, gameent *d, gameent *actor, bool local)
|
||||
{
|
||||
if((d->state!=CS_ALIVE && d->state != CS_LAGGED && d->state != CS_SPAWNING) || intermission) return;
|
||||
|
||||
if(local) damage = d->dodamage(damage);
|
||||
else if(actor==player1) return;
|
||||
|
||||
gameent *h = hudplayer();
|
||||
if(h!=player1 && actor==h && d!=actor)
|
||||
{
|
||||
if(hitsound && lasthit != lastmillis) playsound(S_HIT);
|
||||
lasthit = lastmillis;
|
||||
}
|
||||
if(d==h)
|
||||
{
|
||||
damageblend(damage);
|
||||
damagecompass(damage, actor->o);
|
||||
}
|
||||
damageeffect(damage, d, d!=h);
|
||||
|
||||
if(d->health<=0) { if(local) killed(d, actor); }
|
||||
else if(d==h) playsound(S_PAIN2);
|
||||
else playsound(S_PAIN1, &d->o);
|
||||
}
|
||||
|
||||
void deathstate(gameent *d, bool restore)
|
||||
{
|
||||
d->state = CS_DEAD;
|
||||
d->lastpain = lastmillis;
|
||||
if(!restore)
|
||||
{
|
||||
gibeffect(max(-d->health, 0), d->vel, d);
|
||||
d->deaths++;
|
||||
}
|
||||
if(d==player1)
|
||||
{
|
||||
disablezoom();
|
||||
d->attacking = ACT_IDLE;
|
||||
//d->pitch = 0;
|
||||
d->roll = 0;
|
||||
playsound(S_DIE2);
|
||||
|
@ -372,39 +267,11 @@ namespace game
|
|||
if(d->state==CS_EDITING)
|
||||
{
|
||||
d->editstate = CS_DEAD;
|
||||
d->deaths++;
|
||||
if(d!=player1) d->resetinterp();
|
||||
return;
|
||||
}
|
||||
else if((d->state!=CS_ALIVE && d->state != CS_LAGGED && d->state != CS_SPAWNING) || intermission) return;
|
||||
else if(d->state!=CS_ALIVE && d->state != CS_LAGGED && d->state != CS_SPAWNING) return;
|
||||
|
||||
gameent *h = followingplayer(player1);
|
||||
int contype = d==h || actor==h ? CON_FRAG_SELF : CON_FRAG_OTHER;
|
||||
const char *dname = "", *aname = "";
|
||||
if(m_teammode && teamcolorfrags)
|
||||
{
|
||||
dname = teamcolorname(d, "you");
|
||||
aname = teamcolorname(actor, "you");
|
||||
}
|
||||
else
|
||||
{
|
||||
dname = colorname(d, NULL, "you");
|
||||
aname = colorname(actor, NULL, "you");
|
||||
}
|
||||
if(d==actor)
|
||||
conoutf(contype, "\f2%s suicided%s", dname, d==player1 ? "!" : "");
|
||||
else if(isteam(d->team, actor->team))
|
||||
{
|
||||
contype |= CON_TEAMKILL;
|
||||
if(actor==player1) conoutf(contype, "\f6%s fragged a teammate (%s)", aname, dname);
|
||||
else if(d==player1) conoutf(contype, "\f6%s got fragged by a teammate (%s)", dname, aname);
|
||||
else conoutf(contype, "\f2%s fragged a teammate (%s)", aname, dname);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(d==player1) conoutf(contype, "\f2%s got fragged by %s", dname, aname);
|
||||
else conoutf(contype, "\f2%s fragged %s", aname, dname);
|
||||
}
|
||||
deathstate(d);
|
||||
}
|
||||
|
||||
|
@ -414,30 +281,8 @@ namespace game
|
|||
{
|
||||
maplimit = lastmillis + secs*1000;
|
||||
}
|
||||
else
|
||||
{
|
||||
intermission = true;
|
||||
player1->attacking = ACT_IDLE;
|
||||
if(cmode) cmode->gameover();
|
||||
conoutf(CON_GAMEINFO, "\f2intermission:");
|
||||
conoutf(CON_GAMEINFO, "\f2game has ended!");
|
||||
conoutf(CON_GAMEINFO, "\f2player frags: %d, deaths: %d", player1->frags, player1->deaths);
|
||||
int accuracy = (player1->totaldamage*100)/max(player1->totalshots, 1);
|
||||
conoutf(CON_GAMEINFO, "\f2player total damage dealt: %d, damage wasted: %d, accuracy(%%): %d", player1->totaldamage, player1->totalshots-player1->totaldamage, accuracy);
|
||||
|
||||
disablezoom();
|
||||
|
||||
execident("intermission");
|
||||
}
|
||||
}
|
||||
|
||||
ICOMMAND(getfrags, "", (), intret(player1->frags));
|
||||
ICOMMAND(getflags, "", (), intret(player1->flags));
|
||||
ICOMMAND(getdeaths, "", (), intret(player1->deaths));
|
||||
ICOMMAND(getaccuracy, "", (), intret((player1->totaldamage*100)/max(player1->totalshots, 1)));
|
||||
ICOMMAND(gettotaldamage, "", (), intret(player1->totaldamage));
|
||||
ICOMMAND(gettotalshots, "", (), intret(player1->totalshots));
|
||||
|
||||
vector<gameent *> clients;
|
||||
|
||||
gameent *newclient(int cn) // ensure valid entity
|
||||
|
@ -474,7 +319,6 @@ namespace game
|
|||
if(d)
|
||||
{
|
||||
if(notify && d->name[0]) conoutf("\f4leave:\f7 %s", colorname(d));
|
||||
removeweapons(d);
|
||||
removetrackedparticles(d);
|
||||
removetrackeddynlights(d);
|
||||
if(cmode) cmode->removeplayer(d);
|
||||
|
@ -505,16 +349,11 @@ namespace game
|
|||
|
||||
void startgame()
|
||||
{
|
||||
clearprojectiles();
|
||||
clearbouncers();
|
||||
clearragdolls();
|
||||
|
||||
// reset perma-state
|
||||
loopv(players) players[i]->startgame();
|
||||
|
||||
setclientmode();
|
||||
|
||||
intermission = false;
|
||||
maptime = maprealtime = 0;
|
||||
maplimit = -1;
|
||||
|
||||
|
@ -595,67 +434,18 @@ namespace game
|
|||
const char *colorname(gameent *d, const char *name, const char * alt, const char *color)
|
||||
{
|
||||
if(!name) name = alt && d == player1 ? alt : d->name;
|
||||
bool dup = !name[0] || duplicatename(d, name, alt);
|
||||
if(dup || color[0])
|
||||
{
|
||||
if(dup) return tempformatstring("\fs%s%s \f5(%d)\fr", color, name, d->clientnum);
|
||||
return tempformatstring("\fs%s%s\fr", color, name);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
VARP(teamcolortext, 0, 1, 1);
|
||||
|
||||
const char *teamcolorname(gameent *d, const char *alt)
|
||||
{
|
||||
if(!teamcolortext || !m_teammode || !validteam(d->team) || d->state == CS_SPECTATOR) return colorname(d, NULL, alt);
|
||||
return colorname(d, NULL, alt, teamtextcode[d->team]);
|
||||
}
|
||||
|
||||
const char *teamcolor(const char *prefix, const char *suffix, int team, const char *alt)
|
||||
{
|
||||
if(!teamcolortext || !m_teammode || !validteam(team)) return alt;
|
||||
return tempformatstring("\fs%s%s%s%s\fr", teamtextcode[team], prefix, teamnames[team], suffix);
|
||||
}
|
||||
|
||||
VARP(teamsounds, 0, 1, 1);
|
||||
|
||||
void teamsound(bool sameteam, int n, const vec *loc)
|
||||
{
|
||||
playsound(n, loc, NULL, teamsounds ? (m_teammode && sameteam ? SND_USE_ALT : SND_NO_ALT) : 0);
|
||||
}
|
||||
|
||||
void teamsound(gameent *d, int n, const vec *loc)
|
||||
{
|
||||
teamsound(isteam(d->team, player1->team), n, loc);
|
||||
}
|
||||
|
||||
bool needminimap() { return false; }
|
||||
|
||||
void drawicon(int icon, float x, float y, float sz)
|
||||
{
|
||||
settexture("media/interface/hud/items.png");
|
||||
float tsz = 0.25f, tx = tsz*(icon%4), ty = tsz*(icon/4);
|
||||
gle::defvertex(2);
|
||||
gle::deftexcoord0();
|
||||
gle::begin(GL_TRIANGLE_STRIP);
|
||||
gle::attribf(x, y); gle::attribf(tx, ty);
|
||||
gle::attribf(x+sz, y); gle::attribf(tx+tsz, ty);
|
||||
gle::attribf(x, y+sz); gle::attribf(tx, ty+tsz);
|
||||
gle::attribf(x+sz, y+sz); gle::attribf(tx+tsz, ty+tsz);
|
||||
gle::end();
|
||||
}
|
||||
|
||||
float abovegameplayhud(int w, int h)
|
||||
{
|
||||
switch(hudplayer()->state)
|
||||
{
|
||||
case CS_EDITING:
|
||||
case CS_SPECTATOR:
|
||||
return 1;
|
||||
default:
|
||||
return 1650.0f/1800.0f;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
void drawhudicons(gameent *d)
|
||||
|
@ -682,17 +472,9 @@ namespace game
|
|||
return 0;
|
||||
}
|
||||
|
||||
VARP(teamcrosshair, 0, 1, 1);
|
||||
VARP(hitcrosshair, 0, 425, 1000);
|
||||
|
||||
const char *defaultcrosshair(int index)
|
||||
{
|
||||
switch(index)
|
||||
{
|
||||
case 2: return "media/interface/crosshair/default_hit.png";
|
||||
case 1: return "media/interface/crosshair/teammate.png";
|
||||
default: return "media/interface/crosshair/default.png";
|
||||
}
|
||||
return "media/interface/crosshair/default.png";
|
||||
}
|
||||
|
||||
int selectcrosshair(vec &)
|
||||
|
|
257
src/game/game.hh
257
src/game/game.hh
|
@ -67,10 +67,6 @@ enum // static entity types
|
|||
MAPSOUND = ET_SOUND,
|
||||
SPOTLIGHT = ET_SPOTLIGHT,
|
||||
DECAL = ET_DECAL,
|
||||
TELEPORT, // attr1 = idx, attr2 = model, attr3 = tag
|
||||
TELEDEST, // attr1 = angle, attr2 = idx
|
||||
JUMPPAD, // attr1 = zpush, attr2 = ypush, attr3 = xpush
|
||||
FLAG, // attr1 = angle, attr2 = team
|
||||
MAXENTTYPES,
|
||||
|
||||
I_FIRST = 0,
|
||||
|
@ -81,67 +77,12 @@ struct gameentity : extentity
|
|||
{
|
||||
};
|
||||
|
||||
enum { GUN_RAIL = 0, GUN_PULSE, NUMGUNS };
|
||||
enum { ACT_IDLE = 0, ACT_SHOOT, ACT_MELEE, NUMACTS };
|
||||
enum { ATK_RAIL_SHOOT = 0, ATK_RAIL_MELEE, ATK_PULSE_SHOOT, ATK_PULSE_MELEE, NUMATKS };
|
||||
|
||||
#define validgun(n) ((n) >= 0 && (n) < NUMGUNS)
|
||||
#define validact(n) ((n) >= 0 && (n) < NUMACTS)
|
||||
#define validatk(n) ((n) >= 0 && (n) < NUMATKS)
|
||||
|
||||
enum
|
||||
{
|
||||
M_TEAM = 1<<0,
|
||||
M_OVERTIME = 1<<1,
|
||||
M_EDIT = 1<<2,
|
||||
M_DEMO = 1<<3,
|
||||
M_LOCAL = 1<<4,
|
||||
M_LOBBY = 1<<5,
|
||||
M_RAIL = 1<<6,
|
||||
M_PULSE = 1<<7
|
||||
M_EDIT = 1<<0
|
||||
};
|
||||
|
||||
static struct gamemodeinfo
|
||||
{
|
||||
const char *name, *prettyname;
|
||||
int flags;
|
||||
const char *info;
|
||||
} gamemodes[] =
|
||||
{
|
||||
{ "demo", "Demo", M_DEMO | M_LOCAL, NULL},
|
||||
{ "edit", "Edit", M_EDIT, "Cooperative Editing:\nEdit maps with multiple players simultaneously." },
|
||||
{ "rdm", "rDM", M_LOBBY | M_RAIL, "Railgun Deathmatch:\nFrag everyone with railguns to score points." },
|
||||
{ "pdm", "pDM", M_LOBBY | M_PULSE, "Pulse Rifle Deathmatch:\nFrag everyone with pulse rifles to score points." },
|
||||
{ "rtdm", "rTDM", M_TEAM | M_RAIL, "Railgun Team Deathmatch:\nFrag \fs\f3the enemy team\fr with railguns to score points for \fs\f1your team\fr." },
|
||||
{ "ptdm", "pTDM", M_TEAM | M_PULSE, "Pulse Rifle Team Deathmatch:\nFrag \fs\f3the enemy team\fr with pulse rifles to score points for \fs\f1your team\fr." },
|
||||
};
|
||||
|
||||
#define STARTGAMEMODE (-1)
|
||||
#define NUMGAMEMODES ((int)(sizeof(gamemodes)/sizeof(gamemodes[0])))
|
||||
|
||||
#define m_valid(mode) ((mode) >= STARTGAMEMODE && (mode) < STARTGAMEMODE + NUMGAMEMODES)
|
||||
#define m_check(mode, flag) (m_valid(mode) && gamemodes[(mode) - STARTGAMEMODE].flags&(flag))
|
||||
#define m_checknot(mode, flag) (m_valid(mode) && !(gamemodes[(mode) - STARTGAMEMODE].flags&(flag)))
|
||||
#define m_checkall(mode, flag) (m_valid(mode) && (gamemodes[(mode) - STARTGAMEMODE].flags&(flag)) == (flag))
|
||||
|
||||
#define m_teammode false
|
||||
#define m_overtime false
|
||||
#define isteam(a,b) false
|
||||
#define m_rail false
|
||||
#define m_pulse false
|
||||
|
||||
#define m_demo false
|
||||
#define m_edit true
|
||||
#define m_lobby false
|
||||
#define m_timed false
|
||||
#define m_botmode false
|
||||
#define m_mp(mode) false
|
||||
|
||||
enum { MM_AUTH = -1, MM_OPEN = 0, MM_VETO, MM_LOCKED, MM_PRIVATE, MM_PASSWORD, MM_START = MM_AUTH, MM_INVALID = MM_START - 1 };
|
||||
|
||||
static const char * const mastermodenames[] = { "auth", "open", "veto", "locked", "private", "password" };
|
||||
static const char * const mastermodecolors[] = { "", "\f0", "\f2", "\f2", "\f3", "\f3" };
|
||||
static const char * const mastermodeicons[] = { "server", "server", "serverlock", "serverlock", "serverpriv", "serverpriv" };
|
||||
|
||||
// hardcoded sounds, defined in sounds.cfg
|
||||
enum
|
||||
|
@ -156,22 +97,16 @@ enum
|
|||
|
||||
// network messages codes, c2s, c2c, s2c
|
||||
|
||||
enum { PRIV_NONE = 0, PRIV_MASTER, PRIV_AUTH, PRIV_ADMIN };
|
||||
|
||||
enum
|
||||
{
|
||||
N_CONNECT = 0, N_SERVINFO, N_WELCOME, N_INITCLIENT, N_POS, N_TEXT, N_SOUND, N_CDIS,
|
||||
N_SHOOT, N_EXPLODE, N_SUICIDE,
|
||||
N_DIED, N_DAMAGE, N_HITPUSH, N_SHOTFX, N_EXPLODEFX,
|
||||
N_TRYSPAWN, N_SPAWNSTATE, N_SPAWN, N_FORCEDEATH,
|
||||
N_GUNSELECT, N_TAUNT,
|
||||
N_MAPCHANGE, N_MAPVOTE, N_TEAMINFO, N_ITEMSPAWN, N_ITEMPICKUP, N_ITEMACC, N_TELEPORT, N_JUMPPAD,
|
||||
N_MAPCHANGE,
|
||||
N_PING, N_PONG, N_CLIENTPING,
|
||||
N_TIMEUP, N_FORCEINTERMISSION,
|
||||
N_SERVMSG, N_ITEMLIST, N_RESUME,
|
||||
N_EDITMODE, N_EDITENT, N_EDITF, N_EDITT, N_EDITM, N_FLIP, N_COPY, N_PASTE, N_ROTATE, N_REPLACE, N_DELCUBE, N_CALCLIGHT, N_REMIP, N_EDITVSLOT, N_UNDO, N_REDO, N_NEWMAP, N_GETMAP, N_SENDMAP, N_CLIPBOARD, N_EDITVAR,
|
||||
N_SPECTATOR, N_SETTEAM,
|
||||
N_SAYTEAM,
|
||||
N_CLIENT,
|
||||
N_PAUSEGAME, N_GAMESPEED,
|
||||
N_MAPCRC, N_CHECKMAPS,
|
||||
|
@ -183,17 +118,13 @@ enum
|
|||
static const int msgsizes[] = // size inclusive message token, 0 for variable or not-checked sizes
|
||||
{
|
||||
N_CONNECT, 0, N_SERVINFO, 0, N_WELCOME, 1, N_INITCLIENT, 0, N_POS, 0, N_TEXT, 0, N_SOUND, 2, N_CDIS, 2,
|
||||
N_SHOOT, 0, N_EXPLODE, 0, N_SUICIDE, 1,
|
||||
N_DIED, 5, N_DAMAGE, 5, N_HITPUSH, 7, N_SHOTFX, 10, N_EXPLODEFX, 4,
|
||||
N_TRYSPAWN, 1, N_SPAWNSTATE, 8, N_SPAWN, 3, N_FORCEDEATH, 2,
|
||||
N_GUNSELECT, 2, N_TAUNT, 1,
|
||||
N_MAPCHANGE, 0, N_MAPVOTE, 0, N_TEAMINFO, 0, N_ITEMSPAWN, 2, N_ITEMPICKUP, 2, N_ITEMACC, 3,
|
||||
N_MAPCHANGE, 0,
|
||||
N_PING, 2, N_PONG, 2, N_CLIENTPING, 2,
|
||||
N_TIMEUP, 2, N_FORCEINTERMISSION, 1,
|
||||
N_SERVMSG, 0, N_ITEMLIST, 0, N_RESUME, 0,
|
||||
N_EDITMODE, 2, N_EDITENT, 11, N_EDITF, 16, N_EDITT, 16, N_EDITM, 16, N_FLIP, 14, N_COPY, 14, N_PASTE, 14, N_ROTATE, 15, N_REPLACE, 17, N_DELCUBE, 14, N_CALCLIGHT, 1, N_REMIP, 1, N_EDITVSLOT, 16, N_UNDO, 0, N_REDO, 0, N_NEWMAP, 2, N_GETMAP, 1, N_SENDMAP, 0, N_EDITVAR, 0,
|
||||
N_SPECTATOR, 3, N_SETTEAM, 0,
|
||||
N_SAYTEAM, 0,
|
||||
N_CLIENT, 0,
|
||||
N_PAUSEGAME, 0, N_GAMESPEED, 0,
|
||||
N_MAPCRC, 0, N_CHECKMAPS, 1,
|
||||
|
@ -206,65 +137,15 @@ static const int msgsizes[] = // size inclusive message token, 0 f
|
|||
#define TESSERACT_LANINFO_PORT 41998
|
||||
#define TESSERACT_MASTER_PORT 41999
|
||||
#define PROTOCOL_VERSION 2 // bump when protocol changes
|
||||
#define DEMO_VERSION 1 // bump when demo format changes
|
||||
#define DEMO_MAGIC "TESSERACT_DEMO\0\0"
|
||||
|
||||
struct demoheader
|
||||
{
|
||||
char magic[16];
|
||||
int version, protocol;
|
||||
};
|
||||
|
||||
#define MAXNAMELEN 15
|
||||
|
||||
enum
|
||||
{
|
||||
HICON_RED_FLAG = 0,
|
||||
HICON_BLUE_FLAG,
|
||||
|
||||
HICON_X = 20,
|
||||
HICON_Y = 1650,
|
||||
HICON_TEXTY = 1644,
|
||||
HICON_STEP = 490,
|
||||
HICON_SIZE = 120,
|
||||
HICON_SPACE = 40
|
||||
};
|
||||
|
||||
#if 0
|
||||
static struct itemstat { int add, max, sound; const char *name; int icon, info; } itemstats[] =
|
||||
{
|
||||
};
|
||||
#endif
|
||||
|
||||
#define validitem(n) false
|
||||
|
||||
#define MAXRAYS 1
|
||||
#define EXP_SELFDAMDIV 2
|
||||
#define EXP_SELFPUSH 2.5f
|
||||
#define EXP_DISTSCALE 0.5f
|
||||
|
||||
static const struct attackinfo { int gun, action, anim, vwepanim, hudanim, sound, hudsound, attackdelay, damage, spread, margin, projspeed, kickamount, range, rays, hitpush, exprad, ttl, use; } attacks[NUMATKS] =
|
||||
{
|
||||
{ GUN_RAIL, ACT_SHOOT, ANIM_SHOOT, ANIM_VWEP_SHOOT, ANIM_GUN_SHOOT, S_RAIL1, S_RAIL2, 1300, 1, 0, 0, 0, 30, 2048, 1, 5000, 0, 0, 0 },
|
||||
{ GUN_RAIL, ACT_MELEE, ANIM_MELEE, ANIM_VWEP_MELEE, ANIM_GUN_MELEE, S_MELEE, S_MELEE, 500, 1, 0, 2, 0, 0, 14, 1, 0, 0, 0, 0 },
|
||||
{ GUN_PULSE, ACT_SHOOT, ANIM_SHOOT, ANIM_VWEP_SHOOT, ANIM_GUN_SHOOT, S_PULSE1, S_PULSE2, 700, 1, 0, 1, 1000, 30, 1024, 1, 5000, 15, 0, 0 },
|
||||
{ GUN_PULSE, ACT_MELEE, ANIM_MELEE, ANIM_VWEP_MELEE, ANIM_GUN_MELEE, S_MELEE, S_MELEE, 500, 1, 0, 2, 0, 0, 14, 1, 0, 0, 0, 0 }
|
||||
};
|
||||
|
||||
static const struct guninfo { const char *name, *file, *vwep; int attacks[NUMACTS]; } guns[NUMGUNS] =
|
||||
{
|
||||
{ "railgun", "railgun", "worldgun/railgun", { -1, ATK_RAIL_SHOOT, ATK_RAIL_MELEE }, },
|
||||
{ "pulse rifle", "pulserifle", "worldgun/pulserifle", { -1, ATK_PULSE_SHOOT, ATK_PULSE_MELEE } }
|
||||
};
|
||||
|
||||
// inherited by gameent and server clients
|
||||
struct gamestate
|
||||
{
|
||||
int health, maxhealth;
|
||||
int gunselect, gunwait;
|
||||
int ammo[NUMGUNS];
|
||||
|
||||
gamestate() : maxhealth(1) {}
|
||||
gamestate() {}
|
||||
|
||||
bool canpickup(int type)
|
||||
{
|
||||
|
@ -277,65 +158,20 @@ struct gamestate
|
|||
|
||||
void respawn()
|
||||
{
|
||||
health = maxhealth;
|
||||
gunselect = GUN_RAIL;
|
||||
gunwait = 0;
|
||||
loopi(NUMGUNS) ammo[i] = 0;
|
||||
}
|
||||
|
||||
void spawnstate(int gamemode)
|
||||
{
|
||||
if(m_rail)
|
||||
{
|
||||
gunselect = GUN_RAIL;
|
||||
ammo[GUN_RAIL] = 1;
|
||||
}
|
||||
else if(m_pulse)
|
||||
{
|
||||
gunselect = GUN_PULSE;
|
||||
ammo[GUN_PULSE] = 1;
|
||||
}
|
||||
else if(m_edit)
|
||||
{
|
||||
gunselect = GUN_RAIL;
|
||||
loopi(NUMGUNS) ammo[i] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// just subtract damage here, can set death, etc. later in code calling this
|
||||
int dodamage(int damage)
|
||||
{
|
||||
health -= damage;
|
||||
return damage;
|
||||
}
|
||||
|
||||
int hasammo(int gun, int exclude = -1)
|
||||
{
|
||||
return validgun(gun) && gun != exclude && ammo[gun] > 0;
|
||||
}
|
||||
};
|
||||
|
||||
#define MAXTEAMS 2
|
||||
static const char * const teamnames[1+MAXTEAMS] = { "", "azul", "rojo" };
|
||||
static const char * const teamtextcode[1+MAXTEAMS] = { "\f0", "\f1", "\f3" };
|
||||
static const int teamtextcolor[1+MAXTEAMS] = { 0x1EC850, 0x6496FF, 0xFF4B19 };
|
||||
static const char * const teamblipcolor[1+MAXTEAMS] = { "_neutral", "_blue", "_red" };
|
||||
static inline int teamnumber(const char *name) { loopi(MAXTEAMS) if(!strcmp(teamnames[1+i], name)) return 1+i; return 0; }
|
||||
#define validteam(n) ((n) >= 1 && (n) <= MAXTEAMS)
|
||||
#define teamname(n) (teamnames[validteam(n) ? (n) : 0])
|
||||
|
||||
struct gameent : dynent, gamestate
|
||||
{
|
||||
int weight; // affects the effectiveness of hitpush
|
||||
int clientnum, lastupdate, plag, ping;
|
||||
int lifesequence; // sequence id for each respawn, used in damage test
|
||||
int respawned, suicided;
|
||||
int lastpain;
|
||||
int lastaction, lastattack;
|
||||
int attacking;
|
||||
int lasttaunt;
|
||||
int lastpickup, lastpickupmillis, flagpickup;
|
||||
int frags, flags, deaths, totaldamage, totalshots;
|
||||
int respawned;
|
||||
int lastaction;
|
||||
editinfo *edit;
|
||||
float deltayaw, deltapitch, deltaroll, newyaw, newpitch, newroll;
|
||||
int smoothmillis;
|
||||
|
@ -346,7 +182,7 @@ struct gameent : dynent, gamestate
|
|||
|
||||
vec muzzle;
|
||||
|
||||
gameent() : weight(100), clientnum(-1), lastupdate(0), plag(0), ping(0), lifesequence(0), respawned(-1), suicided(-1), lastpain(0), frags(0), flags(0), deaths(0), totaldamage(0), totalshots(0), edit(NULL), smoothmillis(-1), team(0), playermodel(-1), playercolor(0), ownernum(-1), muzzle(-1, -1, -1)
|
||||
gameent() : weight(100), clientnum(-1), lastupdate(0), plag(0), ping(0), lifesequence(0), respawned(-1), edit(NULL), smoothmillis(-1), team(0), playermodel(-1), playercolor(0), ownernum(-1), muzzle(-1, -1, -1)
|
||||
{
|
||||
name[0] = info[0] = 0;
|
||||
respawn();
|
||||
|
@ -356,40 +192,24 @@ struct gameent : dynent, gamestate
|
|||
freeeditinfo(edit);
|
||||
}
|
||||
|
||||
void hitpush(int damage, const vec &dir, gameent *actor, int atk)
|
||||
{
|
||||
vec push(dir);
|
||||
push.mul((actor==this && attacks[atk].exprad ? EXP_SELFPUSH : 1.0f)*attacks[atk].hitpush*damage/weight);
|
||||
vel.add(push);
|
||||
}
|
||||
|
||||
void respawn()
|
||||
{
|
||||
dynent::reset();
|
||||
gamestate::respawn();
|
||||
respawned = suicided = -1;
|
||||
respawned = -1;
|
||||
lastaction = 0;
|
||||
lastattack = -1;
|
||||
attacking = ACT_IDLE;
|
||||
lasttaunt = 0;
|
||||
lastpickup = -1;
|
||||
lastpickupmillis = 0;
|
||||
flagpickup = 0;
|
||||
lastnode = -1;
|
||||
}
|
||||
|
||||
int respawnwait(int secs, int delay = 0)
|
||||
{
|
||||
return max(0, secs - (::lastmillis - lastpain - delay)/1000);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void startgame()
|
||||
{
|
||||
frags = flags = deaths = 0;
|
||||
totaldamage = totalshots = 0;
|
||||
maxhealth = 1;
|
||||
lifesequence = -1;
|
||||
respawned = suicided = -2;
|
||||
respawned = -2;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -417,10 +237,6 @@ namespace entities
|
|||
extern void spawnitems(bool force = false);
|
||||
extern void putitems(packetbuf &p);
|
||||
extern void setspawn(int i, bool on);
|
||||
extern void teleport(int n, gameent *d);
|
||||
extern void pickupeffects(int n, gameent *d);
|
||||
extern void teleporteffects(gameent *d, int tp, int td, bool local = true);
|
||||
extern void jumppadeffects(gameent *d, int jp, bool local = true);
|
||||
}
|
||||
|
||||
namespace game
|
||||
|
@ -437,7 +253,7 @@ namespace game
|
|||
virtual void setup() {}
|
||||
virtual void checkitems(gameent *d) {}
|
||||
virtual int respawnwait(gameent *d, int delay = 0) { return 0; }
|
||||
virtual void pickspawn(gameent *d) { findplayerspawn(d, -1, m_teammode ? d->team : 0); }
|
||||
virtual void pickspawn(gameent *d) { findplayerspawn(d, -1, 0); }
|
||||
virtual void senditems(packetbuf &p) {}
|
||||
virtual void removeplayer(gameent *d) {}
|
||||
virtual void gameover() {}
|
||||
|
@ -449,7 +265,6 @@ namespace game
|
|||
|
||||
// game
|
||||
extern string clientmap;
|
||||
extern bool intermission;
|
||||
extern int maptime, maprealtime, maplimit;
|
||||
extern gameent *player1;
|
||||
extern vector<gameent *> players, clients;
|
||||
|
@ -462,10 +277,6 @@ namespace game
|
|||
extern gameent *getclient(int cn);
|
||||
extern gameent *newclient(int cn);
|
||||
extern const char *colorname(gameent *d, const char *name = NULL, const char *alt = NULL, const char *color = "");
|
||||
extern const char *teamcolorname(gameent *d, const char *alt = "you");
|
||||
extern const char *teamcolor(const char *prefix, const char *suffix, int team, const char *alt);
|
||||
extern void teamsound(bool sameteam, int n, const vec *loc = NULL);
|
||||
extern void teamsound(gameent *d, int n, const vec *loc = NULL);
|
||||
extern gameent *pointatplayer();
|
||||
extern gameent *hudplayer();
|
||||
extern gameent *followingplayer(gameent *fallback = NULL);
|
||||
|
@ -477,13 +288,8 @@ namespace game
|
|||
extern void startgame();
|
||||
extern void spawnplayer(gameent *);
|
||||
extern void deathstate(gameent *d, bool restore = false);
|
||||
extern void damaged(int damage, gameent *d, gameent *actor, bool local = true);
|
||||
extern void killed(gameent *d, gameent *actor);
|
||||
extern void timeupdate(int timeremain);
|
||||
extern void msgsound(int n, physent *d = NULL);
|
||||
extern void drawicon(int icon, float x, float y, float sz = 120);
|
||||
const char *mastermodecolor(int n, const char *unknown);
|
||||
const char *mastermodeicon(int n, const char *unknown);
|
||||
|
||||
// client
|
||||
extern bool connected, remote, demoplayback;
|
||||
|
@ -492,45 +298,16 @@ namespace game
|
|||
|
||||
extern int parseplayer(const char *arg);
|
||||
extern bool addmsg(int type, const char *fmt = NULL, ...);
|
||||
extern void switchname(const char *name);
|
||||
extern void switchteam(const char *name);
|
||||
extern void switchplayermodel(int playermodel);
|
||||
extern void switchplayercolor(int playercolor);
|
||||
extern void sendmapinfo();
|
||||
extern void changemap(const char *name, int mode);
|
||||
extern void c2sinfo(bool force = false);
|
||||
extern void sendposition(gameent *d, bool reliable = false);
|
||||
|
||||
// weapon
|
||||
extern int getweapon(const char *name);
|
||||
extern void shoot(gameent *d, const vec &targ);
|
||||
extern void shoteffects(int atk, const vec &from, const vec &to, gameent *d, bool local, int id, int prevaction);
|
||||
extern void explode(bool local, gameent *owner, const vec &v, const vec &vel, dynent *safe, int dam, int atk);
|
||||
extern void explodeeffects(int atk, gameent *d, bool local, int id = 0);
|
||||
extern void damageeffect(int damage, gameent *d, bool thirdperson = true);
|
||||
extern void gibeffect(int damage, const vec &vel, gameent *d);
|
||||
extern float intersectdist;
|
||||
extern bool intersect(dynent *d, const vec &from, const vec &to, float margin = 0, float &dist = intersectdist);
|
||||
extern dynent *intersectclosest(const vec &from, const vec &to, gameent *at, float margin = 0, float &dist = intersectdist);
|
||||
extern void clearbouncers();
|
||||
extern void updatebouncers(int curtime);
|
||||
extern void removebouncers(gameent *owner);
|
||||
extern void renderbouncers();
|
||||
extern void clearprojectiles();
|
||||
extern void updateprojectiles(int curtime);
|
||||
extern void removeprojectiles(gameent *owner);
|
||||
extern void renderprojectiles();
|
||||
extern void preloadbouncers();
|
||||
extern void removeweapons(gameent *owner);
|
||||
extern void updateweapons(int curtime);
|
||||
extern void gunselect(int gun, gameent *d);
|
||||
extern void weaponswitch(gameent *d);
|
||||
|
||||
// render
|
||||
struct playermodelinfo
|
||||
{
|
||||
const char *model[1+MAXTEAMS], *hudguns[1+MAXTEAMS],
|
||||
*icon[1+MAXTEAMS];
|
||||
const char *model[1], *hudguns[1],
|
||||
*icon[1];
|
||||
bool ragdoll;
|
||||
};
|
||||
|
||||
|
@ -541,21 +318,13 @@ namespace game
|
|||
extern int getplayercolor(gameent *d, int team);
|
||||
extern int chooserandomplayermodel(int seed);
|
||||
extern void syncplayer();
|
||||
extern void swayhudgun(int curtime);
|
||||
extern vec hudgunorigin(int gun, const vec &from, const vec &to, gameent *d);
|
||||
}
|
||||
|
||||
namespace server
|
||||
{
|
||||
extern const char *modename(int n, const char *unknown = "unknown");
|
||||
extern const char *modeprettyname(int n, const char *unknown = "unknown");
|
||||
extern const char *mastermodename(int n, const char *unknown = "unknown");
|
||||
extern void startintermission();
|
||||
extern void stopdemo();
|
||||
extern void forcemap(const char *map, int mode);
|
||||
extern void forcepaused(bool paused);
|
||||
extern void forcegamespeed(int speed);
|
||||
extern void hashpassword(int cn, int sessionid, const char *pwd, char *result, int maxlen = MAXSTRLEN);
|
||||
extern int msgsizelookup(int msg);
|
||||
extern bool serveroption(const char *arg);
|
||||
extern bool delayspawn(int type);
|
||||
|
|
|
@ -2,528 +2,60 @@
|
|||
|
||||
namespace game
|
||||
{
|
||||
vector<gameent *> bestplayers;
|
||||
vector<int> bestteams;
|
||||
|
||||
VARP(ragdoll, 0, 1, 1);
|
||||
VARP(ragdollmillis, 0, 10000, 300000);
|
||||
VARP(ragdollfade, 0, 100, 5000);
|
||||
VARP(forceplayermodels, 0, 0, 1);
|
||||
VARP(hidedead, 0, 0, 1);
|
||||
|
||||
extern int playermodel;
|
||||
|
||||
vector<gameent *> ragdolls;
|
||||
|
||||
void saveragdoll(gameent *d)
|
||||
{
|
||||
if(!d->ragdoll || !ragdollmillis || (!ragdollfade && lastmillis > d->lastpain + ragdollmillis)) return;
|
||||
gameent *r = new gameent(*d);
|
||||
r->lastupdate = ragdollfade && lastmillis > d->lastpain + max(ragdollmillis - ragdollfade, 0) ? lastmillis - max(ragdollmillis - ragdollfade, 0) : d->lastpain;
|
||||
r->edit = NULL;
|
||||
if(d==player1) r->playermodel = playermodel;
|
||||
ragdolls.add(r);
|
||||
d->ragdoll = NULL;
|
||||
}
|
||||
|
||||
void clearragdolls()
|
||||
{
|
||||
ragdolls.deletecontents();
|
||||
}
|
||||
|
||||
void moveragdolls()
|
||||
{
|
||||
loopv(ragdolls)
|
||||
{
|
||||
gameent *d = ragdolls[i];
|
||||
if(lastmillis > d->lastupdate + ragdollmillis)
|
||||
{
|
||||
delete ragdolls.remove(i--);
|
||||
continue;
|
||||
}
|
||||
moveragdoll(d);
|
||||
}
|
||||
}
|
||||
|
||||
static const int playercolors[] =
|
||||
{
|
||||
0xA12020,
|
||||
0xA15B28,
|
||||
0xB39D52,
|
||||
0x3E752F,
|
||||
0x3F748C,
|
||||
0x214C85,
|
||||
0xB3668C,
|
||||
0x523678,
|
||||
0xB3ADA3
|
||||
};
|
||||
|
||||
static const int playercolorsazul[] =
|
||||
{
|
||||
0x27508A,
|
||||
0x3F748C,
|
||||
0x3B3B80,
|
||||
0x5364B5
|
||||
};
|
||||
|
||||
static const int playercolorsrojo[] =
|
||||
{
|
||||
0xAC2C2A,
|
||||
0x992417,
|
||||
0x802438,
|
||||
0xA3435B
|
||||
};
|
||||
|
||||
extern void changedplayercolor();
|
||||
VARFP(playercolor, 0, 4, sizeof(playercolors)/sizeof(playercolors[0])-1, changedplayercolor());
|
||||
VARFP(playercolorazul, 0, 0, sizeof(playercolorsazul)/sizeof(playercolorsazul[0])-1, changedplayercolor());
|
||||
VARFP(playercolorrojo, 0, 0, sizeof(playercolorsrojo)/sizeof(playercolorsrojo[0])-1, changedplayercolor());
|
||||
|
||||
static const playermodelinfo playermodels[] =
|
||||
{
|
||||
{ { "player/bones", "player/bones", "player/bones" }, { "hudgun", "hudgun", "hudgun" }, { "player", "player_azul", "player_rojo" }, true }
|
||||
};
|
||||
|
||||
extern void changedplayermodel();
|
||||
VARFP(playermodel, 0, 0, sizeof(playermodels)/sizeof(playermodels[0])-1, changedplayermodel());
|
||||
|
||||
int chooserandomplayermodel(int seed)
|
||||
{
|
||||
return (seed&0xFFFF)%(sizeof(playermodels)/sizeof(playermodels[0]));
|
||||
}
|
||||
|
||||
const playermodelinfo *getplayermodelinfo(int n)
|
||||
{
|
||||
if(size_t(n) >= sizeof(playermodels)/sizeof(playermodels[0])) return NULL;
|
||||
return &playermodels[n];
|
||||
}
|
||||
|
||||
const playermodelinfo &getplayermodelinfo(gameent *d)
|
||||
{
|
||||
const playermodelinfo *mdl = getplayermodelinfo(d==player1 || forceplayermodels ? playermodel : d->playermodel);
|
||||
if(!mdl) mdl = getplayermodelinfo(playermodel);
|
||||
return *mdl;
|
||||
}
|
||||
|
||||
int getplayercolor(int team, int color)
|
||||
{
|
||||
#define GETPLAYERCOLOR(playercolors) \
|
||||
return playercolors[color%(sizeof(playercolors)/sizeof(playercolors[0]))];
|
||||
switch(team)
|
||||
{
|
||||
case 1: GETPLAYERCOLOR(playercolorsazul)
|
||||
case 2: GETPLAYERCOLOR(playercolorsrojo)
|
||||
default: GETPLAYERCOLOR(playercolors)
|
||||
}
|
||||
}
|
||||
|
||||
ICOMMAND(getplayercolor, "ii", (int *color, int *team), intret(getplayercolor(*team, *color)));
|
||||
|
||||
int getplayercolor(gameent *d, int team)
|
||||
{
|
||||
if(d==player1) switch(team)
|
||||
{
|
||||
case 1: return getplayercolor(1, playercolorazul);
|
||||
case 2: return getplayercolor(2, playercolorrojo);
|
||||
default: return getplayercolor(0, playercolor);
|
||||
}
|
||||
else return getplayercolor(team, (d->playercolor>>(5*team))&0x1F);
|
||||
}
|
||||
|
||||
void changedplayermodel()
|
||||
{
|
||||
if(player1->clientnum < 0) player1->playermodel = playermodel;
|
||||
if(player1->ragdoll) cleanragdoll(player1);
|
||||
loopv(ragdolls)
|
||||
{
|
||||
gameent *d = ragdolls[i];
|
||||
if(!d->ragdoll) continue;
|
||||
if(!forceplayermodels)
|
||||
{
|
||||
const playermodelinfo *mdl = getplayermodelinfo(d->playermodel);
|
||||
if(mdl) continue;
|
||||
}
|
||||
cleanragdoll(d);
|
||||
}
|
||||
loopv(players)
|
||||
{
|
||||
gameent *d = players[i];
|
||||
if(d == player1 || !d->ragdoll) continue;
|
||||
if(!forceplayermodels)
|
||||
{
|
||||
const playermodelinfo *mdl = getplayermodelinfo(d->playermodel);
|
||||
if(mdl) continue;
|
||||
}
|
||||
cleanragdoll(d);
|
||||
}
|
||||
}
|
||||
|
||||
void changedplayercolor()
|
||||
{
|
||||
if(player1->clientnum < 0) player1->playercolor = playercolor | (playercolorazul<<5) | (playercolorrojo<<10);
|
||||
}
|
||||
|
||||
void syncplayer()
|
||||
{
|
||||
if(player1->playermodel != playermodel)
|
||||
{
|
||||
player1->playermodel = playermodel;
|
||||
addmsg(N_SWITCHMODEL, "ri", player1->playermodel);
|
||||
}
|
||||
|
||||
int col = playercolor | (playercolorazul<<5) | (playercolorrojo<<10);
|
||||
if(player1->playercolor != col)
|
||||
{
|
||||
player1->playercolor = col;
|
||||
addmsg(N_SWITCHCOLOR, "ri", player1->playercolor);
|
||||
}
|
||||
}
|
||||
|
||||
void preloadplayermodel()
|
||||
{
|
||||
loopi(sizeof(playermodels)/sizeof(playermodels[0]))
|
||||
{
|
||||
const playermodelinfo *mdl = getplayermodelinfo(i);
|
||||
if(!mdl) break;
|
||||
if(i != playermodel && (!multiplayer(false) || forceplayermodels)) continue;
|
||||
if(m_teammode)
|
||||
{
|
||||
loopj(MAXTEAMS) preloadmodel(mdl->model[1+j]);
|
||||
}
|
||||
else preloadmodel(mdl->model[0]);
|
||||
}
|
||||
}
|
||||
|
||||
int numanims() { return NUMANIMS; }
|
||||
|
||||
void findanims(const char *pattern, vector<int> &anims)
|
||||
void renderplayer(gameent *d, const playermodelinfo &mdl, int color, float fade, int flags = 0, bool mainpass = true)
|
||||
{
|
||||
loopi(sizeof(animnames)/sizeof(animnames[0])) if(matchanim(animnames[i], pattern)) anims.add(i);
|
||||
}
|
||||
|
||||
VAR(animoverride, -1, 0, NUMANIMS-1);
|
||||
VAR(testanims, 0, 0, 1);
|
||||
VAR(testpitch, -90, 0, 90);
|
||||
|
||||
void renderplayer(gameent *d, const playermodelinfo &mdl, int color, int team, float fade, int flags = 0, bool mainpass = true)
|
||||
{
|
||||
int lastaction = d->lastaction, anim = ANIM_IDLE|ANIM_LOOP, attack = 0, delay = 0;
|
||||
if(d->lastattack >= 0)
|
||||
{
|
||||
attack = attacks[d->lastattack].anim;
|
||||
delay = attacks[d->lastattack].attackdelay+50;
|
||||
}
|
||||
if(intermission && d->state!=CS_DEAD)
|
||||
{
|
||||
anim = attack = ANIM_LOSE|ANIM_LOOP;
|
||||
if(validteam(team) ? bestteams.htfind(team)>=0 : bestplayers.find(d)>=0) anim = attack = ANIM_WIN|ANIM_LOOP;
|
||||
}
|
||||
else if(d->state==CS_ALIVE && d->lasttaunt && lastmillis-d->lasttaunt<1000 && lastmillis-d->lastaction>delay)
|
||||
{
|
||||
lastaction = d->lasttaunt;
|
||||
anim = attack = ANIM_TAUNT;
|
||||
delay = 1000;
|
||||
}
|
||||
modelattach a[5];
|
||||
int ai = 0;
|
||||
if(guns[d->gunselect].vwep)
|
||||
{
|
||||
int vanim = ANIM_VWEP_IDLE|ANIM_LOOP, vtime = 0;
|
||||
if(lastaction && d->lastattack >= 0 && attacks[d->lastattack].gun==d->gunselect && lastmillis < lastaction + delay)
|
||||
{
|
||||
vanim = attacks[d->lastattack].vwepanim;
|
||||
vtime = lastaction;
|
||||
}
|
||||
a[ai++] = modelattach("tag_weapon", guns[d->gunselect].vwep, vanim, vtime);
|
||||
}
|
||||
if(mainpass && !(flags&MDL_ONLYSHADOW))
|
||||
{
|
||||
d->muzzle = vec(-1, -1, -1);
|
||||
if(guns[d->gunselect].vwep) a[ai++] = modelattach("tag_muzzle", &d->muzzle);
|
||||
}
|
||||
const char *mdlname = mdl.model[validteam(team) ? team : 0];
|
||||
float yaw = testanims && d==player1 ? 0 : d->yaw,
|
||||
pitch = testpitch && d==player1 ? testpitch : d->pitch;
|
||||
vec o = d->feetpos();
|
||||
int basetime = 0;
|
||||
if(animoverride) anim = (animoverride<0 ? ANIM_ALL : animoverride)|ANIM_LOOP;
|
||||
else if(d->state==CS_DEAD)
|
||||
{
|
||||
anim = ANIM_DYING|ANIM_NOPITCH;
|
||||
basetime = d->lastpain;
|
||||
if(ragdoll && mdl.ragdoll) anim |= ANIM_RAGDOLL;
|
||||
else if(lastmillis-basetime>1000) anim = ANIM_DEAD|ANIM_LOOP|ANIM_NOPITCH;
|
||||
}
|
||||
else if(d->state==CS_EDITING || d->state==CS_SPECTATOR) anim = ANIM_EDIT|ANIM_LOOP;
|
||||
else if(d->state==CS_LAGGED) anim = ANIM_LAG|ANIM_LOOP;
|
||||
else if(!intermission)
|
||||
{
|
||||
if(lastmillis-d->lastpain < 300)
|
||||
{
|
||||
anim = ANIM_PAIN;
|
||||
basetime = d->lastpain;
|
||||
}
|
||||
else if(d->lastpain < lastaction && lastmillis-lastaction < delay)
|
||||
{
|
||||
anim = attack;
|
||||
basetime = lastaction;
|
||||
}
|
||||
|
||||
if(d->inwater && d->physstate<=PHYS_FALL) anim |= (((game::allowmove(d) && (d->move || d->strafe)) || d->vel.z+d->falling.z>0 ? ANIM_SWIM : ANIM_SINK)|ANIM_LOOP)<<ANIM_SECONDARY;
|
||||
else
|
||||
{
|
||||
static const int dirs[9] =
|
||||
{
|
||||
ANIM_RUN_SE, ANIM_RUN_S, ANIM_RUN_SW,
|
||||
ANIM_RUN_E, 0, ANIM_RUN_W,
|
||||
ANIM_RUN_NE, ANIM_RUN_N, ANIM_RUN_NW
|
||||
};
|
||||
int dir = dirs[(d->move+1)*3 + (d->strafe+1)];
|
||||
if(d->timeinair>100) anim |= ((dir ? dir+ANIM_JUMP_N-ANIM_RUN_N : ANIM_JUMP) | ANIM_END) << ANIM_SECONDARY;
|
||||
else if(dir && game::allowmove(d)) anim |= (dir | ANIM_LOOP) << ANIM_SECONDARY;
|
||||
}
|
||||
|
||||
if(d->crouching) switch((anim>>ANIM_SECONDARY)&ANIM_INDEX)
|
||||
{
|
||||
case ANIM_IDLE: anim &= ~(ANIM_INDEX<<ANIM_SECONDARY); anim |= ANIM_CROUCH<<ANIM_SECONDARY; break;
|
||||
case ANIM_JUMP: anim &= ~(ANIM_INDEX<<ANIM_SECONDARY); anim |= ANIM_CROUCH_JUMP<<ANIM_SECONDARY; break;
|
||||
case ANIM_SWIM: anim &= ~(ANIM_INDEX<<ANIM_SECONDARY); anim |= ANIM_CROUCH_SWIM<<ANIM_SECONDARY; break;
|
||||
case ANIM_SINK: anim &= ~(ANIM_INDEX<<ANIM_SECONDARY); anim |= ANIM_CROUCH_SINK<<ANIM_SECONDARY; break;
|
||||
case 0: anim |= (ANIM_CROUCH|ANIM_LOOP)<<ANIM_SECONDARY; break;
|
||||
case ANIM_RUN_N: case ANIM_RUN_NE: case ANIM_RUN_E: case ANIM_RUN_SE: case ANIM_RUN_S: case ANIM_RUN_SW: case ANIM_RUN_W: case ANIM_RUN_NW:
|
||||
anim += (ANIM_CROUCH_N - ANIM_RUN_N) << ANIM_SECONDARY;
|
||||
break;
|
||||
case ANIM_JUMP_N: case ANIM_JUMP_NE: case ANIM_JUMP_E: case ANIM_JUMP_SE: case ANIM_JUMP_S: case ANIM_JUMP_SW: case ANIM_JUMP_W: case ANIM_JUMP_NW:
|
||||
anim += (ANIM_CROUCH_JUMP_N - ANIM_JUMP_N) << ANIM_SECONDARY;
|
||||
break;
|
||||
}
|
||||
|
||||
if((anim&ANIM_INDEX)==ANIM_IDLE && (anim>>ANIM_SECONDARY)&ANIM_INDEX) anim >>= ANIM_SECONDARY;
|
||||
}
|
||||
if(!((anim>>ANIM_SECONDARY)&ANIM_INDEX)) anim |= (ANIM_IDLE|ANIM_LOOP)<<ANIM_SECONDARY;
|
||||
if(d!=player1) flags |= MDL_CULL_VFC | MDL_CULL_OCCLUDED | MDL_CULL_QUERY;
|
||||
if(d->type==ENT_PLAYER) flags |= MDL_FULLBRIGHT;
|
||||
else flags |= MDL_CULL_DIST;
|
||||
if(!mainpass) flags &= ~(MDL_FULLBRIGHT | MDL_CULL_VFC | MDL_CULL_OCCLUDED | MDL_CULL_QUERY | MDL_CULL_DIST);
|
||||
float trans = d->state == CS_LAGGED ? 0.5f : 1.0f;
|
||||
rendermodel(mdlname, anim, o, yaw, pitch, 0, flags, d, a[0].tag ? a : NULL, basetime, 0, fade, vec4(vec::hexcolor(color), trans));
|
||||
}
|
||||
|
||||
static inline void renderplayer(gameent *d, float fade = 1, int flags = 0)
|
||||
{
|
||||
int team = m_teammode && validteam(d->team) ? d->team : 0;
|
||||
renderplayer(d, getplayermodelinfo(d), getplayercolor(d, team), team, fade, flags);
|
||||
}
|
||||
|
||||
void rendergame()
|
||||
{
|
||||
if(intermission)
|
||||
{
|
||||
bestteams.shrink(0);
|
||||
bestplayers.shrink(0);
|
||||
}
|
||||
|
||||
bool third = isthirdperson();
|
||||
gameent *f = followingplayer(), *exclude = third ? NULL : f;
|
||||
loopv(players)
|
||||
{
|
||||
gameent *d = players[i];
|
||||
if(d == player1 || d->state==CS_SPECTATOR || d->state==CS_SPAWNING || d->lifesequence < 0 || d == exclude || (d->state==CS_DEAD && hidedead)) continue;
|
||||
renderplayer(d);
|
||||
copystring(d->info, colorname(d));
|
||||
if(d->state!=CS_DEAD)
|
||||
{
|
||||
int team = m_teammode && validteam(d->team) ? d->team : 0;
|
||||
particle_text(d->abovehead(), d->info, PART_TEXT, 1, teamtextcolor[team], 2.0f);
|
||||
}
|
||||
}
|
||||
loopv(ragdolls)
|
||||
{
|
||||
gameent *d = ragdolls[i];
|
||||
float fade = 1.0f;
|
||||
if(ragdollmillis && ragdollfade)
|
||||
fade -= clamp(float(lastmillis - (d->lastupdate + max(ragdollmillis - ragdollfade, 0)))/min(ragdollmillis, ragdollfade), 0.0f, 1.0f);
|
||||
renderplayer(d, fade);
|
||||
}
|
||||
if(exclude)
|
||||
renderplayer(exclude, 1, MDL_ONLYSHADOW);
|
||||
else if(!f && (player1->state==CS_ALIVE || (player1->state==CS_EDITING && third) || (player1->state==CS_DEAD && !hidedead)))
|
||||
renderplayer(player1, 1, third ? 0 : MDL_ONLYSHADOW);
|
||||
entities::renderentities();
|
||||
renderbouncers();
|
||||
renderprojectiles();
|
||||
if(cmode) cmode->rendergame();
|
||||
}
|
||||
|
||||
VARP(hudgun, 0, 1, 1);
|
||||
VARP(hudgunsway, 0, 1, 1);
|
||||
|
||||
FVAR(swaystep, 1, 35.0f, 100);
|
||||
FVAR(swayside, 0, 0.10f, 1);
|
||||
FVAR(swayup, -1, 0.15f, 1);
|
||||
|
||||
float swayfade = 0, swayspeed = 0, swaydist = 0;
|
||||
vec swaydir(0, 0, 0);
|
||||
|
||||
void swayhudgun(int curtime)
|
||||
{
|
||||
gameent *d = hudplayer();
|
||||
if(d->state != CS_SPECTATOR)
|
||||
{
|
||||
if(d->physstate >= PHYS_SLOPE)
|
||||
{
|
||||
swayspeed = min(sqrtf(d->vel.x*d->vel.x + d->vel.y*d->vel.y), d->maxspeed);
|
||||
swaydist += swayspeed*curtime/1000.0f;
|
||||
swaydist = fmod(swaydist, 2*swaystep);
|
||||
swayfade = 1;
|
||||
}
|
||||
else if(swayfade > 0)
|
||||
{
|
||||
swaydist += swayspeed*swayfade*curtime/1000.0f;
|
||||
swaydist = fmod(swaydist, 2*swaystep);
|
||||
swayfade -= 0.5f*(curtime*d->maxspeed)/(swaystep*1000.0f);
|
||||
}
|
||||
|
||||
float k = pow(0.7f, curtime/10.0f);
|
||||
swaydir.mul(k);
|
||||
vec vel(d->vel);
|
||||
vel.add(d->falling);
|
||||
swaydir.add(vec(vel).mul((1-k)/(15*max(vel.magnitude(), d->maxspeed))));
|
||||
}
|
||||
}
|
||||
|
||||
struct hudent : dynent
|
||||
{
|
||||
hudent() { type = ENT_CAMERA; }
|
||||
} guninterp;
|
||||
|
||||
void drawhudmodel(gameent *d, int anim, int basetime)
|
||||
{
|
||||
const char *file = guns[d->gunselect].file;
|
||||
if(!file) return;
|
||||
|
||||
vec sway;
|
||||
vecfromyawpitch(d->yaw, 0, 0, 1, sway);
|
||||
float steps = swaydist/swaystep*M_PI;
|
||||
sway.mul(swayside*cosf(steps));
|
||||
sway.z = swayup*(fabs(sinf(steps)) - 1);
|
||||
sway.add(swaydir).add(d->o);
|
||||
if(!hudgunsway) sway = d->o;
|
||||
|
||||
const playermodelinfo &mdl = getplayermodelinfo(d);
|
||||
int team = m_teammode && validteam(d->team) ? d->team : 0,
|
||||
color = getplayercolor(d, team);
|
||||
defformatstring(gunname, "%s/%s", mdl.hudguns[team], file);
|
||||
modelattach a[2];
|
||||
d->muzzle = vec(-1, -1, -1);
|
||||
a[0] = modelattach("tag_muzzle", &d->muzzle);
|
||||
rendermodel(gunname, anim, sway, d->yaw, d->pitch, 0, MDL_NOBATCH, NULL, a, basetime, 0, 1, vec4(vec::hexcolor(color), 1));
|
||||
if(d->muzzle.x >= 0) d->muzzle = calcavatarpos(d->muzzle, 12);
|
||||
}
|
||||
|
||||
void drawhudgun()
|
||||
{
|
||||
gameent *d = hudplayer();
|
||||
if(d->state==CS_SPECTATOR || d->state==CS_EDITING || !hudgun || editmode)
|
||||
{
|
||||
d->muzzle = player1->muzzle = vec(-1, -1, -1);
|
||||
return;
|
||||
}
|
||||
|
||||
int anim = ANIM_GUN_IDLE|ANIM_LOOP, basetime = 0;
|
||||
if(d->lastaction && d->lastattack >= 0 && attacks[d->lastattack].gun==d->gunselect && lastmillis-d->lastaction<attacks[d->lastattack].attackdelay)
|
||||
{
|
||||
anim = attacks[d->lastattack].hudanim;
|
||||
basetime = d->lastaction;
|
||||
}
|
||||
drawhudmodel(d, anim, basetime);
|
||||
}
|
||||
|
||||
void renderavatar()
|
||||
{
|
||||
drawhudgun();
|
||||
}
|
||||
|
||||
void renderplayerpreview(int model, int color, int team, int weap)
|
||||
void renderplayerpreview(int model, int color, int weap)
|
||||
{
|
||||
static gameent *previewent = NULL;
|
||||
if(!previewent)
|
||||
{
|
||||
previewent = new gameent;
|
||||
loopi(NUMGUNS) previewent->ammo[i] = 1;
|
||||
}
|
||||
float height = previewent->eyeheight + previewent->aboveeye,
|
||||
zrad = height/2;
|
||||
vec2 xyrad = vec2(previewent->xradius, previewent->yradius).max(height/4);
|
||||
previewent->o = calcmodelpreviewpos(vec(xyrad, zrad), previewent->yaw).addz(previewent->eyeheight - zrad);
|
||||
previewent->gunselect = validgun(weap) ? weap : GUN_RAIL;
|
||||
const playermodelinfo *mdlinfo = getplayermodelinfo(model);
|
||||
if(!mdlinfo) return;
|
||||
renderplayer(previewent, *mdlinfo, getplayercolor(team, color), team, 1, 0, false);
|
||||
}
|
||||
|
||||
vec hudgunorigin(int gun, const vec &from, const vec &to, gameent *d)
|
||||
{
|
||||
if(d->muzzle.x >= 0) return d->muzzle;
|
||||
vec offset(from);
|
||||
if(d!=hudplayer() || isthirdperson())
|
||||
{
|
||||
vec front, right;
|
||||
vecfromyawpitch(d->yaw, d->pitch, 1, 0, front);
|
||||
offset.add(front.mul(d->radius));
|
||||
offset.z += (d->aboveeye + d->eyeheight)*0.75f - d->eyeheight;
|
||||
vecfromyawpitch(d->yaw, 0, 0, -1, right);
|
||||
offset.add(right.mul(0.5f*d->radius));
|
||||
offset.add(front);
|
||||
return offset;
|
||||
}
|
||||
offset.add(vec(to).sub(from).normalize().mul(2));
|
||||
if(hudgun)
|
||||
{
|
||||
offset.sub(vec(camup).mul(1.0f));
|
||||
offset.add(vec(camright).mul(0.8f));
|
||||
}
|
||||
else offset.sub(vec(camup).mul(0.8f));
|
||||
return offset;
|
||||
}
|
||||
|
||||
void preloadweapons()
|
||||
{
|
||||
const playermodelinfo &mdl = getplayermodelinfo(player1);
|
||||
loopi(NUMGUNS)
|
||||
{
|
||||
const char *file = guns[i].file;
|
||||
if(!file) continue;
|
||||
string fname;
|
||||
if(m_teammode)
|
||||
{
|
||||
loopj(MAXTEAMS)
|
||||
{
|
||||
formatstring(fname, "%s/%s", mdl.hudguns[1+j], file);
|
||||
preloadmodel(fname);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
formatstring(fname, "%s/%s", mdl.hudguns[0], file);
|
||||
preloadmodel(fname);
|
||||
}
|
||||
formatstring(fname, "worldgun/%s", file);
|
||||
preloadmodel(fname);
|
||||
}
|
||||
}
|
||||
|
||||
void preloadsounds()
|
||||
{
|
||||
//for(int i = S_JUMP; i <= S_DIE2; i++) preloadsound(i);
|
||||
}
|
||||
|
||||
void preload()
|
||||
{
|
||||
if(hudgun) preloadweapons();
|
||||
preloadbouncers();
|
||||
preloadplayermodel();
|
||||
preloadsounds();
|
||||
entities::preloadentities();
|
||||
}
|
||||
|
||||
|
|
|
@ -47,81 +47,12 @@ namespace server
|
|||
bool flush(clientinfo *ci, int fmillis);
|
||||
};
|
||||
|
||||
struct hitinfo
|
||||
{
|
||||
int target;
|
||||
int lifesequence;
|
||||
int rays;
|
||||
float dist;
|
||||
vec dir;
|
||||
};
|
||||
|
||||
struct shotevent : timedevent
|
||||
{
|
||||
int id, atk;
|
||||
vec from, to;
|
||||
vector<hitinfo> hits;
|
||||
|
||||
void process(clientinfo *ci);
|
||||
};
|
||||
|
||||
struct explodeevent : timedevent
|
||||
{
|
||||
int id, atk;
|
||||
vector<hitinfo> hits;
|
||||
|
||||
bool keepable() const { return true; }
|
||||
|
||||
void process(clientinfo *ci);
|
||||
};
|
||||
|
||||
struct suicideevent : gameevent
|
||||
{
|
||||
void process(clientinfo *ci);
|
||||
};
|
||||
|
||||
struct pickupevent : gameevent
|
||||
{
|
||||
int ent;
|
||||
|
||||
void process(clientinfo *ci);
|
||||
};
|
||||
|
||||
template <int N>
|
||||
struct projectilestate
|
||||
{
|
||||
int projs[N];
|
||||
int numprojs;
|
||||
|
||||
projectilestate() : numprojs(0) {}
|
||||
|
||||
void reset() { numprojs = 0; }
|
||||
|
||||
void add(int val)
|
||||
{
|
||||
if(numprojs>=N) numprojs = 0;
|
||||
projs[numprojs++] = val;
|
||||
}
|
||||
|
||||
bool remove(int val)
|
||||
{
|
||||
loopi(numprojs) if(projs[i]==val)
|
||||
{
|
||||
projs[i] = projs[--numprojs];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
struct servstate : gamestate
|
||||
{
|
||||
vec o;
|
||||
int state, editstate;
|
||||
int lastdeath, deadflush, lastspawn, lifesequence;
|
||||
int lastshot;
|
||||
projectilestate<8> projs;
|
||||
int frags, flags, deaths, shotdamage, damage;
|
||||
int lasttimeplayed, timeplayed;
|
||||
float effectiveness;
|
||||
|
||||
|
@ -134,18 +65,15 @@ namespace server
|
|||
|
||||
bool waitexpired(int gamemillis)
|
||||
{
|
||||
return gamemillis - lastshot >= gunwait;
|
||||
return true;
|
||||
}
|
||||
|
||||
void reset()
|
||||
{
|
||||
if(state!=CS_SPECTATOR) state = editstate = CS_DEAD;
|
||||
maxhealth = 1;
|
||||
projs.reset();
|
||||
|
||||
timeplayed = 0;
|
||||
effectiveness = 0;
|
||||
frags = flags = deaths = shotdamage = damage = 0;
|
||||
|
||||
lastdeath = 0;
|
||||
|
||||
|
@ -158,13 +86,11 @@ namespace server
|
|||
o = vec(-1e10f, -1e10f, -1e10f);
|
||||
deadflush = 0;
|
||||
lastspawn = -1;
|
||||
lastshot = 0;
|
||||
}
|
||||
|
||||
void reassign()
|
||||
{
|
||||
respawn();
|
||||
projs.reset();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -295,18 +221,6 @@ namespace server
|
|||
}
|
||||
};
|
||||
|
||||
struct ban
|
||||
{
|
||||
int time, expire;
|
||||
uint ip;
|
||||
};
|
||||
|
||||
#define MM_MODE 0xF
|
||||
#define MM_AUTOAPPROVE 0x1000
|
||||
#define MM_PRIVSERV (MM_MODE | MM_AUTOAPPROVE)
|
||||
#define MM_PUBSERV ((1<<MM_OPEN) | (1<<MM_VETO))
|
||||
#define MM_COOPSERV (MM_AUTOAPPROVE | MM_PUBSERV | (1<<MM_LOCKED))
|
||||
|
||||
bool notgotitems = true; // true when map has changed and waiting for clients to send item
|
||||
int gamemode = 0;
|
||||
int gamemillis = 0, gamelimit = 0, nextexceeded = 0, gamespeed = 100;
|
||||
|
@ -315,86 +229,10 @@ namespace server
|
|||
string smapname = "";
|
||||
int interm = 0;
|
||||
enet_uint32 lastsend = 0;
|
||||
int mastermode = MM_OPEN, mastermask = MM_PRIVSERV;
|
||||
stream *mapdata = NULL;
|
||||
|
||||
vector<uint> allowedips;
|
||||
vector<ban> bannedips;
|
||||
|
||||
void addban(uint ip, int expire)
|
||||
{
|
||||
allowedips.removeobj(ip);
|
||||
ban b;
|
||||
b.time = totalmillis;
|
||||
b.expire = totalmillis + expire;
|
||||
b.ip = ip;
|
||||
loopv(bannedips) if(bannedips[i].expire - b.expire > 0) { bannedips.insert(i, b); return; }
|
||||
bannedips.add(b);
|
||||
}
|
||||
|
||||
vector<clientinfo *> connects, clients;
|
||||
|
||||
bool searchmodename(const char *haystack, const char *needle)
|
||||
{
|
||||
if(!needle[0]) return true;
|
||||
do
|
||||
{
|
||||
if(needle[0] != '.')
|
||||
{
|
||||
haystack = strchr(haystack, needle[0]);
|
||||
if(!haystack) break;
|
||||
haystack++;
|
||||
}
|
||||
const char *h = haystack, *n = needle+1;
|
||||
for(; *h && *n; h++)
|
||||
{
|
||||
if(*h == *n) n++;
|
||||
else if(*h != ' ') break;
|
||||
}
|
||||
if(!*n) return true;
|
||||
if(*n == '.') return !*h;
|
||||
} while(needle[0] != '.');
|
||||
return false;
|
||||
}
|
||||
|
||||
int genmodemask(vector<char *> &modes)
|
||||
{
|
||||
int modemask = 0;
|
||||
loopv(modes)
|
||||
{
|
||||
const char *mode = modes[i];
|
||||
int op = mode[0];
|
||||
switch(mode[0])
|
||||
{
|
||||
case '*':
|
||||
modemask |= 1<<NUMGAMEMODES;
|
||||
loopk(NUMGAMEMODES) if(m_checknot(k+STARTGAMEMODE, M_DEMO|M_EDIT|M_LOCAL)) modemask |= 1<<k;
|
||||
continue;
|
||||
case '!':
|
||||
mode++;
|
||||
if(mode[0] != '?') break;
|
||||
case '?':
|
||||
mode++;
|
||||
loopk(NUMGAMEMODES) if(searchmodename(gamemodes[k].name, mode))
|
||||
{
|
||||
if(op == '!') modemask &= ~(1<<k);
|
||||
else modemask |= 1<<k;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
int modenum = INT_MAX;
|
||||
if(isdigit(mode[0])) modenum = atoi(mode);
|
||||
else loopk(NUMGAMEMODES) if(searchmodename(gamemodes[k].name, mode)) { modenum = k+STARTGAMEMODE; break; }
|
||||
if(!m_valid(modenum)) continue;
|
||||
switch(op)
|
||||
{
|
||||
case '!': modemask &= ~(1 << (modenum - STARTGAMEMODE)); break;
|
||||
default: modemask |= 1 << (modenum - STARTGAMEMODE); break;
|
||||
}
|
||||
}
|
||||
return modemask;
|
||||
}
|
||||
|
||||
void *newclientinfo() { return new clientinfo; }
|
||||
void deleteclientinfo(void *ci) { delete (clientinfo *)ci; }
|
||||
|
||||
|
@ -419,28 +257,6 @@ namespace server
|
|||
return msg >= 0 && msg < NUMMSG ? sizetable[msg] : -1;
|
||||
}
|
||||
|
||||
const char *modename(int n, const char *unknown)
|
||||
{
|
||||
if(m_valid(n)) return gamemodes[n - STARTGAMEMODE].name;
|
||||
return unknown;
|
||||
}
|
||||
|
||||
const char *modeprettyname(int n, const char *unknown)
|
||||
{
|
||||
if(m_valid(n)) return gamemodes[n - STARTGAMEMODE].prettyname;
|
||||
return unknown;
|
||||
}
|
||||
|
||||
const char *mastermodename(int n, const char *unknown)
|
||||
{
|
||||
return (n>=MM_START && size_t(n-MM_START)<sizeof(mastermodenames)/sizeof(mastermodenames[0])) ? mastermodenames[n-MM_START] : unknown;
|
||||
}
|
||||
|
||||
const char *privname(int type)
|
||||
{
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
void sendservmsg(const char *s) { sendf(-1, 1, "ris", N_SERVMSG, s); }
|
||||
|
||||
void sendservmsgf(const char *fmt, ...) PRINTFARGS(1, 2);
|
||||
|
@ -510,7 +326,7 @@ namespace server
|
|||
virtual void spawned(clientinfo *ci) {}
|
||||
virtual int fragvalue(clientinfo *victim, clientinfo *actor)
|
||||
{
|
||||
if(victim==actor || isteam(victim->team, actor->team)) return -1;
|
||||
if(victim==actor) return -1;
|
||||
return 1;
|
||||
}
|
||||
virtual void died(clientinfo *victim, clientinfo *actor) {}
|
||||
|
@ -521,7 +337,6 @@ namespace server
|
|||
virtual void cleanup() {}
|
||||
virtual void setup() {}
|
||||
virtual void newmap() {}
|
||||
virtual void intermission() {}
|
||||
virtual bool hidefrags() { return false; }
|
||||
};
|
||||
|
||||
|
@ -551,108 +366,7 @@ namespace server
|
|||
|
||||
bool pickup(int i, int sender) // server side item pickup, acknowledge first client that gets it
|
||||
{
|
||||
if((m_timed && gamemillis>=gamelimit) || !sents.inrange(i) || !sents[i].spawned) return false;
|
||||
clientinfo *ci = getinfo(sender);
|
||||
if(!ci) return false;
|
||||
if(!ci->local && !ci->state.canpickup(sents[i].type))
|
||||
{
|
||||
sendf(sender, 1, "ri3", N_ITEMACC, i, -1);
|
||||
return false;
|
||||
}
|
||||
sents[i].spawned = false;
|
||||
sents[i].spawntime = spawntime(sents[i].type);
|
||||
sendf(-1, 1, "ri3", N_ITEMACC, i, sender);
|
||||
ci->state.pickup(sents[i].type);
|
||||
return true;
|
||||
}
|
||||
|
||||
static teaminfo teaminfos[MAXTEAMS];
|
||||
|
||||
void clearteaminfo()
|
||||
{
|
||||
loopi(MAXTEAMS) teaminfos[i].reset();
|
||||
}
|
||||
|
||||
clientinfo *choosebestclient(float &bestrank)
|
||||
{
|
||||
clientinfo *best = NULL;
|
||||
bestrank = -1;
|
||||
loopv(clients)
|
||||
{
|
||||
clientinfo *ci = clients[i];
|
||||
if(ci->state.timeplayed<0) continue;
|
||||
float rank = ci->state.state!=CS_SPECTATOR ? ci->state.effectiveness/max(ci->state.timeplayed, 1) : -1;
|
||||
if(!best || rank > bestrank) { best = ci; bestrank = rank; }
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
void autoteam()
|
||||
{
|
||||
vector<clientinfo *> team[MAXTEAMS];
|
||||
float teamrank[MAXTEAMS] = {0};
|
||||
for(int round = 0, remaining = clients.length(); remaining>=0; round++)
|
||||
{
|
||||
int first = round&1, second = (round+1)&1, selected = 0;
|
||||
while(teamrank[first] <= teamrank[second])
|
||||
{
|
||||
float rank;
|
||||
clientinfo *ci = choosebestclient(rank);
|
||||
if(!ci) break;
|
||||
if(smode && smode->hidefrags()) rank = 1;
|
||||
else if(selected && rank<=0) break;
|
||||
ci->state.timeplayed = -1;
|
||||
team[first].add(ci);
|
||||
if(rank>0) teamrank[first] += rank;
|
||||
selected++;
|
||||
if(rank<=0) break;
|
||||
}
|
||||
if(!selected) break;
|
||||
remaining -= selected;
|
||||
}
|
||||
loopi(MAXTEAMS) loopvj(team[i])
|
||||
{
|
||||
clientinfo *ci = team[i][j];
|
||||
if(ci->team == 1+i) continue;
|
||||
ci->team = 1+i;
|
||||
sendf(-1, 1, "riiii", N_SETTEAM, ci->clientnum, ci->team, -1);
|
||||
}
|
||||
}
|
||||
|
||||
struct teamrank
|
||||
{
|
||||
float rank;
|
||||
int clients;
|
||||
|
||||
teamrank() : rank(0), clients(0) {}
|
||||
};
|
||||
|
||||
int chooseworstteam(clientinfo *exclude = NULL)
|
||||
{
|
||||
teamrank teamranks[MAXTEAMS];
|
||||
loopv(clients)
|
||||
{
|
||||
clientinfo *ci = clients[i];
|
||||
if(ci==exclude || ci->state.state==CS_SPECTATOR || !validteam(ci->team)) continue;
|
||||
|
||||
ci->state.timeplayed += lastmillis - ci->state.lasttimeplayed;
|
||||
ci->state.lasttimeplayed = lastmillis;
|
||||
|
||||
teamrank &ts = teamranks[ci->team-1];
|
||||
ts.rank += ci->state.effectiveness/max(ci->state.timeplayed, 1);
|
||||
ts.clients++;
|
||||
}
|
||||
teamrank *worst = &teamranks[0];
|
||||
for(int i = 1; i < MAXTEAMS; i++)
|
||||
{
|
||||
teamrank &ts = teamranks[i];
|
||||
if(smode && smode->hidefrags())
|
||||
{
|
||||
if(ts.clients < worst->clients || (ts.clients == worst->clients && ts.rank < worst->rank)) worst = &ts;
|
||||
}
|
||||
else if(ts.rank < worst->rank || (ts.rank == worst->rank && ts.clients < worst->clients)) worst = &ts;
|
||||
}
|
||||
return 1+int(worst-teamranks);
|
||||
return false;
|
||||
}
|
||||
|
||||
int welcomepacket(packetbuf &p, clientinfo *ci);
|
||||
|
@ -709,7 +423,7 @@ namespace server
|
|||
}
|
||||
|
||||
uchar operator[](int msg) const { return msg >= 0 && msg < NUMMSG ? msgmask[msg] : 0; }
|
||||
} msgfilter(-1, N_CONNECT, N_SERVINFO, N_INITCLIENT, N_WELCOME, N_MAPCHANGE, N_SERVMSG, N_DAMAGE, N_HITPUSH, N_SHOTFX, N_EXPLODEFX, N_DIED, N_SPAWNSTATE, N_FORCEDEATH, N_TEAMINFO, N_ITEMACC, N_ITEMSPAWN, N_TIMEUP, N_CDIS, N_PONG, N_RESUME, N_SENDMAP, N_CLIENT, -2, N_CALCLIGHT, N_REMIP, N_NEWMAP, N_GETMAP, N_SENDMAP, N_CLIPBOARD, -3, N_EDITENT, N_EDITF, N_EDITT, N_EDITM, N_FLIP, N_COPY, N_PASTE, N_ROTATE, N_REPLACE, N_DELCUBE, N_EDITVAR, N_EDITVSLOT, N_UNDO, N_REDO, -4, N_POS, NUMMSG);
|
||||
} msgfilter(-1, N_CONNECT, N_SERVINFO, N_INITCLIENT, N_WELCOME, N_MAPCHANGE, N_SERVMSG, N_DAMAGE, N_HITPUSH, N_SHOTFX, N_EXPLODEFX, N_DIED, N_SPAWNSTATE, N_FORCEDEATH, N_CDIS, N_PONG, N_RESUME, N_SENDMAP, N_CLIENT, -2, N_CALCLIGHT, N_REMIP, N_NEWMAP, N_GETMAP, N_SENDMAP, N_CLIPBOARD, -3, N_EDITENT, N_EDITF, N_EDITT, N_EDITM, N_FLIP, N_COPY, N_PASTE, N_ROTATE, N_REPLACE, N_DELCUBE, N_EDITVAR, N_EDITVSLOT, N_UNDO, N_REDO, -4, N_POS, NUMMSG);
|
||||
|
||||
int checktype(int type, clientinfo *ci)
|
||||
{
|
||||
|
@ -892,10 +606,6 @@ namespace server
|
|||
void sendstate(servstate &gs, T &p)
|
||||
{
|
||||
putint(p, gs.lifesequence);
|
||||
putint(p, gs.health);
|
||||
putint(p, gs.maxhealth);
|
||||
putint(p, gs.gunselect);
|
||||
loopi(NUMGUNS) putint(p, gs.ammo[i]);
|
||||
}
|
||||
|
||||
void spawnstate(clientinfo *ci)
|
||||
|
@ -909,9 +619,8 @@ namespace server
|
|||
{
|
||||
servstate &gs = ci->state;
|
||||
spawnstate(ci);
|
||||
sendf(ci->ownernum, 1, "rii5v", N_SPAWNSTATE, ci->clientnum, gs.lifesequence,
|
||||
gs.health, gs.maxhealth,
|
||||
gs.gunselect, NUMGUNS, gs.ammo);
|
||||
sendf(ci->ownernum, 1, "rii4", N_SPAWNSTATE, ci->clientnum, gs.lifesequence,
|
||||
1, 1);
|
||||
gs.lastspawn = gamemillis;
|
||||
}
|
||||
|
||||
|
@ -927,9 +636,6 @@ namespace server
|
|||
putint(p, N_INITCLIENT);
|
||||
putint(p, ci->clientnum);
|
||||
sendstring(ci->name, p);
|
||||
putint(p, ci->team);
|
||||
putint(p, ci->playermodel);
|
||||
putint(p, ci->playercolor);
|
||||
}
|
||||
|
||||
void welcomeinitclient(packetbuf &p, int exclude = -1)
|
||||
|
@ -945,8 +651,7 @@ namespace server
|
|||
|
||||
bool hasmap(clientinfo *ci)
|
||||
{
|
||||
return (m_edit && (clients.length() > 0 || ci->local)) ||
|
||||
(smapname[0] && (!m_timed || gamemillis < gamelimit || (ci->state.state==CS_SPECTATOR && !ci->local) || numclients(ci->clientnum, true, true, true)));
|
||||
return true;
|
||||
}
|
||||
|
||||
int welcomepacket(packetbuf &p, clientinfo *ci)
|
||||
|
@ -956,11 +661,6 @@ namespace server
|
|||
sendstring(smapname, p);
|
||||
putint(p, gamemode);
|
||||
putint(p, notgotitems ? 1 : 0);
|
||||
if(!ci || (m_timed && smapname[0]))
|
||||
{
|
||||
putint(p, N_TIMEUP);
|
||||
putint(p, gamemillis < gamelimit && !interm ? max((gamelimit - gamemillis)/1000, 1) : 0);
|
||||
}
|
||||
if(!notgotitems)
|
||||
{
|
||||
putint(p, N_ITEMLIST);
|
||||
|
@ -983,15 +683,6 @@ namespace server
|
|||
putint(p, gamespeed);
|
||||
putint(p, -1);
|
||||
}
|
||||
if(m_teammode)
|
||||
{
|
||||
putint(p, N_TEAMINFO);
|
||||
loopi(MAXTEAMS)
|
||||
{
|
||||
teaminfo &t = teaminfos[i];
|
||||
putint(p, t.frags);
|
||||
}
|
||||
}
|
||||
if(ci)
|
||||
{
|
||||
putint(p, N_SETTEAM);
|
||||
|
@ -999,25 +690,6 @@ namespace server
|
|||
putint(p, ci->team);
|
||||
putint(p, -1);
|
||||
}
|
||||
if(ci && m_mp(gamemode) && ci->state.state!=CS_SPECTATOR)
|
||||
{
|
||||
if(smode && !smode->canspawn(ci, true))
|
||||
{
|
||||
ci->state.state = CS_DEAD;
|
||||
putint(p, N_FORCEDEATH);
|
||||
putint(p, ci->clientnum);
|
||||
sendf(-1, 1, "ri2x", N_FORCEDEATH, ci->clientnum, ci->clientnum);
|
||||
}
|
||||
else
|
||||
{
|
||||
servstate &gs = ci->state;
|
||||
spawnstate(ci);
|
||||
putint(p, N_SPAWNSTATE);
|
||||
putint(p, ci->clientnum);
|
||||
sendstate(gs, p);
|
||||
gs.lastspawn = gamemillis;
|
||||
}
|
||||
}
|
||||
if(ci && ci->state.state==CS_SPECTATOR)
|
||||
{
|
||||
putint(p, N_SPECTATOR);
|
||||
|
@ -1034,9 +706,6 @@ namespace server
|
|||
if(ci && oi->clientnum==ci->clientnum) continue;
|
||||
putint(p, oi->clientnum);
|
||||
putint(p, oi->state.state);
|
||||
putint(p, oi->state.frags);
|
||||
putint(p, oi->state.flags);
|
||||
putint(p, oi->state.deaths);
|
||||
sendstate(oi->state, p);
|
||||
}
|
||||
putint(p, -1);
|
||||
|
@ -1049,11 +718,9 @@ namespace server
|
|||
void sendresume(clientinfo *ci)
|
||||
{
|
||||
servstate &gs = ci->state;
|
||||
sendf(-1, 1, "ri3i7vi", N_RESUME, ci->clientnum, gs.state,
|
||||
gs.frags, gs.flags, gs.deaths,
|
||||
sendf(-1, 1, "ri3ii", N_RESUME, ci->clientnum, gs.state,
|
||||
gs.lifesequence,
|
||||
gs.health, gs.maxhealth,
|
||||
gs.gunselect, NUMGUNS, gs.ammo, -1);
|
||||
-1);
|
||||
}
|
||||
|
||||
void sendinitclient(clientinfo *ci)
|
||||
|
@ -1074,8 +741,7 @@ namespace server
|
|||
server_entity se = { NOTUSED, 0, false };
|
||||
while(sents.length()<=i) sents.add(se);
|
||||
sents[i].type = ments[i].type;
|
||||
if(m_mp(gamemode) && delayspawn(sents[i].type)) sents[i].spawntime = spawntime(sents[i].type);
|
||||
else sents[i].spawned = true;
|
||||
sents[i].spawned = true;
|
||||
}
|
||||
notgotitems = false;
|
||||
}
|
||||
|
@ -1088,7 +754,7 @@ namespace server
|
|||
|
||||
gamemode = mode;
|
||||
gamemillis = 0;
|
||||
gamelimit = (m_overtime ? 15 : 10)*60000;
|
||||
gamelimit = 600000;
|
||||
interm = 0;
|
||||
nextexceeded = 0;
|
||||
copystring(smapname, s);
|
||||
|
@ -1099,22 +765,17 @@ namespace server
|
|||
ci->state.timeplayed += lastmillis - ci->state.lasttimeplayed;
|
||||
}
|
||||
|
||||
if(!m_mp(gamemode)) kicknonlocalclients(DISC_LOCAL);
|
||||
kicknonlocalclients(DISC_LOCAL);
|
||||
|
||||
sendf(-1, 1, "risii", N_MAPCHANGE, smapname, gamemode, 1);
|
||||
|
||||
clearteaminfo();
|
||||
if(m_teammode) autoteam();
|
||||
|
||||
smode = NULL;
|
||||
|
||||
if(m_timed && smapname[0]) sendf(-1, 1, "ri2", N_TIMEUP, gamemillis < gamelimit && !interm ? max((gamelimit - gamemillis)/1000, 1) : 0);
|
||||
loopv(clients)
|
||||
{
|
||||
clientinfo *ci = clients[i];
|
||||
ci->mapchange();
|
||||
ci->state.lasttimeplayed = lastmillis;
|
||||
if(m_mp(gamemode) && ci->state.state!=CS_SPECTATOR) sendspawn(ci);
|
||||
}
|
||||
|
||||
if(smode) smode->setup();
|
||||
|
@ -1125,156 +786,6 @@ namespace server
|
|||
changemap(map, mode);
|
||||
}
|
||||
|
||||
void checkintermission()
|
||||
{
|
||||
if(gamemillis >= gamelimit && !interm)
|
||||
{
|
||||
sendf(-1, 1, "ri2", N_TIMEUP, 0);
|
||||
if(smode) smode->intermission();
|
||||
changegamespeed(100);
|
||||
interm = gamemillis + 10000;
|
||||
}
|
||||
}
|
||||
|
||||
void startintermission() { gamelimit = min(gamelimit, gamemillis); checkintermission(); }
|
||||
|
||||
void dodamage(clientinfo *target, clientinfo *actor, int damage, int atk, const vec &hitpush = vec(0, 0, 0))
|
||||
{
|
||||
servstate &ts = target->state;
|
||||
ts.dodamage(damage);
|
||||
if(target!=actor && !isteam(target->team, actor->team)) actor->state.damage += damage;
|
||||
sendf(-1, 1, "ri5", N_DAMAGE, target->clientnum, actor->clientnum, damage, ts.health);
|
||||
if(target==actor) target->setpushed();
|
||||
else if(!hitpush.iszero())
|
||||
{
|
||||
ivec v(vec(hitpush).rescale(DNF));
|
||||
sendf(ts.health<=0 ? -1 : target->ownernum, 1, "ri7", N_HITPUSH, target->clientnum, atk, damage, v.x, v.y, v.z);
|
||||
target->setpushed();
|
||||
}
|
||||
if(ts.health<=0)
|
||||
{
|
||||
target->state.deaths++;
|
||||
int fragvalue = smode ? smode->fragvalue(target, actor) : (target==actor || isteam(target->team, actor->team) ? -1 : 1);
|
||||
actor->state.frags += fragvalue;
|
||||
if(fragvalue>0)
|
||||
{
|
||||
int friends = 0, enemies = 0; // note: friends also includes the fragger
|
||||
if(m_teammode) loopv(clients) if(clients[i]->team != actor->team) enemies++; else friends++;
|
||||
else { friends = 1; enemies = clients.length()-1; }
|
||||
actor->state.effectiveness += fragvalue*friends/float(max(enemies, 1));
|
||||
}
|
||||
teaminfo *t = m_teammode && validteam(actor->team) ? &teaminfos[actor->team-1] : NULL;
|
||||
if(t) t->frags += fragvalue;
|
||||
sendf(-1, 1, "ri5", N_DIED, target->clientnum, actor->clientnum, actor->state.frags, t ? t->frags : 0);
|
||||
target->position.setsize(0);
|
||||
if(smode) smode->died(target, actor);
|
||||
ts.state = CS_DEAD;
|
||||
ts.lastdeath = gamemillis;
|
||||
ts.deadflush = ts.lastdeath + DEATHMILLIS;
|
||||
// don't issue respawn yet until DEATHMILLIS has elapsed
|
||||
// ts.respawn();
|
||||
}
|
||||
}
|
||||
|
||||
void suicide(clientinfo *ci)
|
||||
{
|
||||
servstate &gs = ci->state;
|
||||
if(gs.state!=CS_ALIVE) return;
|
||||
int fragvalue = smode ? smode->fragvalue(ci, ci) : -1;
|
||||
ci->state.frags += fragvalue;
|
||||
ci->state.deaths++;
|
||||
teaminfo *t = m_teammode && validteam(ci->team) ? &teaminfos[ci->team-1] : NULL;
|
||||
if(t) t->frags += fragvalue;
|
||||
sendf(-1, 1, "ri5", N_DIED, ci->clientnum, ci->clientnum, gs.frags, t ? t->frags : 0);
|
||||
ci->position.setsize(0);
|
||||
if(smode) smode->died(ci, NULL);
|
||||
gs.state = CS_DEAD;
|
||||
gs.lastdeath = gamemillis;
|
||||
gs.respawn();
|
||||
}
|
||||
|
||||
void suicideevent::process(clientinfo *ci)
|
||||
{
|
||||
suicide(ci);
|
||||
}
|
||||
|
||||
void explodeevent::process(clientinfo *ci)
|
||||
{
|
||||
servstate &gs = ci->state;
|
||||
switch(atk)
|
||||
{
|
||||
case ATK_PULSE_SHOOT:
|
||||
if(!gs.projs.remove(id)) return;
|
||||
break;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
sendf(-1, 1, "ri4x", N_EXPLODEFX, ci->clientnum, atk, id, ci->ownernum);
|
||||
loopv(hits)
|
||||
{
|
||||
hitinfo &h = hits[i];
|
||||
clientinfo *target = getinfo(h.target);
|
||||
if(!target || target->state.state!=CS_ALIVE || h.lifesequence!=target->state.lifesequence || h.dist<0 || h.dist>attacks[atk].exprad) continue;
|
||||
|
||||
bool dup = false;
|
||||
loopj(i) if(hits[j].target==h.target) { dup = true; break; }
|
||||
if(dup) continue;
|
||||
|
||||
float damage = attacks[atk].damage*(1-h.dist/EXP_DISTSCALE/attacks[atk].exprad);
|
||||
if(target==ci) damage /= EXP_SELFDAMDIV;
|
||||
if(damage > 0) dodamage(target, ci, max(int(damage), 1), atk, h.dir);
|
||||
}
|
||||
}
|
||||
|
||||
void shotevent::process(clientinfo *ci)
|
||||
{
|
||||
servstate &gs = ci->state;
|
||||
int wait = millis - gs.lastshot;
|
||||
if(!gs.isalive(gamemillis) ||
|
||||
wait<gs.gunwait ||
|
||||
!validatk(atk))
|
||||
return;
|
||||
int gun = attacks[atk].gun;
|
||||
if(gs.ammo[gun]<=0 || (attacks[atk].range && from.dist(to) > attacks[atk].range + 1))
|
||||
return;
|
||||
gs.ammo[gun] -= attacks[atk].use;
|
||||
gs.lastshot = millis;
|
||||
gs.gunwait = attacks[atk].attackdelay;
|
||||
sendf(-1, 1, "rii9x", N_SHOTFX, ci->clientnum, atk, id,
|
||||
int(from.x*DMF), int(from.y*DMF), int(from.z*DMF),
|
||||
int(to.x*DMF), int(to.y*DMF), int(to.z*DMF),
|
||||
ci->ownernum);
|
||||
gs.shotdamage += attacks[atk].damage*attacks[atk].rays;
|
||||
switch(atk)
|
||||
{
|
||||
case ATK_PULSE_SHOOT: gs.projs.add(id); break;
|
||||
default:
|
||||
{
|
||||
int totalrays = 0, maxrays = attacks[atk].rays;
|
||||
loopv(hits)
|
||||
{
|
||||
hitinfo &h = hits[i];
|
||||
clientinfo *target = getinfo(h.target);
|
||||
if(!target || target->state.state!=CS_ALIVE || h.lifesequence!=target->state.lifesequence || h.rays<1 || h.dist > attacks[atk].range + 1) continue;
|
||||
|
||||
totalrays += h.rays;
|
||||
if(totalrays>maxrays) continue;
|
||||
int damage = h.rays*attacks[atk].damage;
|
||||
dodamage(target, ci, damage, atk, h.dir);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void pickupevent::process(clientinfo *ci)
|
||||
{
|
||||
servstate &gs = ci->state;
|
||||
if(m_mp(gamemode) && !gs.isalive(gamemillis)) return;
|
||||
pickup(ent, ci->clientnum);
|
||||
}
|
||||
|
||||
bool gameevent::flush(clientinfo *ci, int fmillis)
|
||||
{
|
||||
process(ci);
|
||||
|
@ -1343,30 +854,13 @@ namespace server
|
|||
{
|
||||
gamemillis += curtime;
|
||||
|
||||
if(!m_timed || gamemillis < gamelimit)
|
||||
{
|
||||
processevents();
|
||||
if(curtime)
|
||||
{
|
||||
loopv(sents) if(sents[i].spawntime) // spawn entities when timer reached
|
||||
{
|
||||
sents[i].spawntime -= curtime;
|
||||
if(sents[i].spawntime<=0)
|
||||
{
|
||||
sents[i].spawntime = 0;
|
||||
sents[i].spawned = true;
|
||||
sendf(-1, 1, "ri2", N_ITEMSPAWN, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
if(smode) smode->update();
|
||||
}
|
||||
processevents();
|
||||
if(smode) smode->update();
|
||||
}
|
||||
|
||||
while(bannedips.length() && bannedips[0].expire-totalmillis <= 0) bannedips.remove(0);
|
||||
loopv(connects) if(totalmillis-connects[i]->connectmillis>15000) disconnect_client(connects[i]->clientnum, DISC_TIMEOUT);
|
||||
|
||||
if(nextexceeded && gamemillis > nextexceeded && (!m_timed || gamemillis < gamelimit))
|
||||
if(nextexceeded && gamemillis > nextexceeded)
|
||||
{
|
||||
nextexceeded = 0;
|
||||
loopvrev(clients)
|
||||
|
@ -1379,7 +873,6 @@ namespace server
|
|||
|
||||
if(shouldstep && !gamepaused)
|
||||
{
|
||||
if(m_timed && smapname[0] && gamemillis-curtime>0) checkintermission();
|
||||
if(interm > 0 && gamemillis>interm)
|
||||
{
|
||||
interm = -1;
|
||||
|
@ -1389,15 +882,6 @@ namespace server
|
|||
shouldstep = clients.length() > 0;
|
||||
}
|
||||
|
||||
void forcespectator(clientinfo *ci)
|
||||
{
|
||||
if(ci->state.state==CS_ALIVE) suicide(ci);
|
||||
if(smode) smode->leavegame(ci);
|
||||
ci->state.state = CS_SPECTATOR;
|
||||
ci->state.timeplayed += lastmillis - ci->state.lasttimeplayed;
|
||||
sendf(-1, 1, "ri3", N_SPECTATOR, ci->clientnum, 1);
|
||||
}
|
||||
|
||||
struct crcinfo
|
||||
{
|
||||
int crc, matches;
|
||||
|
@ -1407,66 +891,14 @@ namespace server
|
|||
|
||||
static bool compare(const crcinfo &x, const crcinfo &y) { return x.matches > y.matches; }
|
||||
};
|
||||
|
||||
VAR(modifiedmapspectator, 0, 1, 2);
|
||||
|
||||
void checkmaps(int req = -1)
|
||||
{
|
||||
if(m_edit || !smapname[0]) return;
|
||||
vector<crcinfo> crcs;
|
||||
int total = 0, unsent = 0, invalid = 0;
|
||||
if(mcrc) crcs.add(crcinfo(mcrc, clients.length() + 1));
|
||||
loopv(clients)
|
||||
{
|
||||
clientinfo *ci = clients[i];
|
||||
if(ci->state.state==CS_SPECTATOR) continue;
|
||||
total++;
|
||||
if(!ci->clientmap[0])
|
||||
{
|
||||
if(ci->mapcrc < 0) invalid++;
|
||||
else if(!ci->mapcrc) unsent++;
|
||||
}
|
||||
else
|
||||
{
|
||||
crcinfo *match = NULL;
|
||||
loopvj(crcs) if(crcs[j].crc == ci->mapcrc) { match = &crcs[j]; break; }
|
||||
if(!match) crcs.add(crcinfo(ci->mapcrc, 1));
|
||||
else match->matches++;
|
||||
}
|
||||
}
|
||||
if(!mcrc && total - unsent < min(total, 4)) return;
|
||||
crcs.sort(crcinfo::compare);
|
||||
string msg;
|
||||
loopv(clients)
|
||||
{
|
||||
clientinfo *ci = clients[i];
|
||||
if(ci->state.state==CS_SPECTATOR || ci->clientmap[0] || ci->mapcrc >= 0 || (req < 0 && ci->warned)) continue;
|
||||
formatstring(msg, "%s has modified map \"%s\"", colorname(ci), smapname);
|
||||
sendf(req, 1, "ris", N_SERVMSG, msg);
|
||||
if(req < 0) ci->warned = true;
|
||||
}
|
||||
if(crcs.length() >= 2) loopv(crcs)
|
||||
{
|
||||
crcinfo &info = crcs[i];
|
||||
if(i || info.matches <= crcs[i+1].matches) loopvj(clients)
|
||||
{
|
||||
clientinfo *ci = clients[j];
|
||||
if(ci->state.state==CS_SPECTATOR || !ci->clientmap[0] || ci->mapcrc != info.crc || (req < 0 && ci->warned)) continue;
|
||||
formatstring(msg, "%s has modified map \"%s\"", colorname(ci), smapname);
|
||||
sendf(req, 1, "ris", N_SERVMSG, msg);
|
||||
if(req < 0) ci->warned = true;
|
||||
}
|
||||
}
|
||||
if(req < 0 && modifiedmapspectator && (mcrc || modifiedmapspectator > 1)) loopv(clients)
|
||||
{
|
||||
clientinfo *ci = clients[i];
|
||||
if(!ci->local && ci->warned && ci->state.state != CS_SPECTATOR) forcespectator(ci);
|
||||
}
|
||||
}
|
||||
|
||||
bool shouldspectate(clientinfo *ci)
|
||||
{
|
||||
return !ci->local && ci->warned && modifiedmapspectator && (mcrc || modifiedmapspectator > 1);
|
||||
return false;
|
||||
}
|
||||
|
||||
void unspectate(clientinfo *ci)
|
||||
|
@ -1486,7 +918,6 @@ namespace server
|
|||
|
||||
void noclients()
|
||||
{
|
||||
bannedips.shrink(0);
|
||||
}
|
||||
|
||||
void localconnect(int n)
|
||||
|
@ -1514,9 +945,7 @@ namespace server
|
|||
ci->sessionid = (rnd(0x1000000)*((totalmillis%10000)+1))&0xFFFFFF;
|
||||
|
||||
connects.add(ci);
|
||||
if(!m_mp(gamemode)) return DISC_LOCAL;
|
||||
sendservinfo(ci);
|
||||
return DISC_NONE;
|
||||
return DISC_LOCAL;
|
||||
}
|
||||
|
||||
void clientdisconnect(int n)
|
||||
|
@ -1556,7 +985,6 @@ namespace server
|
|||
|
||||
void receivefile(int sender, uchar *data, int len)
|
||||
{
|
||||
if(!m_edit || len <= 0 || len > 4*1024*1024) return;
|
||||
clientinfo *ci = getinfo(sender);
|
||||
if(ci->state.state==CS_SPECTATOR && !ci->local) return;
|
||||
if(mapdata) DELETEP(mapdata);
|
||||
|
@ -1590,10 +1018,9 @@ namespace server
|
|||
|
||||
ci->connected = true;
|
||||
ci->needclipboard = totalmillis ? totalmillis : 1;
|
||||
if(mastermode>=MM_LOCKED) ci->state.state = CS_SPECTATOR;
|
||||
ci->state.lasttimeplayed = lastmillis;
|
||||
|
||||
ci->team = m_teammode ? chooseworstteam(ci) : 0;
|
||||
ci->team = 0;
|
||||
|
||||
sendwelcome(ci);
|
||||
sendinitclient(ci);
|
||||
|
@ -1708,7 +1135,6 @@ namespace server
|
|||
ci->state.editstate = ci->state.state;
|
||||
ci->state.state = CS_EDITING;
|
||||
ci->events.setsize(0);
|
||||
ci->state.projs.reset();
|
||||
}
|
||||
else ci->state.state = ci->state.editstate;
|
||||
QUEUE_MSG;
|
||||
|
@ -1737,10 +1163,6 @@ namespace server
|
|||
break;
|
||||
}
|
||||
|
||||
case N_CHECKMAPS:
|
||||
checkmaps(sender);
|
||||
break;
|
||||
|
||||
case N_TRYSPAWN:
|
||||
if(!ci || !cq || cq->state.state!=CS_DEAD || cq->state.lastspawn>=0 || (smode && !smode->canspawn(cq))) break;
|
||||
if(!ci->clientmap[0] && !ci->mapcrc)
|
||||
|
@ -1761,11 +1183,10 @@ namespace server
|
|||
|
||||
case N_SPAWN:
|
||||
{
|
||||
int ls = getint(p), gunselect = getint(p);
|
||||
if(!cq || (cq->state.state!=CS_ALIVE && cq->state.state!=CS_DEAD && cq->state.state!=CS_EDITING) || ls!=cq->state.lifesequence || cq->state.lastspawn<0 || !validgun(gunselect)) break;
|
||||
int ls = getint(p);
|
||||
if(!cq || (cq->state.state!=CS_ALIVE && cq->state.state!=CS_DEAD && cq->state.state!=CS_EDITING) || ls!=cq->state.lifesequence || cq->state.lastspawn<0) break;
|
||||
cq->state.lastspawn = -1;
|
||||
cq->state.state = CS_ALIVE;
|
||||
cq->state.gunselect = gunselect;
|
||||
cq->exceeded = 0;
|
||||
if(smode) smode->spawned(cq);
|
||||
QUEUE_AI;
|
||||
|
@ -1787,21 +1208,6 @@ namespace server
|
|||
break;
|
||||
}
|
||||
|
||||
case N_SAYTEAM:
|
||||
{
|
||||
getstring(text, p);
|
||||
if(!ci || !cq || (ci->state.state==CS_SPECTATOR && !ci->local) || !m_teammode || !validteam(cq->team)) break;
|
||||
filtertext(text, text, true, true);
|
||||
loopv(clients)
|
||||
{
|
||||
clientinfo *t = clients[i];
|
||||
if(t==cq || t->state.state==CS_SPECTATOR || cq->team != t->team) continue;
|
||||
sendf(t->clientnum, 1, "riis", N_SAYTEAM, cq->clientnum, text);
|
||||
}
|
||||
if(isdedicatedserver() && cq) logoutf("%s <%s>: %s", colorname(cq), teamnames[cq->team], text);
|
||||
break;
|
||||
}
|
||||
|
||||
case N_SWITCHNAME:
|
||||
{
|
||||
QUEUE_MSG;
|
||||
|
@ -1864,10 +1270,6 @@ namespace server
|
|||
break;
|
||||
}
|
||||
|
||||
case N_FORCEINTERMISSION:
|
||||
if(ci->local && !hasnonlocalclients()) startintermission();
|
||||
break;
|
||||
|
||||
case N_NEWMAP:
|
||||
{
|
||||
int size = getint(p);
|
||||
|
@ -2006,8 +1408,8 @@ namespace server
|
|||
putint(p, maxclients);
|
||||
putint(p, gamepaused || gamespeed != 100 ? 5 : 3); // number of attrs following
|
||||
putint(p, gamemode);
|
||||
putint(p, m_timed ? max((gamelimit - gamemillis)/1000, 0) : 0);
|
||||
putint(p, MM_PRIVATE);
|
||||
putint(p, 0);
|
||||
putint(p, 0);
|
||||
if(gamepaused || gamespeed != 100)
|
||||
{
|
||||
putint(p, gamepaused ? 1 : 0);
|
||||
|
|
|
@ -1,775 +0,0 @@
|
|||
// weapon.cpp: all shooting and effects code, projectile management
|
||||
#include "game.hh"
|
||||
|
||||
namespace game
|
||||
{
|
||||
static const int OFFSETMILLIS = 500;
|
||||
vec rays[MAXRAYS];
|
||||
|
||||
struct hitmsg
|
||||
{
|
||||
int target, lifesequence, info1, info2;
|
||||
ivec dir;
|
||||
};
|
||||
vector<hitmsg> hits;
|
||||
|
||||
#if 0
|
||||
#define MINDEBRIS 3
|
||||
VARP(maxdebris, MINDEBRIS, 10, 100);
|
||||
VARP(maxgibs, 0, 4, 100);
|
||||
#endif
|
||||
|
||||
ICOMMAND(getweapon, "", (), intret(player1->gunselect));
|
||||
|
||||
void gunselect(int gun, gameent *d)
|
||||
{
|
||||
if(gun!=d->gunselect)
|
||||
{
|
||||
addmsg(N_GUNSELECT, "rci", d, gun);
|
||||
playsound(S_WEAPLOAD, d == player1 ? NULL : &d->o);
|
||||
}
|
||||
d->gunselect = gun;
|
||||
}
|
||||
|
||||
void nextweapon(int dir, bool force = false)
|
||||
{
|
||||
if(player1->state!=CS_ALIVE) return;
|
||||
dir = (dir < 0 ? NUMGUNS-1 : 1);
|
||||
int gun = player1->gunselect;
|
||||
loopi(NUMGUNS)
|
||||
{
|
||||
gun = (gun + dir)%NUMGUNS;
|
||||
if(force || player1->ammo[gun]) break;
|
||||
}
|
||||
if(gun != player1->gunselect) gunselect(gun, player1);
|
||||
else playsound(S_NOAMMO);
|
||||
}
|
||||
ICOMMAND(nextweapon, "ii", (int *dir, int *force), nextweapon(*dir, *force!=0));
|
||||
|
||||
int getweapon(const char *name)
|
||||
{
|
||||
if(isdigit(name[0])) return parseint(name);
|
||||
else
|
||||
{
|
||||
int len = strlen(name);
|
||||
loopi(sizeof(guns)/sizeof(guns[0])) if(!strncasecmp(guns[i].name, name, len)) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void setweapon(const char *name, bool force = false)
|
||||
{
|
||||
int gun = getweapon(name);
|
||||
if(player1->state!=CS_ALIVE || !validgun(gun)) return;
|
||||
if(force || player1->ammo[gun]) gunselect(gun, player1);
|
||||
else playsound(S_NOAMMO);
|
||||
}
|
||||
ICOMMAND(setweapon, "si", (char *name, int *force), setweapon(name, *force!=0));
|
||||
|
||||
void cycleweapon(int numguns, int *guns, bool force = false)
|
||||
{
|
||||
if(numguns<=0 || player1->state!=CS_ALIVE) return;
|
||||
int offset = 0;
|
||||
loopi(numguns) if(guns[i] == player1->gunselect) { offset = i+1; break; }
|
||||
loopi(numguns)
|
||||
{
|
||||
int gun = guns[(i+offset)%numguns];
|
||||
if(gun>=0 && gun<NUMGUNS && (force || player1->ammo[gun]))
|
||||
{
|
||||
gunselect(gun, player1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
playsound(S_NOAMMO);
|
||||
}
|
||||
ICOMMAND(cycleweapon, "V", (tagval *args, int numargs),
|
||||
{
|
||||
int numguns = min(numargs, 3);
|
||||
int guns[3];
|
||||
loopi(numguns) guns[i] = getweapon(args[i].getstr());
|
||||
cycleweapon(numguns, guns);
|
||||
});
|
||||
|
||||
void weaponswitch(gameent *d)
|
||||
{
|
||||
if(d->state!=CS_ALIVE) return;
|
||||
int s = d->gunselect;
|
||||
if(s!=GUN_PULSE && d->ammo[GUN_PULSE]) s = GUN_PULSE;
|
||||
else if(s!=GUN_RAIL && d->ammo[GUN_RAIL]) s = GUN_RAIL;
|
||||
gunselect(s, d);
|
||||
}
|
||||
|
||||
ICOMMAND(weapon, "V", (tagval *args, int numargs),
|
||||
{
|
||||
if(player1->state!=CS_ALIVE) return;
|
||||
loopi(3)
|
||||
{
|
||||
const char *name = i < numargs ? args[i].getstr() : "";
|
||||
if(name[0])
|
||||
{
|
||||
int gun = getweapon(name);
|
||||
if(validgun(gun) && gun != player1->gunselect && player1->ammo[gun]) { gunselect(gun, player1); return; }
|
||||
} else { weaponswitch(player1); return; }
|
||||
}
|
||||
playsound(S_NOAMMO);
|
||||
});
|
||||
|
||||
void offsetray(const vec &from, const vec &to, int spread, float range, vec &dest)
|
||||
{
|
||||
vec offset;
|
||||
do offset = vec(rndscale(1), rndscale(1), rndscale(1)).sub(0.5f);
|
||||
while(offset.squaredlen() > 0.5f*0.5f);
|
||||
offset.mul((to.dist(from)/1024)*spread);
|
||||
offset.z /= 2;
|
||||
dest = vec(offset).add(to);
|
||||
if(dest != from)
|
||||
{
|
||||
vec dir = vec(dest).sub(from).normalize();
|
||||
raycubepos(from, dir, dest, range, RAY_CLIPMAT|RAY_ALPHAPOLY);
|
||||
}
|
||||
}
|
||||
|
||||
void createrays(int atk, const vec &from, const vec &to) // create random spread of rays
|
||||
{
|
||||
loopi(attacks[atk].rays) offsetray(from, to, attacks[atk].spread, attacks[atk].range, rays[i]);
|
||||
}
|
||||
|
||||
enum { BNC_GIBS, BNC_DEBRIS };
|
||||
|
||||
struct bouncer : physent
|
||||
{
|
||||
int lifetime, bounces;
|
||||
float lastyaw, roll;
|
||||
bool local;
|
||||
gameent *owner;
|
||||
int bouncetype, variant;
|
||||
vec offset;
|
||||
int offsetmillis;
|
||||
int id;
|
||||
|
||||
bouncer() : bounces(0), roll(0), variant(0)
|
||||
{
|
||||
type = ENT_BOUNCE;
|
||||
}
|
||||
};
|
||||
|
||||
vector<bouncer *> bouncers;
|
||||
|
||||
void newbouncer(const vec &from, const vec &to, bool local, int id, gameent *owner, int type, int lifetime, int speed)
|
||||
{
|
||||
bouncer &bnc = *bouncers.add(new bouncer);
|
||||
bnc.o = from;
|
||||
bnc.radius = bnc.xradius = bnc.yradius = type==BNC_DEBRIS ? 0.5f : 1.5f;
|
||||
bnc.eyeheight = bnc.radius;
|
||||
bnc.aboveeye = bnc.radius;
|
||||
bnc.lifetime = lifetime;
|
||||
bnc.local = local;
|
||||
bnc.owner = owner;
|
||||
bnc.bouncetype = type;
|
||||
bnc.id = local ? lastmillis : id;
|
||||
|
||||
switch(type)
|
||||
{
|
||||
case BNC_DEBRIS: bnc.variant = rnd(4); break;
|
||||
case BNC_GIBS: bnc.variant = rnd(3); break;
|
||||
}
|
||||
|
||||
vec dir(to);
|
||||
dir.sub(from).safenormalize();
|
||||
bnc.vel = dir;
|
||||
bnc.vel.mul(speed);
|
||||
|
||||
avoidcollision(&bnc, dir, owner, 0.1f);
|
||||
|
||||
bnc.offset = from;
|
||||
bnc.offset.sub(bnc.o);
|
||||
bnc.offsetmillis = OFFSETMILLIS;
|
||||
|
||||
bnc.resetinterp();
|
||||
}
|
||||
|
||||
void bounced(physent *d, const vec &surface)
|
||||
{
|
||||
if(d->type != ENT_BOUNCE) return;
|
||||
bouncer *b = (bouncer *)d;
|
||||
if(b->bouncetype != BNC_GIBS || b->bounces >= 2) return;
|
||||
b->bounces++;
|
||||
addstain(STAIN_BLOOD, vec(b->o).sub(vec(surface).mul(b->radius)), surface, 2.96f/b->bounces, bvec(0x60, 0xFF, 0xFF), rnd(4));
|
||||
}
|
||||
|
||||
void updatebouncers(int time)
|
||||
{
|
||||
loopv(bouncers)
|
||||
{
|
||||
bouncer &bnc = *bouncers[i];
|
||||
vec old(bnc.o);
|
||||
bool stopped = false;
|
||||
// cheaper variable rate physics for debris, gibs, etc.
|
||||
for(int rtime = time; rtime > 0;)
|
||||
{
|
||||
int qtime = min(30, rtime);
|
||||
rtime -= qtime;
|
||||
if((bnc.lifetime -= qtime)<0 || bounce(&bnc, qtime/1000.0f, 0.6f, 0.5f, 1)) { stopped = true; break; }
|
||||
}
|
||||
if(stopped)
|
||||
{
|
||||
delete bouncers.remove(i--);
|
||||
}
|
||||
else
|
||||
{
|
||||
bnc.roll += old.sub(bnc.o).magnitude()/(4*RAD);
|
||||
bnc.offsetmillis = max(bnc.offsetmillis-time, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void removebouncers(gameent *owner)
|
||||
{
|
||||
loopv(bouncers) if(bouncers[i]->owner==owner) { delete bouncers[i]; bouncers.remove(i--); }
|
||||
}
|
||||
|
||||
void clearbouncers() { bouncers.deletecontents(); }
|
||||
|
||||
struct projectile
|
||||
{
|
||||
vec dir, o, from, to, offset;
|
||||
float speed;
|
||||
gameent *owner;
|
||||
int atk;
|
||||
bool local;
|
||||
int offsetmillis;
|
||||
int id;
|
||||
};
|
||||
vector<projectile> projs;
|
||||
|
||||
void clearprojectiles() { projs.shrink(0); }
|
||||
|
||||
void newprojectile(const vec &from, const vec &to, float speed, bool local, int id, gameent *owner, int atk)
|
||||
{
|
||||
projectile &p = projs.add();
|
||||
p.dir = vec(to).sub(from).safenormalize();
|
||||
p.o = from;
|
||||
p.from = from;
|
||||
p.to = to;
|
||||
p.offset = hudgunorigin(attacks[atk].gun, from, to, owner);
|
||||
p.offset.sub(from);
|
||||
p.speed = speed;
|
||||
p.local = local;
|
||||
p.owner = owner;
|
||||
p.atk = atk;
|
||||
p.offsetmillis = OFFSETMILLIS;
|
||||
p.id = local ? lastmillis : id;
|
||||
}
|
||||
|
||||
void removeprojectiles(gameent *owner)
|
||||
{
|
||||
// can't use loopv here due to strange GCC optimizer bug
|
||||
int len = projs.length();
|
||||
loopi(len) if(projs[i].owner==owner) { projs.remove(i--); len--; }
|
||||
}
|
||||
|
||||
VARP(blood, 0, 1, 1);
|
||||
|
||||
void damageeffect(int damage, gameent *d, bool thirdperson)
|
||||
{
|
||||
vec p = d->o;
|
||||
p.z += 0.6f*(d->eyeheight + d->aboveeye) - d->eyeheight;
|
||||
if(blood) particle_splash(PART_BLOOD, max(damage/10, rnd(3)+1), 1000, p, 0x60FFFF, 2.96f);
|
||||
#if 0
|
||||
if(thirdperson) particle_textcopy(d->abovehead(), tempformatstring("%d", damage), PART_TEXT, 2000, 0xFF4B19, 4.0f, -8);
|
||||
#endif
|
||||
}
|
||||
|
||||
void spawnbouncer(const vec &p, const vec &vel, gameent *d, int type)
|
||||
{
|
||||
vec to(rnd(100)-50, rnd(100)-50, rnd(100)-50);
|
||||
if(to.iszero()) to.z += 1;
|
||||
to.normalize();
|
||||
to.add(p);
|
||||
newbouncer(p, to, true, 0, d, type, rnd(1000)+1000, rnd(100)+20);
|
||||
}
|
||||
|
||||
void gibeffect(int damage, const vec &vel, gameent *d)
|
||||
{
|
||||
#if 0
|
||||
if(!blood || !maxgibs || damage < 0) return;
|
||||
vec from = d->abovehead();
|
||||
loopi(rnd(maxgibs)+1) spawnbouncer(from, vel, d, BNC_GIBS);
|
||||
#endif
|
||||
}
|
||||
|
||||
void hit(int damage, dynent *d, gameent *at, const vec &vel, int atk, float info1, int info2 = 1)
|
||||
{
|
||||
if(at==player1 && d!=at)
|
||||
{
|
||||
extern int hitsound;
|
||||
if(hitsound && lasthit != lastmillis) playsound(S_HIT);
|
||||
lasthit = lastmillis;
|
||||
}
|
||||
|
||||
gameent *f = (gameent *)d;
|
||||
|
||||
f->lastpain = lastmillis;
|
||||
if(at->type==ENT_PLAYER && !isteam(at->team, f->team)) at->totaldamage += damage;
|
||||
|
||||
if(!m_mp(gamemode) || f==at) f->hitpush(damage, vel, at, atk);
|
||||
|
||||
if(!m_mp(gamemode)) damaged(damage, f, at);
|
||||
else
|
||||
{
|
||||
hitmsg &h = hits.add();
|
||||
h.target = f->clientnum;
|
||||
h.lifesequence = f->lifesequence;
|
||||
h.info1 = int(info1*DMF);
|
||||
h.info2 = info2;
|
||||
h.dir = f==at ? ivec(0, 0, 0) : ivec(vec(vel).mul(DNF));
|
||||
if(at==player1)
|
||||
{
|
||||
damageeffect(damage, f);
|
||||
if(f==player1)
|
||||
{
|
||||
damageblend(damage);
|
||||
damagecompass(damage, at ? at->o : f->o);
|
||||
playsound(S_PAIN2);
|
||||
}
|
||||
else playsound(S_PAIN1, &f->o);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void hitpush(int damage, dynent *d, gameent *at, vec &from, vec &to, int atk, int rays)
|
||||
{
|
||||
hit(damage, d, at, vec(to).sub(from).safenormalize(), atk, from.dist(to), rays);
|
||||
}
|
||||
|
||||
float projdist(dynent *o, vec &dir, const vec &v, const vec &vel)
|
||||
{
|
||||
vec middle = o->o;
|
||||
middle.z += (o->aboveeye-o->eyeheight)/2;
|
||||
dir = vec(middle).sub(v).add(vec(vel).mul(5)).safenormalize();
|
||||
|
||||
float low = min(o->o.z - o->eyeheight + o->radius, middle.z),
|
||||
high = max(o->o.z + o->aboveeye - o->radius, middle.z);
|
||||
vec closest(o->o.x, o->o.y, clamp(v.z, low, high));
|
||||
return max(closest.dist(v) - o->radius, 0.0f);
|
||||
}
|
||||
|
||||
void radialeffect(dynent *o, const vec &v, const vec &vel, int qdam, gameent *at, int atk)
|
||||
{
|
||||
if(o->state!=CS_ALIVE) return;
|
||||
vec dir;
|
||||
float dist = projdist(o, dir, v, vel);
|
||||
if(dist<attacks[atk].exprad)
|
||||
{
|
||||
float damage = qdam*(1-dist/EXP_DISTSCALE/attacks[atk].exprad);
|
||||
if(o==at) damage /= EXP_SELFDAMDIV;
|
||||
if(damage > 0) hit(max(int(damage), 1), o, at, dir, atk, dist);
|
||||
}
|
||||
}
|
||||
|
||||
void explode(bool local, gameent *owner, const vec &v, const vec &vel, dynent *safe, int damage, int atk)
|
||||
{
|
||||
particle_splash(PART_SPARK, 200, 300, v, 0x50CFE5, 0.45f);
|
||||
playsound(S_PULSEEXPLODE, &v);
|
||||
particle_fireball(v, 1.15f*attacks[atk].exprad, PART_PULSE_BURST, int(attacks[atk].exprad*20), 0x50CFE5, 4.0f);
|
||||
vec debrisorigin = vec(v).sub(vec(vel).mul(5));
|
||||
adddynlight(safe ? v : debrisorigin, 2*attacks[atk].exprad, vec(1.0f, 3.0f, 4.0f), 350, 40, 0, attacks[atk].exprad/2, vec(0.5f, 1.5f, 2.0f));
|
||||
#if 0
|
||||
int numdebris = maxdebris > MINDEBRIS ? rnd(maxdebris-MINDEBRIS)+MINDEBRIS : min(maxdebris, MINDEBRIS);
|
||||
if(numdebris)
|
||||
{
|
||||
vec debrisvel = vec(vel).neg();
|
||||
loopi(numdebris)
|
||||
spawnbouncer(debrisorigin, debrisvel, owner, BNC_DEBRIS);
|
||||
}
|
||||
#endif
|
||||
if(!local) return;
|
||||
int numdyn = numdynents();
|
||||
loopi(numdyn)
|
||||
{
|
||||
dynent *o = iterdynents(i);
|
||||
if(o->o.reject(v, o->radius + attacks[atk].exprad) || o==safe) continue;
|
||||
radialeffect(o, v, vel, damage, owner, atk);
|
||||
}
|
||||
}
|
||||
|
||||
void pulsestain(const projectile &p, const vec &pos)
|
||||
{
|
||||
vec dir = vec(p.dir).neg();
|
||||
float rad = attacks[p.atk].exprad*0.75f;
|
||||
addstain(STAIN_PULSE_SCORCH, pos, dir, rad);
|
||||
addstain(STAIN_PULSE_GLOW, pos, dir, rad, 0x50CFE5);
|
||||
}
|
||||
|
||||
void projsplash(projectile &p, const vec &v, dynent *safe)
|
||||
{
|
||||
explode(p.local, p.owner, v, p.dir, safe, attacks[p.atk].damage, p.atk);
|
||||
pulsestain(p, v);
|
||||
}
|
||||
|
||||
void explodeeffects(int atk, gameent *d, bool local, int id)
|
||||
{
|
||||
if(local) return;
|
||||
switch(atk)
|
||||
{
|
||||
case ATK_PULSE_SHOOT:
|
||||
loopv(projs)
|
||||
{
|
||||
projectile &p = projs[i];
|
||||
if(p.atk == atk && p.owner == d && p.id == id && !p.local)
|
||||
{
|
||||
vec pos = vec(p.offset).mul(p.offsetmillis/float(OFFSETMILLIS)).add(p.o);
|
||||
explode(p.local, p.owner, pos, p.dir, NULL, 0, atk);
|
||||
pulsestain(p, pos);
|
||||
projs.remove(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool projdamage(dynent *o, projectile &p, const vec &v)
|
||||
{
|
||||
if(o->state!=CS_ALIVE) return false;
|
||||
if(!intersect(o, p.o, v, attacks[p.atk].margin)) return false;
|
||||
projsplash(p, v, o);
|
||||
vec dir;
|
||||
projdist(o, dir, v, p.dir);
|
||||
hit(attacks[p.atk].damage, o, p.owner, dir, p.atk, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
void updateprojectiles(int time)
|
||||
{
|
||||
if(projs.empty()) return;
|
||||
gameent *noside = followingplayer(player1);
|
||||
loopv(projs)
|
||||
{
|
||||
projectile &p = projs[i];
|
||||
p.offsetmillis = max(p.offsetmillis-time, 0);
|
||||
vec dv;
|
||||
float dist = p.to.dist(p.o, dv);
|
||||
dv.mul(time/max(dist*1000/p.speed, float(time)));
|
||||
vec v = vec(p.o).add(dv);
|
||||
bool exploded = false;
|
||||
hits.setsize(0);
|
||||
if(p.local)
|
||||
{
|
||||
vec halfdv = vec(dv).mul(0.5f), bo = vec(p.o).add(halfdv);
|
||||
float br = max(fabs(halfdv.x), fabs(halfdv.y)) + 1 + attacks[p.atk].margin;
|
||||
loopj(numdynents())
|
||||
{
|
||||
dynent *o = iterdynents(j);
|
||||
if(p.owner==o || o->o.reject(bo, o->radius + br)) continue;
|
||||
if(projdamage(o, p, v)) { exploded = true; break; }
|
||||
}
|
||||
}
|
||||
if(!exploded)
|
||||
{
|
||||
if(dist<4)
|
||||
{
|
||||
if(p.o!=p.to) // if original target was moving, reevaluate endpoint
|
||||
{
|
||||
if(raycubepos(p.o, p.dir, p.to, 0, RAY_CLIPMAT|RAY_ALPHAPOLY)>=4) continue;
|
||||
}
|
||||
projsplash(p, v, NULL);
|
||||
exploded = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
vec pos = vec(p.offset).mul(p.offsetmillis/float(OFFSETMILLIS)).add(v);
|
||||
particle_splash(PART_PULSE_FRONT, 1, 1, pos, 0x50CFE5, 2.4f, 150, 20);
|
||||
if(p.owner != noside)
|
||||
{
|
||||
float len = min(20.0f, vec(p.offset).add(p.from).dist(pos));
|
||||
vec dir = vec(dv).normalize(),
|
||||
tail = vec(dir).mul(-len).add(pos),
|
||||
head = vec(dir).mul(2.4f).add(pos);
|
||||
particle_flare(tail, head, 1, PART_PULSE_SIDE, 0x50CFE5, 2.5f);
|
||||
}
|
||||
}
|
||||
}
|
||||
if(exploded)
|
||||
{
|
||||
if(p.local)
|
||||
addmsg(N_EXPLODE, "rci3iv", p.owner, lastmillis-maptime, p.atk, p.id-maptime,
|
||||
hits.length(), hits.length()*sizeof(hitmsg)/sizeof(int), hits.getbuf());
|
||||
projs.remove(i--);
|
||||
}
|
||||
else p.o = v;
|
||||
}
|
||||
}
|
||||
|
||||
void railhit(const vec &from, const vec &to, bool stain = true)
|
||||
{
|
||||
vec dir = vec(from).sub(to).safenormalize();
|
||||
if(stain)
|
||||
{
|
||||
addstain(STAIN_RAIL_HOLE, to, dir, 2.0f);
|
||||
addstain(STAIN_RAIL_GLOW, to, dir, 2.5f, 0x50CFE5);
|
||||
}
|
||||
adddynlight(vec(to).madd(dir, 4), 10, vec(0.25f, 0.75f, 1.0f), 225, 75);
|
||||
}
|
||||
|
||||
void shoteffects(int atk, const vec &from, const vec &to, gameent *d, bool local, int id, int prevaction) // create visual effect from a shot
|
||||
{
|
||||
int gun = attacks[atk].gun;
|
||||
switch(atk)
|
||||
{
|
||||
case ATK_PULSE_SHOOT:
|
||||
if(d->muzzle.x >= 0)
|
||||
particle_flare(d->muzzle, d->muzzle, 140, PART_PULSE_MUZZLE_FLASH, 0x50CFE5, 3.50f, d);
|
||||
newprojectile(from, to, attacks[atk].projspeed, local, id, d, atk);
|
||||
break;
|
||||
|
||||
case ATK_RAIL_SHOOT:
|
||||
particle_splash(PART_SPARK, 200, 250, to, 0x50CFE5, 0.45f);
|
||||
particle_flare(hudgunorigin(gun, from, to, d), to, 500, PART_RAIL_TRAIL, 0x50CFE5, 0.5f);
|
||||
if(d->muzzle.x >= 0)
|
||||
particle_flare(d->muzzle, d->muzzle, 140, PART_RAIL_MUZZLE_FLASH, 0x50CFE5, 2.75f, d);
|
||||
adddynlight(hudgunorigin(gun, d->o, to, d), 35, vec(0.25f, 0.75f, 1.0f), 75, 75, DL_FLASH, 0, vec(0, 0, 0), d);
|
||||
if(!local) railhit(from, to);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if(d == followingplayer(player1)) playsound(attacks[atk].hudsound, NULL);
|
||||
else playsound(attacks[atk].sound, &d->o);
|
||||
}
|
||||
|
||||
void particletrack(physent *owner, vec &o, vec &d)
|
||||
{
|
||||
if(owner->type!=ENT_PLAYER) return;
|
||||
gameent *pl = (gameent *)owner;
|
||||
if(pl->muzzle.x < 0 || pl->lastattack < 0 || attacks[pl->lastattack].gun != pl->gunselect) return;
|
||||
float dist = o.dist(d);
|
||||
o = pl->muzzle;
|
||||
if(dist <= 0) d = o;
|
||||
else
|
||||
{
|
||||
vecfromyawpitch(owner->yaw, owner->pitch, 1, 0, d);
|
||||
float newdist = raycube(owner->o, d, dist, RAY_CLIPMAT|RAY_ALPHAPOLY);
|
||||
d.mul(min(newdist, dist)).add(owner->o);
|
||||
}
|
||||
}
|
||||
|
||||
void dynlighttrack(physent *owner, vec &o, vec &hud)
|
||||
{
|
||||
if(owner->type!=ENT_PLAYER) return;
|
||||
gameent *pl = (gameent *)owner;
|
||||
if(pl->muzzle.x < 0 || pl->lastattack < 0 || attacks[pl->lastattack].gun != pl->gunselect) return;
|
||||
o = pl->muzzle;
|
||||
hud = owner == followingplayer(player1) ? vec(pl->o).add(vec(0, 0, 2)) : pl->muzzle;
|
||||
}
|
||||
|
||||
float intersectdist = 1e16f;
|
||||
|
||||
bool intersect(dynent *d, const vec &from, const vec &to, float margin, float &dist) // if lineseg hits entity bounding box
|
||||
{
|
||||
vec bottom(d->o), top(d->o);
|
||||
bottom.z -= d->eyeheight + margin;
|
||||
top.z += d->aboveeye + margin;
|
||||
return linecylinderintersect(from, to, bottom, top, d->radius + margin, dist);
|
||||
}
|
||||
|
||||
dynent *intersectclosest(const vec &from, const vec &to, gameent *at, float margin, float &bestdist)
|
||||
{
|
||||
dynent *best = NULL;
|
||||
bestdist = 1e16f;
|
||||
loopi(numdynents())
|
||||
{
|
||||
dynent *o = iterdynents(i);
|
||||
if(o==at || o->state!=CS_ALIVE) continue;
|
||||
float dist;
|
||||
if(!intersect(o, from, to, margin, dist)) continue;
|
||||
if(dist<bestdist)
|
||||
{
|
||||
best = o;
|
||||
bestdist = dist;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
void shorten(const vec &from, vec &target, float dist)
|
||||
{
|
||||
target.sub(from).mul(min(1.0f, dist)).add(from);
|
||||
}
|
||||
|
||||
void raydamage(vec &from, vec &to, gameent *d, int atk)
|
||||
{
|
||||
dynent *o;
|
||||
float dist;
|
||||
int maxrays = attacks[atk].rays, margin = attacks[atk].margin;
|
||||
if(attacks[atk].rays > 1)
|
||||
{
|
||||
dynent *hits[MAXRAYS];
|
||||
loopi(maxrays)
|
||||
{
|
||||
if((hits[i] = intersectclosest(from, rays[i], d, margin, dist)))
|
||||
{
|
||||
shorten(from, rays[i], dist);
|
||||
railhit(from, rays[i], false);
|
||||
}
|
||||
else railhit(from, rays[i]);
|
||||
}
|
||||
loopi(maxrays) if(hits[i])
|
||||
{
|
||||
o = hits[i];
|
||||
hits[i] = NULL;
|
||||
int numhits = 1;
|
||||
for(int j = i+1; j < maxrays; j++) if(hits[j] == o)
|
||||
{
|
||||
hits[j] = NULL;
|
||||
numhits++;
|
||||
}
|
||||
hitpush(numhits*attacks[atk].damage, o, d, from, to, atk, numhits);
|
||||
}
|
||||
}
|
||||
else if((o = intersectclosest(from, to, d, margin, dist)))
|
||||
{
|
||||
shorten(from, to, dist);
|
||||
railhit(from, to, false);
|
||||
hitpush(attacks[atk].damage, o, d, from, to, atk, 1);
|
||||
}
|
||||
else if(attacks[atk].action!=ACT_MELEE) railhit(from, to);
|
||||
}
|
||||
|
||||
void shoot(gameent *d, const vec &targ)
|
||||
{
|
||||
int prevaction = d->lastaction, attacktime = lastmillis-prevaction;
|
||||
if(attacktime<d->gunwait) return;
|
||||
d->gunwait = 0;
|
||||
if(!d->attacking) return;
|
||||
int gun = d->gunselect, act = d->attacking, atk = guns[gun].attacks[act];
|
||||
d->lastaction = lastmillis;
|
||||
d->lastattack = atk;
|
||||
if(!d->ammo[gun])
|
||||
{
|
||||
if(d==player1)
|
||||
{
|
||||
msgsound(S_NOAMMO, d);
|
||||
d->gunwait = 600;
|
||||
d->lastattack = -1;
|
||||
weaponswitch(d);
|
||||
}
|
||||
return;
|
||||
}
|
||||
d->ammo[gun] -= attacks[atk].use;
|
||||
|
||||
vec from = d->o, to = targ, dir = vec(to).sub(from).safenormalize();
|
||||
float dist = to.dist(from);
|
||||
if(!(d->physstate >= PHYS_SLOPE && d->crouching && d->crouched()))
|
||||
{
|
||||
vec kickback = vec(dir).mul(attacks[atk].kickamount*-2.5f);
|
||||
d->vel.add(kickback);
|
||||
}
|
||||
float shorten = attacks[atk].range && dist > attacks[atk].range ? attacks[atk].range : 0,
|
||||
barrier = raycube(d->o, dir, dist, RAY_CLIPMAT|RAY_ALPHAPOLY);
|
||||
if(barrier > 0 && barrier < dist && (!shorten || barrier < shorten))
|
||||
shorten = barrier;
|
||||
if(shorten) to = vec(dir).mul(shorten).add(from);
|
||||
|
||||
if(attacks[atk].rays > 1) createrays(atk, from, to);
|
||||
else if(attacks[atk].spread) offsetray(from, to, attacks[atk].spread, attacks[atk].range, to);
|
||||
|
||||
hits.setsize(0);
|
||||
|
||||
if(!attacks[atk].projspeed) raydamage(from, to, d, atk);
|
||||
|
||||
shoteffects(atk, from, to, d, true, 0, prevaction);
|
||||
|
||||
if(d==player1)
|
||||
{
|
||||
addmsg(N_SHOOT, "rci2i6iv", d, lastmillis-maptime, atk,
|
||||
(int)(from.x*DMF), (int)(from.y*DMF), (int)(from.z*DMF),
|
||||
(int)(to.x*DMF), (int)(to.y*DMF), (int)(to.z*DMF),
|
||||
hits.length(), hits.length()*sizeof(hitmsg)/sizeof(int), hits.getbuf());
|
||||
}
|
||||
|
||||
d->gunwait = attacks[atk].attackdelay;
|
||||
if(attacks[atk].action != ACT_MELEE) d->gunwait += int(d->gunwait*((101+rnd(111))/100.f));
|
||||
d->totalshots += attacks[atk].damage*attacks[atk].rays;
|
||||
}
|
||||
|
||||
void adddynlights()
|
||||
{
|
||||
loopv(projs)
|
||||
{
|
||||
projectile &p = projs[i];
|
||||
if(p.atk!=ATK_PULSE_SHOOT) continue;
|
||||
vec pos(p.o);
|
||||
pos.add(vec(p.offset).mul(p.offsetmillis/float(OFFSETMILLIS)));
|
||||
adddynlight(pos, 20, vec(0.25f, 0.75f, 1.0f));
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
static const char * const gibnames[3] = { "gibs/gib01", "gibs/gib02", "gibs/gib03" };
|
||||
static const char * const debrisnames[4] = { "debris/debris01", "debris/debris02", "debris/debris03", "debris/debris04" };
|
||||
#endif
|
||||
|
||||
void preloadbouncers()
|
||||
{
|
||||
#if 0
|
||||
loopi(sizeof(gibnames)/sizeof(gibnames[0])) preloadmodel(gibnames[i]);
|
||||
loopi(sizeof(debrisnames)/sizeof(debrisnames[0])) preloadmodel(debrisnames[i]);
|
||||
#endif
|
||||
}
|
||||
|
||||
void renderbouncers()
|
||||
{
|
||||
float yaw, pitch;
|
||||
loopv(bouncers)
|
||||
{
|
||||
bouncer &bnc = *bouncers[i];
|
||||
vec pos(bnc.o);
|
||||
pos.add(vec(bnc.offset).mul(bnc.offsetmillis/float(OFFSETMILLIS)));
|
||||
vec vel(bnc.vel);
|
||||
if(vel.magnitude() <= 25.0f) yaw = bnc.lastyaw;
|
||||
else
|
||||
{
|
||||
vectoyawpitch(vel, yaw, pitch);
|
||||
yaw += 90;
|
||||
bnc.lastyaw = yaw;
|
||||
}
|
||||
pitch = -bnc.roll;
|
||||
const char *mdl = NULL;
|
||||
int cull = MDL_CULL_VFC|MDL_CULL_DIST|MDL_CULL_OCCLUDED;
|
||||
float fade = 1;
|
||||
if(bnc.lifetime < 250) fade = bnc.lifetime/250.0f;
|
||||
switch(bnc.bouncetype)
|
||||
{
|
||||
#if 0
|
||||
case BNC_GIBS: mdl = gibnames[bnc.variant]; break;
|
||||
case BNC_DEBRIS: mdl = debrisnames[bnc.variant]; break;
|
||||
#endif
|
||||
default: continue;
|
||||
}
|
||||
rendermodel(mdl, ANIM_MAPMODEL|ANIM_LOOP, pos, yaw, pitch, 0, cull, NULL, NULL, 0, 0, fade);
|
||||
}
|
||||
}
|
||||
|
||||
void renderprojectiles()
|
||||
{
|
||||
}
|
||||
|
||||
void removeweapons(gameent *d)
|
||||
{
|
||||
removebouncers(d);
|
||||
removeprojectiles(d);
|
||||
}
|
||||
|
||||
void updateweapons(int curtime)
|
||||
{
|
||||
updateprojectiles(curtime);
|
||||
if(player1->clientnum>=0 && player1->state==CS_ALIVE) shoot(player1, worldpos); // only shoot when connected to server
|
||||
updatebouncers(curtime); // need to do this after the player shoots so bouncers don't end up inside player's BB next frame
|
||||
}
|
||||
};
|
||||
|
|
@ -74,7 +74,7 @@ namespace game
|
|||
extern int numdynents();
|
||||
extern void rendergame();
|
||||
extern void renderavatar();
|
||||
extern void renderplayerpreview(int model, int color, int team, int weap);
|
||||
extern void renderplayerpreview(int model, int color, int weap);
|
||||
extern int numanims();
|
||||
extern void findanims(const char *pattern, vector<int> &anims);
|
||||
extern void writegamedata(vector<char> &extras);
|
||||
|
|
Loading…
Reference in New Issue