From 1313a3e8e49ab409ec87344106d32ffb00b1054b Mon Sep 17 00:00:00 2001 From: Lee Salzman Date: Fri, 10 Aug 2007 06:48:36 +0000 Subject: [PATCH] *** empty log message *** git-svn-id: svn://svn.code.sf.net/p/actiongame/code/trunk/ac@1217 e016b1c5-ed68-44aa-a2f7-31cac88cffde --- source/src/Makefile | 1 - source/src/bot/ac_bot.cpp | 1 - source/src/bot/ac_bot_ai.cpp | 10 +- source/src/client.cpp | 78 +-- source/src/clientgame.cpp | 266 +++++----- source/src/clients2c.cpp | 325 ++++++------ source/src/cube.h | 2 +- source/src/editing.cpp | 3 +- source/src/entities.cpp | 139 +++-- source/src/entity.h | 199 ++++++-- source/src/geom.h | 30 ++ source/src/main.cpp | 12 +- source/src/physics.cpp | 4 +- source/src/protocol.cpp | 30 +- source/src/protocol.h | 17 +- source/src/protos.h | 45 +- source/src/rendergl.cpp | 33 +- source/src/renderhud.cpp | 53 +- source/src/rendermodel.cpp | 6 +- source/src/renderparticles.cpp | 7 - source/src/savegamedemo.cpp | 623 ----------------------- source/src/scoreboard.cpp | 4 +- source/src/server.cpp | 903 ++++++++++++++++++++++++++------- source/src/serverms.cpp | 12 +- source/src/sound.cpp | 1 - source/src/tools.h | 28 - source/src/weapon.cpp | 328 ++++++------ source/src/worldio.cpp | 1 - 28 files changed, 1586 insertions(+), 1575 deletions(-) delete mode 100644 source/src/savegamedemo.cpp diff --git a/source/src/Makefile b/source/src/Makefile index 87e2502e..b553a099 100644 --- a/source/src/Makefile +++ b/source/src/Makefile @@ -25,7 +25,6 @@ CLIENT_OBJS= \ renderparticles.o \ rendertext.o \ rndmap.o \ - savegamedemo.o \ scoreboard.o \ server.o \ serverbrowser.o \ diff --git a/source/src/bot/ac_bot.cpp b/source/src/bot/ac_bot.cpp index 155ee8d3..e6469117 100644 --- a/source/src/bot/ac_bot.cpp +++ b/source/src/bot/ac_bot.cpp @@ -14,7 +14,6 @@ #ifdef AC_CUBE -extern vector sents; extern int triggertime; extern itemstat itemstats[]; extern void spawnstate(playerent *d); diff --git a/source/src/bot/ac_bot_ai.cpp b/source/src/bot/ac_bot_ai.cpp index 57ace81b..8a509dda 100644 --- a/source/src/bot/ac_bot_ai.cpp +++ b/source/src/bot/ac_bot_ai.cpp @@ -131,27 +131,27 @@ entity *CACBot::SearchForEnts(bool bUseWPs, float flRange, float flMaxHeight) switch(e.type) { case I_CLIPS: - sMaxAmmo = itemstats[GUN_PISTOL].max; + sMaxAmmo = ammostats[GUN_PISTOL].max; bInteresting = (m_pMyEnt->ammo[GUN_PISTOL]ammo[GUN_PISTOL]; break; case I_AMMO: - sMaxAmmo = itemstats[m_pMyEnt->primary].max; + sMaxAmmo = ammostats[m_pMyEnt->primary].max; bInteresting = (m_pMyEnt->ammo[m_pMyEnt->primary]ammo[m_pMyEnt->primary]; break; case I_GRENADE: - sMaxAmmo = itemstats[GUN_GRENADE].max; + sMaxAmmo = ammostats[GUN_GRENADE].max; bInteresting = (m_pMyEnt->mag[GUN_GRENADE]mag[GUN_GRENADE]; break; case I_HEALTH: - sMaxAmmo = itemstats[7].max; //FIXME + sMaxAmmo = powerupstats[I_HEALTH-I_HEALTH].max; //FIXME bInteresting = (m_pMyEnt->health < sMaxAmmo); sAmmo = m_pMyEnt->health; break; case I_ARMOUR: - sMaxAmmo = itemstats[8].max; // FIXME + sMaxAmmo = powerupstats[I_ARMOUR-I_HEALTH].max; // FIXME bInteresting = (m_pMyEnt->armour < sMaxAmmo); sAmmo = m_pMyEnt->armour; break; diff --git a/source/src/client.cpp b/source/src/client.cpp index 2fa76dcb..e4516ec8 100644 --- a/source/src/client.cpp +++ b/source/src/client.cpp @@ -52,6 +52,7 @@ string clientpassword = ""; void abortconnect() { if(!connpeer) return; + clientpassword[0] = '\0'; if(connpeer->state!=ENET_PEER_STATE_DISCONNECTED) enet_peer_reset(connpeer); connpeer = NULL; if(curpeer) return; @@ -79,6 +80,7 @@ void connects(char *servername, char *password) if(!resolverwait(servername, &address)) { conoutf("\f3could not resolve server %s", servername); + clientpassword[0] = '\0'; return; } } @@ -97,7 +99,11 @@ void connects(char *servername, char *password) connmillis = lastmillis; connattempts = 0; } - else conoutf("\f3could not connect to server"); + else + { + conoutf("\f3could not connect to server"); + clientpassword[0] = '\0'; + } } void connectadmin(char *servername, char *password) @@ -134,15 +140,9 @@ void disconnect(int onlyclean, int async) conoutf("disconnected"); cleanup = true; } - else if(demoplayback) - { - // todo: additional cleanups - cleanup = true; - } if(cleanup) { - stop(); c2sinit = false; player1->clientnum = -1; player1->lifesequence = 0; @@ -199,7 +199,6 @@ vector messages; void addmsg(int type, const char *fmt, ...) { - if(demoplayback) return; static uchar buf[MAXTRANS]; ucharbuf p(buf, MAXTRANS); putint(p, type); @@ -212,6 +211,15 @@ void addmsg(int type, const char *fmt, ...) while(*fmt) switch(*fmt++) { case 'r': reliable = true; break; + case 'v': + { + int n = va_arg(args, int); + int *v = va_arg(args, int *); + loopi(n) putint(p, v[i]); + numi += n; + break; + } + case 'i': { int n = isdigit(*fmt) ? *fmt++-'0' : 1; @@ -223,8 +231,8 @@ void addmsg(int type, const char *fmt, ...) } va_end(args); } - int num = nums?0:numi; - if(num!=msgsizelookup(type)) { s_sprintfd(s)("inconsistant msg size for %d (%d != %d)", type, num, msgsizelookup(type)); fatal(s); } + int num = nums?0:numi, msgsize = msgsizelookup(type); + if(msgsize && num!=msgsize) { s_sprintfd(s)("inconsistant msg size for %d (%d != %d)", type, num, msgsize); fatal(s); } int len = p.length(); messages.add(len&0xFF); messages.add((len>>8)|(reliable ? 0x80 : 0)); @@ -252,7 +260,7 @@ void c2sinfo(playerent *d) // send update to the server if(d->clientnum<0) return; // we haven't had a welcome message from the server yet if(lastmillis-lastupdate<40) return; // don't update faster than 25fps - bool hasmsg = gun_changed || senditemstoserver || !c2sinit || messages.length() || lastmillis-lastping>250; + bool hasmsg = senditemstoserver || !c2sinit || messages.length() || lastmillis-lastping>250; // limit updates for dead players to the ping rate of 4fps, as above if(!hasmsg && laststate==CS_DEAD && player1->state==CS_DEAD) return; @@ -266,20 +274,18 @@ void c2sinfo(playerent *d) // send update to the server putuint(q, (int)(d->o.x*DMF)); // quantize coordinates to 1/16th of a cube, between 1 and 3 bytes putuint(q, (int)(d->o.y*DMF)); putuint(q, (int)(d->o.z*DMF)); - putuint(q, (int)(d->yaw*DAF)); - putint(q, (int)(d->pitch*DAF)); - putint(q, (int)(d->roll*DAF)); - putint(q, (int)(d->vel.x*DVF)); // quantize to 1/100, almost always 1 byte - putint(q, (int)(d->vel.y*DVF)); - putint(q, (int)(d->vel.z*DVF)); - // pack rest in 1 int: strafe:2, move:2, onfloor:1, state:3, onladder: 1 - putint(q, (d->strafe&3) | ((d->move&3)<<2) | (((int)d->onfloor)<<4) | ((editmode ? CS_EDITING : d->state)<<5) | (((int)d->onladder)<<8) ); + putuint(q, (int)d->yaw); + putint(q, (int)d->pitch); + putint(q, (int)d->roll); + putint(q, (int)(d->vel.x*DVELF)); + putint(q, (int)(d->vel.y*DVELF)); + putint(q, (int)(d->vel.z*DVELF)); + // pack rest in 1 int: strafe:2, move:2, onfloor:1, onladder: 1 + putint(q, (d->strafe&3) | ((d->move&3)<<2) | (((int)d->onfloor)<<4) | (((int)d->onladder)<<5) ); enet_packet_resize(packet, q.length()); - incomingdemodata(0, q.buf, q.length(), true); sendpackettoserv(0, packet); - bool serveriteminitdone = false; if(hasmsg) { packet = enet_packet_create (NULL, MAXTRANS, 0); @@ -289,24 +295,10 @@ void c2sinfo(playerent *d) // send update to the server { packet->flags = ENET_PACKET_FLAG_RELIABLE; c2sinit = true; - if(clientpassword[0]) - { - putint(p, SV_PWD); - sendstring(clientpassword, p); - clientpassword[0] = 0; - } putint(p, SV_INITC2S); sendstring(player1->name, p); sendstring(player1->team, p); putint(p, player1->skin); - putint(p, player1->lifesequence); - } - if(gun_changed) - { - packet->flags = ENET_PACKET_FLAG_RELIABLE; - putint(p, SV_WEAPCHANGE); - putint(p, player1->gunselect); - gun_changed = false; } if(senditemstoserver) { @@ -315,7 +307,6 @@ void c2sinfo(playerent *d) // send update to the server if(!m_noitems) putitems(p); putint(p, -1); senditemstoserver = false; - serveriteminitdone = true; } int i = 0; while(i < messages.length()) // send messages collected during the previous frames @@ -337,13 +328,23 @@ void c2sinfo(playerent *d) // send update to the server else { enet_packet_resize(packet, p.length()); - incomingdemodata(42, p.buf, p.length()); sendpackettoserv(1, packet); } } if(clienthost) enet_host_flush(clienthost); lastupdate = lastmillis; - if(serveriteminitdone) loadgamerest(); // hack +} + +void sendintro() +{ + ENetPacket *packet = enet_packet_create(NULL, MAXTRANS, 0); + ucharbuf p(packet->data, packet->dataLength); + putint(p, SV_CONNECT); + sendstring(clientpassword, p); + clientpassword[0] = '\0'; + putint(p, player1->nextprimary); + enet_packet_resize(packet, p.length()); + sendpackettoserv(1, packet); } void gets2c() // get updates from the server @@ -374,6 +375,7 @@ void gets2c() // get updates from the server throttle(); if(rate) setrate(rate); if(editmode) toggleedit(); + sendintro(); break; case ENET_EVENT_TYPE_RECEIVE: diff --git a/source/src/clientgame.cpp b/source/src/clientgame.cpp index 58ab59a3..ced40151 100644 --- a/source/src/clientgame.cpp +++ b/source/src/clientgame.cpp @@ -84,12 +84,12 @@ int smallerteam() return teamsize[0] < teamsize[1] ? 0 : 1; } -void changeteam(int team) // force team and respawn +void changeteam(int team, bool forced) // force team and respawn { c2sinit = false; if(m_ctf) tryflagdrop(NULL); filtertext(player1->team, team_string(team), false, MAXTEAMLEN); - deathstate(player1); + if(!forced) deathstate(player1); } void newteam(char *name) @@ -144,16 +144,15 @@ void deathstate(playerent *pl) } void spawnstate(playerent *d) // reset player state not persistent accross spawns -{ +{ d->respawn(); - if(d==player1) + d->spawnstate(gamemode); + //d->lastaction = lastmillis; + if(d==player1) { - gun_changed = true; if(player1->skin!=player1->nextskin) setskin(player1, player1->nextskin); setscope(false); } - equip(d); - if(m_osok) d->health = 1; } playerent *newplayerent() // create a new blank player @@ -191,12 +190,14 @@ void freebotent(botent *d) } } - - void respawnself() { - spawnplayer(player1); - showscores(false); + if(m_mp(gamemode)) addmsg(SV_TRYSPAWN, "r"); + else + { + spawnplayer(player1); + showscores(false); + } } void respawn() @@ -211,21 +212,6 @@ void respawn() } } -void arenacount(playerent *d, int &alive, int &dead, char *&lastteam, char *&lastname, bool &oneteam) -{ - if(d->state!=CS_DEAD) - { - alive++; - if(lastteam && strcmp(lastteam, d->team)) oneteam = false; - lastteam = d->team; - lastname = d->name; - } - else - { - dead++; - } -} - void checkakimbo() { if(player1->akimbo && player1->akimbomillis && player1->akimbomillis<=lastmillis) @@ -240,11 +226,11 @@ void checkakimbo() void zapplayer(playerent *&d) { + removebounceents(d); + removeprojectiles(d); DELETEP(d); } -extern int democlientnum; - void otherplayers() { loopv(players) if(players[i] && players[i]->type==ENT_PLAYER && players[i]->state==CS_ALIVE) @@ -256,7 +242,7 @@ void otherplayers() continue; } else if(!lagtime || intermission) continue; - if(!demoplayback || i!=democlientnum) moveplayer(players[i], 2, false); // use physics to extrapolate player position + moveplayer(players[i], 2, false); // use physics to extrapolate player position } } @@ -289,37 +275,30 @@ void updateworld(int curtime, int lastmillis) // main game update loop //if(m_arena) arenarespawn(); //arenarespawn(); moveprojectiles((float)curtime); - demoplaybackstep(); - if(!demoplayback) - { - if(getclientnum()>=0) shoot(player1, worldpos); // only shoot when connected to server - gets2c(); // do this first, so we have most accurate information when our player moves - } + if(getclientnum()>=0) shoot(player1, worldpos); // only shoot when connected to server + gets2c(); // do this first, so we have most accurate information when our player moves movebounceents(); otherplayers(); - if(!demoplayback) + //monsterthink(); + + // Added by Rick: let bots think + if(m_botmode) BotManager.Think(); + + //put game mode extra call here + if(player1->state==CS_DEAD) { - //monsterthink(); - - // Added by Rick: let bots think - if(m_botmode) BotManager.Think(); - - //put game mode extra call here - if(player1->state==CS_DEAD) + if(lastmillis-player1->lastpain<2000) { - if(lastmillis-player1->lastpain<2000) - { - player1->move = player1->strafe = 0; - moveplayer(player1, 10, false); - } + player1->move = player1->strafe = 0; + moveplayer(player1, 10, false); } - else if(!intermission) - { - moveplayer(player1, 20, true); - checkitems(player1); - } - c2sinfo(player1); // do this last, to reduce the effective frame lag } + else if(!intermission) + { + moveplayer(player1, 20, true); + checkitems(player1); + } + c2sinfo(player1); // do this last, to reduce the effective frame lag } #define SECURESPAWNDIST 15 @@ -341,39 +320,35 @@ float nearestenemy(vec place, char *team) else return nearestenemydist; } -int findplayerstart(playerent *d) +void findplayerstart(playerent *d) { int r = fixspawn-->0 ? 4 : rnd(10)+1; - - if(m_teammode) loopi(r) spawncycle = findentity(PLAYERSTART, spawncycle+1, team_int(d->team)); - else if(m_arena) loopi(r) spawncycle = findentity(PLAYERSTART, spawncycle+1, 100); + entity *e = NULL; + if(m_teammode || m_arena) + { + int type = m_teammode ? team_int(d->team) : 100; + loopi(r) spawncycle = findentity(PLAYERSTART, spawncycle+1, type); + if(spawncycle >= 0) e = &ents[spawncycle]; + } else { - int bestent = -1; float bestdist = -1; loopi(r) { spawncycle = findentity(PLAYERSTART, spawncycle+1); - if(spawncycle < 0 || spawncycle >= ents.length()) continue; + if(spawncycle < 0) continue; float dist = nearestenemy(vec(ents[spawncycle].x, ents[spawncycle].y, ents[spawncycle].z), d->team); - if(bestent < 0 || dist < 0 || (bestdist >= 0 && dist > bestdist)) { bestent = spawncycle; bestdist = dist; } + if(!e || dist < 0 || (bestdist >= 0 && dist > bestdist)) { e = &ents[spawncycle]; bestdist = dist; } } - - return bestent; } - return spawncycle; -} -void spawnplayer(playerent *d) // place at random spawn -{ - int e = findplayerstart(d); - if(e!=-1) + if(e) { - d->o.x = ents[e].x; - d->o.y = ents[e].y; - d->o.z = ents[e].z; - d->yaw = ents[e].attr1; + d->o.x = e->x; + d->o.y = e->y; + d->o.z = e->z; + d->yaw = e->attr1; d->pitch = 0; d->roll = 0; } @@ -384,33 +359,43 @@ void spawnplayer(playerent *d) // place at random spawn } entinmap(d); - spawnstate(d); - d->state = CS_ALIVE; +} + +void spawnplayer(playerent *d) +{ + findplayerstart(d); + d->respawn(); + d->spawnstate(gamemode); + d->state = d==player1 && editmode ? CS_EDITING : CS_ALIVE; } void showteamkill() { player1->lastteamkill = lastmillis; } // damage arriving from the network, monsters, yourself, all ends up here. -void dodamage(int damage, int actor, playerent *act, bool gib, playerent *pl) +void dodamage(int damage, playerent *pl, playerent *actor, bool gib, bool local) { - if(!act) return; - if(pl->state!=CS_ALIVE || editmode || intermission) return; + if(pl->state!=CS_ALIVE || intermission) return; + + pl->lastpain = lastmillis; + if(local) damage = pl->dodamage(damage); + else if(actor==player1) return; + if(pl==player1) { damageblend(damage); - demoblend(damage); + pl->damageroll(damage); } - pl->lastpain = lastmillis; - int ad = damage*30/100; // let armour absorb when possible - if(ad>pl->armour) ad = pl->armour; - pl->armour -= ad; - damage -= ad; - float droll = damage/0.5f; - pl->roll += pl->roll>0 ? droll : (pl->roll<0 ? -droll : (rnd(2) ? droll : -droll)); // give player a kick depending on amount of damage + else damageeffect(damage, pl); + + if(pl->health<=0) { if(local) dokill(pl, actor, gib); } + else if(pl==player1) playsound(S_PAIN6); + else playsound(S_PAIN1+rnd(5), &pl->o); + +#if 0 if((pl->health -= damage)<=0) { - s_sprintfd(death)("%s", gib ? "gibbed" : "fragged"); + s_sprintfd(death)("%s", gib ? "gibbed" : "fragged"); if(pl->type==ENT_BOT) { if(pl==act) @@ -422,12 +407,12 @@ void dodamage(int damage, int actor, playerent *act, bool gib, playerent *pl) { --act->frags; conoutf("\f2%s %s %s teammate (%s)", act==player1 ? "you" : colorname(act, 0), death, act==player1 ? "a" : "his", colorname(pl, 1)); - if(act==player1) showteamkill(); + if(act==player1) showteamkill(); } else { - act->frags += gib ? 2 : 1; - conoutf("\f2%s %s %s", act==player1 ? "you" : colorname(act, 0), death, colorname(pl, 1)); + act->frags += gib ? 2 : 1; + conoutf("\f2%s %s %s", act==player1 ? "you" : colorname(act, 0), death, colorname(pl, 1)); } } else if(act==pl) @@ -444,21 +429,73 @@ void dodamage(int damage, int actor, playerent *act, bool gib, playerent *pl) if(pl==player1) { showscores(true); - setscope(false); + setscope(false); addmsg(gib ? SV_GIBDIED : SV_DIED, "ri", actor); - if(m_ctf) tryflagdrop(act && isteam(act->team, player1->team)); + if(m_ctf) tryflagdrop(act && isteam(act->team, player1->team)); } - deathstate(pl); - pl->lifesequence++; - playsound(S_DIE1+rnd(2), pl!=player1 ? &pl->o : NULL); - if(act && act->gunselect == GUN_SNIPER && gib) playsound(S_HEADSHOT); - if(gib) addgib(pl); - if(pl!=player1 || act->type==ENT_BOT) act->frags += gib ? 2 : 1; + deathstate(pl); + pl->lifesequence++; + playsound(S_DIE1+rnd(2), pl!=player1 ? &pl->o : NULL); + if(act && act->gunselect == GUN_SNIPER && gib) playsound(S_HEADSHOT); + if(gib) addgib(pl); + if(pl!=player1 || act->type==ENT_BOT) act->frags += gib ? 2 : 1; } else { playsound(S_PAIN6, pl!=player1 ? &pl->o : NULL); } +#endif +} + +void dokill(playerent *pl, playerent *act, bool gib) +{ + if(pl->state!=CS_ALIVE || intermission) return; + + string pname, aname, death; + s_strcpy(pname, pl==player1 ? "you" : colorname(pl)); + s_strcpy(aname, act==player1 ? "you" : colorname(act)); + s_strcpy(death, gib ? "gibbed" : "fragged"); + + if(pl==act) + conoutf("\f2%s suicided%s", pname, pl==player1 ? "!" : ""); + else if(isteam(pl->team, act->team)) + { + if(pl==player1) conoutf("\f2you got %s by a teammate (%s)", death, aname); + else conoutf("\f2%s %s %s teammate (%s)", aname, death, act==player1 ? "a" : "his", pname); + if(act==player1) showteamkill(); + } + else + { + if(pl==player1) conoutf("\f2you got %s by %s", death, aname); + else conoutf("\f2%s %s %s", aname, death, pname); + } + + pl->state = CS_DEAD; + pl->lastaction = lastmillis; + pl->pitch = 0; + pl->roll = 60; + pl->strafe = 0; + pl->attacking = false; + pl->lifesequence++; + playsound(S_DIE1+rnd(2), pl!=player1 ? &pl->o : NULL); + if(pl==player1) + { + showscores(true); + setscope(false); + dblend = 0; + if(pl->inhandnade) thrownade(pl, vec(0,0,0), pl->inhandnade); + if(m_ctf) tryflagdrop(pl!=act && isteam(act->team, pl->team)); + } + if(gib) + { + if(pl!=act && act->gunselect == GUN_SNIPER) playsound(S_HEADSHOT); + addgib(pl); + } + if(!m_mp(gamemode)) + { + if(pl==act || isteam(pl->team, act->team)) act->frags--; + else act->frags += gib ? 2 : 1; + } } VAR(minutesremaining, 1, 0, 0); @@ -538,6 +575,8 @@ void preparectf(bool cleanonly=false) } } +int suicided = -1; + void startmap(char *name) // called just after a map load { clearminimap(); @@ -549,12 +588,15 @@ void startmap(char *name) // called just after a map load else kickallbots(); // End add by Rick projreset(); + clearbounceents(); resetspawns(); if(m_ctf) preparectf(); particlereset(); + suicided = -1; spawncycle = -1; - spawnplayer(player1); - player1->frags = player1->flagscore = player1->lifesequence = 0; + if(!m_mp(gamemode)) spawnplayer(player1); + else findplayerstart(player1); + player1->frags = player1->flagscore = 0; loopv(players) if(players[i]) players[i]->frags = players[i]->flagscore = players[i]->lifesequence = 0; s_strcpy(clientmap, name); if(editmode) toggleedit(); @@ -565,16 +607,19 @@ void startmap(char *name) // called just after a map load intermission = false; minutesremaining = -1; if(*clientmap) conoutf("game mode is \"%s\"", modestr(gamemode)); - clearbounceents(); } COMMANDN(map, changemap, ARG_1STR); void suicide() { - if(player1->state==CS_DEAD) return; - dodamage(1000, -1, player1); - demodamage(1000, player1->o); + if(player1->state!=CS_ALIVE) return; + if(!m_mp(gamemode)) dokill(player1, player1); + else if(suicided!=player1->lifesequence) + { + addmsg(SV_SUICIDE, "r"); + suicided = player1->lifesequence; + } } COMMAND(suicide, ARG_NONE); @@ -585,15 +630,8 @@ void flagmsg(int flag, int action) { flaginfo &f = flaginfos[flag]; if(!f.actor || !f.ack) return; - bool own = flag == team_int(player1->team), firstperson = false; - const char *teamstr; - if(demoplayback && !localdemoplayer1st()) - teamstr = flag ? "the RVSF" : "the CLA"; - else - { - teamstr = flag == team_int(player1->team) ? "your" : "the enemy"; - firstperson = f.actor == player1; - } + bool own = flag == team_int(player1->team), firstperson = f.actor == player1; + const char *teamstr = flag == team_int(player1->team) ? "your" : "the enemy"; switch(action) { @@ -626,8 +664,6 @@ void flagmsg(int flag, int action) conoutf("\f2you scored"); addmsg(SV_FLAGS, "ri", ++player1->flagscore); } - else if(demoplayback) - conoutf("\f2%s scored for team %s", colorname(f.actor), f.actor->team); else conoutf("\f2%s scored for %s team", colorname(f.actor), (own ? "the enemy" : "your")); break; } diff --git a/source/src/clients2c.cpp b/source/src/clients2c.cpp index c1b62580..31b2b84f 100644 --- a/source/src/clients2c.cpp +++ b/source/src/clients2c.cpp @@ -3,7 +3,6 @@ #include "cube.h" #include "bot/bot.h" -extern int democlientnum; extern bool c2sinit, senditemstoserver; extern string clientpassword; @@ -21,7 +20,13 @@ void changemapserv(char *name, int mode) // forced map change from the se void changemap(char *name) // request map change, server may ignore { - addmsg(SV_MAPCHANGE, "rsi", name, nextmode); + ENetPacket *packet = enet_packet_create(NULL, MAXTRANS, ENET_PACKET_FLAG_RELIABLE); + ucharbuf p(packet->data, packet->dataLength); + putint(p, SV_MAPCHANGE); + sendstring(name, p); + putint(p, nextmode); + enet_packet_resize(packet, p.length()); + sendpackettoserv(1, packet); } // update the position of other clients in the game in our world @@ -68,12 +73,12 @@ void parsepositions(ucharbuf &p) o.x = getuint(p)/DMF; o.y = getuint(p)/DMF; o.z = getuint(p)/DMF; - yaw = getuint(p)/DAF; - pitch = getint(p)/DAF; - roll = getint(p)/DAF; - vel.x = getint(p)/DVF; - vel.y = getint(p)/DVF; - vel.z = getint(p)/DVF; + yaw = (float)getuint(p); + pitch = (float)getint(p); + roll = (float)getint(p); + vel.x = getint(p)/DVELF; + vel.y = getint(p)/DVELF; + vel.z = getint(p)/DVELF; int f = getint(p); playerent *d = getclient(cn); if(!d) continue; @@ -88,13 +93,10 @@ void parsepositions(ucharbuf &p) f >>= 2; d->onfloor = f&1; f >>= 1; - int oldstate = d->state, state = f&7; - if(state==CS_DEAD && oldstate!=CS_DEAD) d->lastpain = lastmillis; - d->state = state; - f >>= 3; d->onladder = f&1; - if(!demoplayback) updatepos(d); - if(oldstate!=CS_DEAD && d->state!=CS_DEAD) updatelagtime(d); + updatepos(d); + updatelagtime(d); + if(d->state==CS_LAGGED) d->state = CS_ALIVE; break; } @@ -108,7 +110,7 @@ void parsemessages(int cn, playerent *d, ucharbuf &p) { static char text[MAXTRANS]; int type, joining = 0; - bool mapchanged = false, c2si = false, gib = false; + bool mapchanged = false; while(p.remaining()) switch(type = getint(p)) { @@ -137,7 +139,7 @@ void parsemessages(int cn, playerent *d, ucharbuf &p) } case SV_SOUND: - playsound(getint(p), !d || (localdemoplayer1st() && cn==democlientnum) ? NULL : &d->o); + playsound(getint(p), !d ? NULL : &d->o); break; @@ -211,14 +213,11 @@ void parsemessages(int cn, playerent *d, ucharbuf &p) { c2sinit = false; // send new players my info again conoutf("connected: %s", colorname(d, 0, text)); - gun_changed = true; } s_strncpy(d->name, text, MAXNAMELEN+1); getstring(text, p); filtertext(d->team, text, false, MAXTEAMLEN); setskin(d, getint(p)); - d->lifesequence = getint(p); - c2si = true; if(m_ctf) loopi(2) { flaginfo &f = flaginfos[i]; @@ -237,143 +236,152 @@ void parsemessages(int cn, playerent *d, ucharbuf &p) break; } - case SV_SHOT: + case SV_EDITMODE: { - if(!d) return; - int gun = getint(p); - vec s, e; - s.x = getint(p)/DMF; - s.y = getint(p)/DMF; - s.z = getint(p)/DMF; - e.x = getint(p)/DMF; - e.y = getint(p)/DMF; - e.z = getint(p)/DMF; - if(gun==GUN_SHOTGUN) createrays(s, e); - d->lastaction = lastmillis; - d->lastattackgun = gun; - shootv(gun, s, e, d, false, getint(p)); + int val = getint(p); + if(!d) break; + if(val) d->state = CS_EDITING; + else d->state = CS_ALIVE; break; } - case SV_GIBDAMAGE: - gib = true; - case SV_DAMAGE: - { - if(!d) return; - int target = getint(p); - int damage = getint(p); - int ls = getint(p); - if(!demoplayback && target==getclientnum()) - { - if(ls==player1->lifesequence) - { - particle_splash(3, damage/10, 1000, player1->o); - dodamage(damage, cn, d, gib); - } - } - else + + case SV_SPAWN: + { + int ls = getint(p), gunselect = getint(p); + if(!d) break; + d->lifesequence = ls; + d->gunselect = gunselect; + d->state = CS_ALIVE; + break; + } + + case SV_SPAWNSTATE: + { + if(editmode) toggleedit(); + player1->respawn(); + player1->lifesequence = getint(p); + player1->health = getint(p); + player1->armour = getint(p); + player1->primary = getint(p); + player1->gunselect = getint(p); + loopi(NUMGUNS) player1->ammo[i] = getint(p); + loopi(NUMGUNS) player1->mag[i] = getint(p); + player1->state = CS_ALIVE; + findplayerstart(player1); + showscores(false); + setscope(false); + if(player1->skin!=player1->nextskin) setskin(player1, player1->nextskin); + if(m_arena) { - playerent *victim = getclient(target); - if(victim) - { - playsound(S_PAIN1+rnd(5), &victim->o); - particle_splash(3, damage/10, 1000, victim->o); - victim->lastpain = lastmillis; - } + conoutf("new round starting... fight!"); + if(m_botmode) BotManager.RespawnBots(); } - gib = false; - break; - } - - case SV_GIBDIED: - gib = true; + addmsg(SV_SPAWN, "rii", player1->lifesequence, player1->gunselect); + break; + } + + case SV_SHOTFX: + { + int scn = getint(p), gun = getint(p); + vec from, to; + loopk(3) from[k] = getint(p)/DMF; + loopk(3) to[k] = getint(p)/DMF; + playerent *s = getclient(scn); + if(!s || gun==GUN_GRENADE) break; + if(gun==GUN_SHOTGUN) createrays(from, to); + s->lastaction = lastmillis; + s->lastattackgun = s->gunselect; + shootv(gun, from, to, s, false, 0); + break; + } + + case SV_THROWNADE: + { + vec from, to; + loopk(3) from[k] = getint(p)/DMF; + loopk(3) to[k] = getint(p)/DMF; + int nademillis = getint(p); + if(!d) break; + d->lastaction = lastmillis; + d->lastattackgun = GUN_GRENADE; + shootv(GUN_GRENADE, from, to, d, false, nademillis); + break; + } + + case SV_GIBDAMAGE: + case SV_DAMAGE: + { + int tcn = getint(p), + acn = getint(p), + damage = getint(p), + armour = getint(p), + health = getint(p); + playerent *target = tcn==getclientnum() ? player1 : getclient(tcn), + *actor = acn==getclientnum() ? player1 : getclient(acn); + if(!target || !actor) break; + if(target==player1) + { + target->armour = armour; + target->health = health; + } + dodamage(damage, target, actor, type==SV_GIBDAMAGE, false); + break; + } + + case SV_HITPUSH: + { + int gun = getint(p), damage = getint(p); + vec dir; + loopk(3) dir[k] = getint(p)/DNF; + player1->hitpush(damage, dir, NULL, gun); + break; + } + + case SV_GIBDIED: case SV_DIED: { - if(!d) return; - int actor = getint(p); - playerent *act = NULL; - s_sprintfd(death)("%s", gib ? "gibbed" : "fragged"); - - if(actor==cn) - { - conoutf("\f2%s suicided", colorname(d)); - act = d; - } - else if(actor==getclientnum() && (!demoplayback || localdemoplayer1st())) - { - act = player1; - int frags; - if(isteam(player1->team, d->team)) - { - frags = -1; - conoutf("\f2you %s a teammate (%s)", death, colorname(d)); - extern void showteamkill(); - showteamkill(); - } - else - { - frags = gib ? 2 : 1; - conoutf("\f2you %s %s", death, colorname(d)); - } - addmsg(SV_FRAGS, "ri", player1->frags += frags); - if(gib) addgib(d); - } - else - { - playerent *a = getclient(actor); - if(a) - { - act = a; - if(isteam(a->team, d->team)) - { - conoutf("\f2%s %s his teammate (%s)", colorname(a, 0), death, colorname(d, 1)); - } - else - { - conoutf("\f2%s %s %s", colorname(a, 0), death, colorname(d, 1)); - } - if(gib) addgib(d); - } - } - playsound(S_DIE1+rnd(2), &d->o); - if(act && act->gunselect == GUN_SNIPER && gib) playsound(S_HEADSHOT); - if(!c2si) d->lifesequence++; - gib = false; + int vcn = getint(p), acn = getint(p), frags = getint(p); + playerent *victim = vcn==getclientnum() ? player1 : getclient(vcn), + *actor = acn==getclientnum() ? player1 : getclient(acn); + if(!actor) break; + actor->frags = frags; + if(!victim) break; + dokill(victim, actor, type==SV_GIBDIED); break; } - - case SV_FRAGS: - if(!d) return; - d->frags = getint(p); - break; case SV_RESUME: { - int cn = getint(p), frags = getint(p), flags = getint(p), lseq = getint(p); - playerent *d = cn==getclientnum() ? player1 : newclient(cn); - if(d) + for(;;) { + int cn = getint(p); + if(cn<0) break; + int state = getint(p), lifesequence = getint(p), gunselect = getint(p), flagscore = getint(p), frags = getint(p); + playerent *d = (cn == getclientnum() ? player1 : newclient(cn)); + if(!d) continue; + if(d!=player1) d->state = state; + d->lifesequence = lifesequence; + d->gunselect = gunselect; + d->flagscore = flagscore; d->frags = frags; - d->flagscore = flags; - d->lifesequence = lseq; } break; } - case SV_ITEMPICKUP: - setspawn(getint(p), false); - getint(p); - break; - case SV_ITEMSPAWN: { - uint i = getint(p); + int i = getint(p); setspawn(i, true); break; } - case SV_ITEMACC: // server acknowledges that I picked up this item - realpickup(getint(p), player1); + case SV_ITEMACC: + { + int i = getint(p), cn = getint(p); + playerent *d = cn==getclientnum() ? player1 : getclient(cn); + pickupeffects(i, d); break; + } case SV_EDITH: // coop editing messages, should be extended to include all possible editing ops case SV_EDITT: @@ -419,7 +427,7 @@ void parsemessages(int cn, playerent *d, ucharbuf &p) case SV_PONG: { int millis = getint(p); - if(!demoplayback) addmsg(SV_CLIENTPING, "i", player1->ping = (player1->ping*5+lastmillis-millis)/6); + addmsg(SV_CLIENTPING, "i", player1->ping = (player1->ping*5+lastmillis-millis)/6); break; } @@ -512,22 +520,30 @@ void parsemessages(int cn, playerent *d, ucharbuf &p) case SV_ARENAWIN: { - int alive = getint(p); - getstring(text, p); + int acn = getint(p); + playerent *alive = acn<0 ? NULL : (acn==getclientnum() ? player1 : getclient(acn)); conoutf("arena round is over! next round in 5 seconds..."); - if(!alive) conoutf("everyone died!"); - else if(m_botmode && player1->state==CS_DEAD) conoutf("the bots have won the round!"); - else if(m_teammode) conoutf("team %s has won the round!", text); - else conoutf("%s is the survivor!", text); + if(m_botmode && acn==-2) conoutf("the bots have won the round!"); + else if(!alive) conoutf("everyone died!"); + else if(m_teammode) conoutf("team %s has won the round!", alive->team); + else if(alive==player1) conoutf("you are the survivor!"); + else conoutf("%s is the survivor!", colorname(alive)); break; } - case SV_ARENASPAWN: - conoutf("new round starting... fight!"); - respawnself(); - if(m_botmode) BotManager.RespawnBots(); - clearbounceents(); + case SV_FORCEDEATH: + { + int cn = getint(p); + playerent *d = cn==getclientnum() ? player1 : newclient(cn); + if(!d) break; + if(d==player1) + { + if(editmode) toggleedit(); + showscores(true); + } + d->state = CS_DEAD; break; + } case SV_SERVOPINFO: { @@ -591,26 +607,11 @@ void parsemessages(int cn, playerent *d, ucharbuf &p) case SV_FORCETEAM: { - changeteam(getint(p)); - if(!m_arena || joining<=2) spawnplayer(player1); + changeteam(getint(p), true); break; } - /* demo recording compat */ - case SV_PING: - getint(p); - break; - default: - if(demoplayback) // filter demo messages - { - int size = msgsizelookup(type); - if(size>0) - { - loopi(size-1) getint(p); - break; - } - } neterr("type"); return; } @@ -618,8 +619,6 @@ void parsemessages(int cn, playerent *d, ucharbuf &p) void localservertoclient(int chan, uchar *buf, int len) // processes any updates from the server { - incomingdemodata(chan, buf, len); - ucharbuf p(buf, len); switch(chan) @@ -627,9 +626,5 @@ void localservertoclient(int chan, uchar *buf, int len) // processes any updat case 0: parsepositions(p); break; case 1: case 2: parsemessages(-1, NULL, p); break; - case 42: // player1 demo data only - extern int democlientnum; - parsemessages(democlientnum, getclient(democlientnum), p); - break; } } diff --git a/source/src/cube.h b/source/src/cube.h index 1908bc69..8aa0327d 100644 --- a/source/src/cube.h +++ b/source/src/cube.h @@ -55,8 +55,8 @@ #include "world.h" #include "model.h" #include "protocol.h" -#include "entity.h" #include "sound.h" +#include "entity.h" #include "command.h" typedef vector cvector; diff --git a/source/src/editing.cpp b/source/src/editing.cpp index 557ad909..c21973b5 100644 --- a/source/src/editing.cpp +++ b/source/src/editing.cpp @@ -49,7 +49,8 @@ void toggleedit() } keyrepeat(editmode); selset = false; - editing = editmode; + editing = editmode ? 1 : 0; + addmsg(SV_EDITMODE, "ri", editing); } COMMANDN(edittoggle, toggleedit, ARG_NONE); diff --git a/source/src/entities.cpp b/source/src/entities.cpp index f0e755d7..5f8011ac 100644 --- a/source/src/entities.cpp +++ b/source/src/entities.cpp @@ -64,64 +64,82 @@ void renderentities() } } -itemstat itemstats[] = -{ - {1, 1, 1, S_ITEMAMMO}, //knife dummy - {16, 32, 72, S_ITEMAMMO}, //pistol - {14, 28, 21, S_ITEMAMMO}, //shotgun - {60, 90, 90, S_ITEMAMMO}, //subgun - {10, 20, 15, S_ITEMAMMO}, //sniper - {30, 60, 60, S_ITEMAMMO}, //assault - {2, 0, 2, S_ITEMAMMO}, //grenade - {33, 100, 100, S_ITEMHEALTH}, //health - {50, 100, 100, S_ITEMARMOUR}, //armour - {16, 0, 72, S_ITEMPUP}, //powerup -}; - // 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 equipitem(playerent *d, int i, int &v, int t) +void pickupeffects(int n, playerent *d) { - itemstat &is = itemstats[t]; - ents[i].spawned = false; - v += is.add; - if(v>is.max) v = is.max; - if(d==player1) playsoundc(is.sound); - else playsound(is.sound, &d->o); -} - -void realpickup(int n, playerent *d) -{ - switch(ents[n].type) + entity &e = ents[n]; + e.spawned = false; + if(d!=player1 && d->type!=ENT_BOT) return; + d->pickup(e.type); + itemstat &is = d->itemstats(e.type); + if(&is) { - case I_CLIPS: - equipitem(d, n, d->ammo[1], 1); - break; - case I_AMMO: - equipitem(d, n, d->ammo[d->primary], d->primary); - break; - case I_GRENADE: - equipitem(d, n, d->mag[6], 6); + if(d==player1) playsoundc(is.sound); + else playsound(is.sound, &d->o); + } + switch(e.type) + { + case I_GRENADE: d->thrownademillis = 0; break; - case I_HEALTH: - equipitem(d, n, d->health, 7); - break; - case I_ARMOUR: - equipitem(d, n, d->armour, 8); - break; + case I_AKIMBO: d->akimbomillis = lastmillis+30000; - d->mag[GUN_PISTOL] = 16; - equipitem(d, n, d->ammo[1], 9); - if(d==player1 && d->gunselect!=GUN_SNIPER && !d->inhandnade) weaponswitch(GUN_PISTOL); + if(d==player1) + { + if(d->gunselect!=GUN_SNIPER && !d->inhandnade) weaponswitch(GUN_PISTOL); + addmsg(SV_AKIMBO, "ri", lastmillis); + } break; } } // these functions are called when the client touches the item +// these functions are called when the client touches the item + +void trypickup(int n, playerent *d) +{ + entity &e = ents[n]; + switch(e.type) + { + default: + if(d->canpickup(e.type)) + { + if(d->type==ENT_PLAYER) addmsg(SV_ITEMPICKUP, "ri", n); + else if(d->type==ENT_BOT && serverpickup(n, -1)) pickupeffects(n, d); + e.spawned = false; + } + break; + + case LADDER: + d->onladder = true; + break; + + case CTF_FLAG: + { + if(d==player1) + { + int flag = e.attr2; + flaginfo &f = flaginfos[flag]; + flaginfo &of = flaginfos[team_opposite(flag)]; + if(f.state == CTFF_STOLEN) break; + + if(flag == team_int(d->team)) // its the own flag + { + if(f.state == CTFF_DROPPED) flagreturn(); + else if(f.state == CTFF_INBASE && of.state == CTFF_STOLEN && of.actor == d && of.ack) flagscore(); + } + else flagpickup(); + } + break; + } + } +} + +#if 0 void additem(playerent *d, int i, int &v, int spawnsec, int t) { if(vo.z); float dist1 = d->o.dist(v); float dist2 = d->o.z - (S(e.x, e.y)->floor+d->eyeheight); - if(dist1<1.5f && dist2floor+d->eyeheight); - if(d->o.dist(v)<2.5f) pickup(i, d); + if(d->o.dist(v)<2.5f) trypickup(i, d); } } void putitems(ucharbuf &p) // puts items in network stream and also spawns them locally { - loopv(ents) if(isitem(ents[i].type) || ents[i].type==CARROT || (multiplayer(false) && gamespeed!=100 && (i=-1))) + loopv(ents) if(isitem(ents[i].type) || (multiplayer(false) && gamespeed!=100 && (i=-1))) { if(m_noitemsnade && ents[i].type!=I_GRENADE) continue; else if(m_pistol && ents[i].type==I_AMMO) continue; putint(p, i); + putint(p, ents[i].type); ents[i].spawned = true; } } @@ -231,33 +251,6 @@ void resetspawns() } void setspawn(int i, bool on) { if(ents.inrange(i)) ents[i].spawned = on; } -void equip(playerent *d) -{ - if(m_pistol) d->primary = GUN_PISTOL; - else if(m_osok) d->primary = GUN_SNIPER; - else if(m_lss) d->primary = GUN_KNIFE; - else d->primary = d->nextprimary; - - loopi(NUMGUNS) d->ammo[i] = d->mag[i] = 0; - - d->mag[GUN_KNIFE] = d->ammo[GUN_KNIFE] = 1; - d->mag[GUN_GRENADE] = d->ammo[GUN_GRENADE] = 0; - - if(!m_nopistol) - { - d->ammo[GUN_PISTOL] = itemstats[GUN_PISTOL].max-magsize(GUN_PISTOL); - d->mag[GUN_PISTOL] = magsize(GUN_PISTOL); - } - - if(!m_noprimary) - { - d->ammo[d->primary] = itemstats[d->primary].start-magsize(d->primary); - d->mag[d->primary] = magsize(d->primary); - } - - d->gunselect = d->primary; -} - void item(int num) { switch(num) diff --git a/source/src/entity.h b/source/src/entity.h index 4f94f7e5..68c048ee 100644 --- a/source/src/entity.h +++ b/source/src/entity.h @@ -27,9 +27,50 @@ struct entity : public persistent_entity bool spawned; // the only dynamic state of a map entity }; +struct itemstat { int add, start, max, sound; }; +static itemstat ammostats[] = +{ + {1, 1, 1, S_ITEMAMMO}, //knife dummy + {16, 32, 72, S_ITEMAMMO}, //pistol + {14, 28, 21, S_ITEMAMMO}, //shotgun + {60, 90, 90, S_ITEMAMMO}, //subgun + {10, 20, 15, S_ITEMAMMO}, //sniper + {30, 60, 60, S_ITEMAMMO}, //assault + {2, 0, 2, S_ITEMAMMO} //grenade +}; + +static itemstat powerupstats[] = +{ + {33, 100, 100, S_ITEMHEALTH}, //health + {50, 100, 100, S_ITEMARMOUR}, //armour + {16, 0, 72, S_ITEMPUP} //powerup +}; + enum { GUN_KNIFE = 0, GUN_PISTOL, GUN_SHOTGUN, GUN_SUBGUN, GUN_SNIPER, GUN_ASSAULT, GUN_GRENADE, NUMGUNS }; #define reloadable_gun(g) (g != GUN_KNIFE && g != GUN_GRENADE) +#define SGRAYS 21 +#define SGSPREAD 2 +#define EXPDAMRAD 10 + +struct guninfo { short sound, reload, reloadtime, attackdelay, damage, projspeed, part, spread, recoil, magsize, mdl_kick_rot, mdl_kick_back; bool isauto; }; +static guninfo guns[NUMGUNS] = +{ + { S_KNIFE, S_NULL, 0, 500, 50, 0, 0, 1, 1, 1, 0, 0, false }, + { S_PISTOL, S_RPISTOL, 1400, 170, 19, 0, 0, 80, 10, 8, 6, 5, false }, // *SGRAYS + { S_SHOTGUN, S_RSHOTGUN, 2400, 1000, 5, 0, 0, 1, 35, 7, 9, 9, false }, //reload time is for 1 shell from 7 too powerful to 6 + { S_SUBGUN, S_RSUBGUN, 1650, 80, 16, 0, 0, 70, 15, 30, 1, 2, true }, + { S_SNIPER, S_RSNIPER, 1950, 1500, 85, 0, 0, 60, 50, 5, 4, 4, false }, + { S_ASSAULT, S_RASSAULT, 2000, 130, 24, 0, 0, 20, 40, 15, 0, 2, true }, //recoil was 44 + { S_NULL, S_NULL, 1000, 650, 150, 20, 6, 1, 1, 1, 3, 1, false }, +}; + +static inline int reloadtime(int gun) { return guns[gun].reloadtime; } +static inline int attackdelay(int gun) { return guns[gun].attackdelay; } +static inline int magsize(int gun) { return guns[gun].magsize; } +static inline int kick_rot(int gun) { return guns[gun].mdl_kick_rot; } +static inline int kick_back(int gun) { return guns[gun].mdl_kick_back; } + #define isteam(a,b) (m_teammode && strcmp(a, b)==0) #define TEAM_CLA 0 @@ -39,9 +80,6 @@ enum { GUN_KNIFE = 0, GUN_PISTOL, GUN_SHOTGUN, GUN_SUBGUN, GUN_SNIPER, GUN_ASSAU #define team_int(t) (strcmp((t), "CLA") == 0 ? TEAM_CLA : TEAM_RVSF) #define team_opposite(o) ((o) == TEAM_CLA ? TEAM_RVSF : TEAM_CLA) -struct itemstat { int add, start, max, sound; }; -// End add - enum { ENT_PLAYER = 0, ENT_BOT, ENT_CAMERA, ENT_BOUNCE }; enum { CS_ALIVE = 0, CS_DEAD, CS_LAGGED, CS_EDITING }; enum { CR_DEFAULT, CR_MASTER, CR_ADMIN }; @@ -153,56 +191,167 @@ struct poshist } }; -struct playerent : dynent +struct playerstate +{ + int health, armour; + int primary, nextprimary; + int gunselect, gunwait; + int akimbo; + int ammo[NUMGUNS], mag[NUMGUNS]; + + playerstate() : primary(GUN_ASSAULT), nextprimary(GUN_ASSAULT) {} + + itemstat &itemstats(int type) + { + switch(type) + { + case I_CLIPS: return ammostats[GUN_PISTOL]; + case I_AMMO: return ammostats[primary]; + case I_GRENADE: return ammostats[GUN_GRENADE]; + case I_HEALTH: + case I_ARMOUR: + case I_AKIMBO: + return powerupstats[type-I_HEALTH]; + default: + return *(itemstat *)0; + } + } + + bool canpickup(int type) + { + switch(type) + { + case I_CLIPS: return ammo[GUN_PISTOL] is.max) v = is.max; + } + + void pickup(int type) + { + switch(type) + { + case I_CLIPS: additem(ammostats[GUN_PISTOL], ammo[GUN_PISTOL]); break; + case I_AMMO: additem(ammostats[primary], ammo[primary]); break; + case I_GRENADE: additem(ammostats[GUN_GRENADE], mag[GUN_GRENADE]); break; + case I_HEALTH: additem(powerupstats[type-I_HEALTH], health); break; + case I_ARMOUR: additem(powerupstats[type-I_HEALTH], armour); break; + case I_AKIMBO: + akimbo = 1; + mag[GUN_PISTOL] = 2*magsize(GUN_PISTOL); + additem(powerupstats[type-I_HEALTH], ammo[GUN_PISTOL]); + break; + } + } + + void respawn() + { + health = 100; + armour = 0; + gunselect = GUN_PISTOL; + gunwait = 0; + akimbo = 0; + loopi(NUMGUNS) ammo[i] = mag[i] = 0; + ammo[GUN_KNIFE] = mag[GUN_KNIFE] = 1; + } + + void spawnstate(int gamemode) + { + if(m_pistol) primary = GUN_PISTOL; + else if(m_osok) primary = GUN_SNIPER; + else if(m_lss) primary = GUN_KNIFE; + else primary = nextprimary; + + if(!m_nopistol) + { + ammo[GUN_PISTOL] = ammostats[GUN_PISTOL].max-magsize(GUN_PISTOL); + mag[GUN_PISTOL] = magsize(GUN_PISTOL); + } + + if(!m_noprimary) + { + ammo[primary] = ammostats[primary].start-magsize(primary); + mag[primary] = magsize(primary); + } + + gunselect = primary; + + if(m_osok) health = 1; + } + + // just subtract damage here, can set death, etc. later in code calling this + int dodamage(int damage) + { + int ad = damage*30/100; // let armour absorb when possible + if(ad>armour) ad = armour; + armour -= ad; + damage -= ad; + health -= damage; + return damage; + } +}; + +struct playerent : dynent, playerstate { int clientnum, lastupdate, plag, ping; int lifesequence; // sequence id for each respawn, used in damage test int frags, flagscore; - int health, armour; - int gunselect, gunwait; int lastaction, lastattackgun, lastmove, lastpain, lastteamkill; - int clientrole; + int clientrole; bool attacking; - int ammo[NUMGUNS]; - int mag[NUMGUNS]; string name, team; int shots; //keeps track of shots from auto weapons bool reloading, weaponchanging; int nextweapon; // weapon we switch to - int primary; //primary gun - int nextprimary; // primary after respawning int skin, nextskin; // skin after respawning int thrownademillis; struct bounceent *inhandnade; - int akimbo; int akimbolastaction[2]; - int akimbomillis; + int akimbomillis; poshist history; // Previous stored locations of this player playerent() : clientnum(-1), plag(0), ping(0), lifesequence(0), frags(0), flagscore(0), lastpain(0), lastteamkill(0), clientrole(CR_DEFAULT), - shots(0), reloading(false), primary(GUN_ASSAULT), nextprimary(GUN_ASSAULT), + shots(0), reloading(false), skin(0), nextskin(0), inhandnade(NULL) { name[0] = team[0] = 0; respawn(); } + void damageroll(float damage) + { + float damroll = 2.0f*damage; + roll += roll>0 ? damroll : (roll<0 ? -damroll : (rnd(2) ? damroll : -damroll)); // give player a kick + } + + void hitpush(int damage, const vec &dir, playerent *actor, int gun) + { + vec push(dir); + push.mul(damage/100.0f); + vel.add(push); + } + void respawn() { - reset(); + dynent::reset(); + playerstate::respawn(); history.reset(); - health = 100; - armour = 0; lastaction = akimbolastaction[0] = akimbolastaction[1] = 0; akimbomillis = 0; - gunselect = GUN_PISTOL; - gunwait = 0; attacking = false; weaponchanging = false; - akimbo = 0; - loopi(NUMGUNS) ammo[i] = mag[i] = 0; } }; @@ -224,14 +373,6 @@ struct botent : playerent botent() : pBot(NULL), enemy(NULL) { type = ENT_BOT; } }; -// Moved from server.cpp by Rick -struct server_entity // server side version of "entity" type -{ - bool spawned; - int spawnsecs; -}; -// End move - enum { CTFF_INBASE = 0, CTFF_STOLEN, CTFF_DROPPED }; struct flaginfo diff --git a/source/src/geom.h b/source/src/geom.h index 56cf99b0..36e5dbd6 100644 --- a/source/src/geom.h +++ b/source/src/geom.h @@ -40,3 +40,33 @@ struct vec bool reject(const vec &o, float max) const { return x>o.x+max || xo.y+max || ystate==CS_DEAD) respawn(); else player1->attacking = on; } @@ -462,7 +461,6 @@ void attack(bool on) void jumpn(bool on) { if(intermission) return; - if(demoplayback && on) demopaused = !demopaused; else if(player1->state==CS_DEAD) { if(on) respawn(); @@ -497,7 +495,7 @@ void mousemove(int dx, int dy) camera1->yaw += (dx/SENSF)*(sensitivity/(float)sensitivityscale); camera1->pitch -= (dy/SENSF)*(sensitivity/(float)sensitivityscale)*(invmouse ? -1 : 1); fixcamerarange(); - if(camera1!=player1 && player1->state!=CS_DEAD && !demoplayback) + if(camera1!=player1 && player1->state!=CS_DEAD) { player1->yaw = camera1->yaw; player1->pitch = camera1->pitch; diff --git a/source/src/protocol.cpp b/source/src/protocol.cpp index 35e6819c..fc233fba 100644 --- a/source/src/protocol.cpp +++ b/source/src/protocol.cpp @@ -74,26 +74,6 @@ void getstring(char *text, ucharbuf &p, int len) while(*t++); } -// machine independent float (single precision, 24 bit mantissa, 7 bit exponent, sign) - -void putfloat(ucharbuf &p, float n) -{ - int e; - double d = frexp((double) n, &e); - e += 64; - if (e < 0 || e > 127) e &= 0x7f; // hack: this float is not properly initialized... - int m = ((int) (d * (1<<24))) & ((1<<24)-1); - p.put(m); p.put(m>>8); p.put(m>>16); p.put(e | (d<0?0x80:0)); -} - -float getfloat(ucharbuf &p) -{ - int m = p.get(); m |= p.get()<<8; m |= p.get()<<16; - int e = p.get(); - m |= e & 0x80 ? (-1 ^ ((1<<24)-1)) : 0; - return (float) ldexp((double) m / (1<<24), (e & 0x7f) - 64); -} - void filtertext(char *dst, const char *src, bool whitespace, int len) { @@ -124,18 +104,20 @@ const char *modestr(int n) { return (n>=0 && (size_t)n < sizeof(modenames)/sizeo char msgsizesl[] = // size inclusive message token, 0 for variable or not-checked sizes { SV_INITS2C, 5, SV_INITC2S, 0, SV_POS, 0, SV_TEXT, 0, SV_TEAMTEXT, 0, SV_SOUND, 2, SV_CDIS, 2, - SV_GIBDIED, 2, SV_DIED, 2, SV_GIBDAMAGE, 4, SV_DAMAGE, 4, SV_SHOT, 9, SV_FRAGS, 2, SV_RESUME, 4, + SV_SHOOT, 0, SV_EXPLODE, 0, SV_SUICIDE, 1, SV_AKIMBO, 2, SV_RELOAD, 3, + SV_GIBDIED, 4, SV_DIED, 4, SV_GIBDAMAGE, 6, SV_DAMAGE, 6, SV_HITPUSH, 6, SV_SHOTFX, 9, SV_THROWNADE, 8, + SV_TRYSPAWN, 1, SV_SPAWNSTATE, 20, SV_SPAWN, 3, SV_FORCEDEATH, 2, SV_RESUME, 0, SV_TIMEUP, 2, SV_EDITENT, 10, SV_MAPRELOAD, 2, SV_ITEMACC, 2, - SV_MAPCHANGE, 0, SV_ITEMSPAWN, 2, SV_ITEMPICKUP, 3, + SV_MAPCHANGE, 0, SV_ITEMSPAWN, 2, SV_ITEMPICKUP, 2, SV_PING, 2, SV_PONG, 2, SV_CLIENTPING, 2, SV_GAMEMODE, 2, SV_EDITH, 7, SV_EDITT, 7, SV_EDITS, 6, SV_EDITD, 6, SV_EDITE, 6, SV_SENDMAP, 0, SV_RECVMAP, 1, SV_SERVMSG, 0, SV_ITEMLIST, 0, SV_WEAPCHANGE, 2, SV_MODELSKIN, 2, SV_FLAGPICKUP, 2, SV_FLAGDROP, 2, SV_FLAGRETURN, 2, SV_FLAGSCORE, 2, SV_FLAGRESET, 2, SV_FLAGINFO, 0, SV_FLAGS, 2, - SV_ARENASPAWN, 0, SV_ARENAWIN, 0, + SV_ARENAWIN, 2, SV_SETMASTER, 2, SV_SETADMIN, 0, SV_SERVOPINFO, 3, SV_SERVOPCMD, 3, SV_SERVOPCMDDENIED, 2, SV_FORCETEAM, 2, SV_AUTOTEAM, 2, - SV_PWD, 0, + SV_CONNECT, 0, SV_CLIENT, 0, -1 }; diff --git a/source/src/protocol.h b/source/src/protocol.h index 8b5e41f6..f15df5c1 100644 --- a/source/src/protocol.h +++ b/source/src/protocol.h @@ -3,33 +3,34 @@ #define MAXTRANS 5000 // max amount of data to swallow in 1 go #define CUBE_SERVER_PORT 28763 #define CUBE_SERVINFO_PORT 28764 -#define PROTOCOL_VERSION 1125 // bump when protocol changes -#define SAVEGAMEVERSION 1009 // bump if dynent/netprotocol changes or any other savegame/demo data bumped from 5 +#define PROTOCOL_VERSION 1126 // bump when protocol changes // network messages codes, c2s, c2c, s2c enum { SV_INITS2C = 0, SV_INITC2S, SV_POS, SV_TEXT, SV_TEAMTEXT, SV_SOUND, SV_CDIS, - SV_GIBDIED, SV_DIED, SV_GIBDAMAGE, SV_DAMAGE, SV_SHOT, SV_FRAGS, SV_RESUME, + SV_SHOOT, SV_EXPLODE, SV_SUICIDE, SV_AKIMBO, SV_RELOAD, + SV_GIBDIED, SV_DIED, SV_GIBDAMAGE, SV_DAMAGE, SV_HITPUSH, SV_SHOTFX, SV_THROWNADE, + SV_TRYSPAWN, SV_SPAWNSTATE, SV_SPAWN, SV_FORCEDEATH, SV_RESUME, SV_TIMEUP, SV_EDITENT, SV_MAPRELOAD, SV_ITEMACC, SV_MAPCHANGE, SV_ITEMSPAWN, SV_ITEMPICKUP, SV_PING, SV_PONG, SV_CLIENTPING, SV_GAMEMODE, - SV_EDITH, SV_EDITT, SV_EDITS, SV_EDITD, SV_EDITE, + SV_EDITMODE, SV_EDITH, SV_EDITT, SV_EDITS, SV_EDITD, SV_EDITE, SV_SENDMAP, SV_RECVMAP, SV_SERVMSG, SV_ITEMLIST, SV_WEAPCHANGE, SV_MODELSKIN, SV_FLAGPICKUP, SV_FLAGDROP, SV_FLAGRETURN, SV_FLAGSCORE, SV_FLAGRESET, SV_FLAGINFO, SV_FLAGS, - SV_ARENASPAWN, SV_ARENAWIN, + SV_ARENAWIN, SV_SETMASTER, SV_SETADMIN, SV_SERVOPINFO, SV_SERVOPCMD, SV_SERVOPCMDDENIED, SV_FORCETEAM, SV_AUTOTEAM, - SV_PWD, + SV_CONNECT, SV_CLIENT, }; enum { SOPCMD_KICK = 0, SOPCMD_BAN, SOPCMD_REMBANS, SOPCMD_MASTERMODE, SOPCMD_AUTOTEAM, SOPCMD_FORCETEAM, SOPCMD_GIVEMASTER, SOPCMD_NUM}; #define DMF 16.0f -#define DAF 1.0f -#define DVF 100.0f +#define DNF 100.0f +#define DVELF 4.0f enum { DISC_NONE = 0, DISC_EOP, DISC_CN, DISC_MKICK, DISC_MBAN, DISC_TAGT, DISC_BANREFUSE, DISC_WRONGPW, DISC_SOPLOGINFAIL, DISC_MAXCLIENTS, DISC_MASTERMODE, DISC_AUTOKICK, DISC_NUM }; diff --git a/source/src/protos.h b/source/src/protos.h index 936345fc..b596ca2f 100644 --- a/source/src/protos.h +++ b/source/src/protos.h @@ -112,17 +112,17 @@ extern void toserver(char *text); extern void addmsg(int type, const char *fmt = NULL, ...); extern bool multiplayer(bool msg = true); extern bool allowedittoggle(); -extern void sendpackettoserv(int chan, struct _ENetPacket *packet); +extern void sendpackettoserv(int chan, ENetPacket *packet); extern void gets2c(); extern void c2sinfo(playerent *d); extern void c2skeepalive(); extern void neterr(char *s); extern int getclientnum(); extern void changemapserv(char *name, int mode); -extern void changeteam(int team); +extern void changeteam(int team, bool forced = false); extern void newteam(char *name); extern bool securemapcheck(char *map); -extern bool sendpwd; +extern void sendintro(); // clientgame extern flaginfo flaginfos[2]; @@ -133,7 +133,8 @@ extern void changemap(char *name); extern void initclient(); extern void deathstate(playerent *pl); extern void spawnplayer(playerent *d); -extern void dodamage(int damage, int actor, playerent *act, bool gib = false, playerent *pl = player1); +extern void dodamage(int damage, playerent *pl, playerent *actor, bool gib = false, bool local = true); +extern void dokill(playerent *pl, playerent *act, bool gib = false); extern playerent *newplayerent(); extern botent *newbotent(); extern void freebotent(botent *d); @@ -155,6 +156,7 @@ extern void flaginbase(int flag, int action, int act); extern void flagmsg(int flag, int action); extern void arenarespawn(); extern void respawn(); +extern void findplayerstart(playerent *d); extern void serveropcommand(int cmd, int arg1); extern void refreshsopmenu(void *menu, bool init); extern char *colorname(playerent *d, int num = 0, char *name = NULL, char *prefix = ""); @@ -262,18 +264,6 @@ extern void save_world(char *fname); extern void load_world(char *mname); extern void writemap(char *mname, int msize, uchar *mdata); extern uchar *readmap(char *mname, int *msize); -extern void loadgamerest(); -extern void incomingdemodata(int chan, uchar *buf, int len, bool extras = false); -extern void demoplaybackstep(); -extern void stop(); -extern void stopifrecording(); -extern void demodamage(int damage, vec &o); -extern void demoblend(int damage); -extern bool demopaused; -extern playerent *demoplayer; -extern void shiftdemoplayer(int i); -extern bool localdemoplayer1st(); -extern int demomillis(); // physics extern float raycube(const vec &o, const vec &ray, vec &surface); @@ -321,11 +311,13 @@ extern void shootv(int gun, vec &from, vec &to, playerent *d = 0, bool local = f extern void createrays(vec &from, vec &to); extern void moveprojectiles(float time); extern bounceent *newbounceent(); +extern void removebounceents(playerent *owner); extern void movebounceents(); extern void clearbounceents(); extern void renderbounceents(); extern void addgib(playerent *d); extern void projreset(); +extern void removeprojectiles(playerent *d); extern playerent *playerincrosshair(); extern int reloadtime(int gun); extern void reload(playerent *d); @@ -333,26 +325,23 @@ extern int attackdelay(int gun); extern int magsize(int gun); extern int kick_rot(int gun); extern int kick_back(int gun); -extern bool gun_changed; extern bool akimboside; extern void checkweaponswitch(); extern void weaponswitch(int gun); extern void setscope(bool activate); -// Added by Rick -extern bool intersect(dynent *d, vec &from, vec &to, vec *end = NULL); -// End add by Rick +extern bool intersect(dynent *d, const vec &from, const vec &to, vec *end = NULL); +extern bool intersect(entity *e, const vec &from, const vec &to, vec *end = NULL); +extern void damageeffect(int damage, playerent *d); // entities extern char *entnames[]; extern void putitems(ucharbuf &p); -extern void realpickup(int n, playerent *d); +extern void pickupeffects(int n, playerent *d); extern void renderentities(); extern void resetspawns(); extern void setspawn(int i, bool on); extern void checkitems(playerent *d); -extern void equip(playerent *d); -extern bool intersect(entity *e, vec &from, vec &to, vec *end=NULL); // rndmap extern void perlinarea(block &b, int scale, int seed, int psize); @@ -372,14 +361,12 @@ extern void initserver(bool dedicated, int uprate, char *sdesc, char *ip, char * extern void cleanupserver(); extern void localconnect(); extern void localdisconnect(); -extern void localclienttoserver(int chan, struct _ENetPacket *); -extern void serverslice(int seconds, unsigned int timeout); +extern void localclienttoserver(int chan, ENetPacket *); +extern void serverslice(uint timeout); extern void putint(ucharbuf &p, int n); extern int getint(ucharbuf &p); extern void putuint(ucharbuf &p, int n); extern int getuint(ucharbuf &p); -extern void putfloat(ucharbuf &p, float n); -extern float getfloat(ucharbuf &p); extern void sendstring(const char *t, ucharbuf &p); extern void getstring(char *t, ucharbuf &p, int len = MAXTRANS); extern void filtertext(char *dst, const char *src, bool whitespace = true, int len = sizeof(string)-1); @@ -387,7 +374,7 @@ extern void startintermission(); extern void restoreserverstate(vector &ents); extern uchar *retrieveservers(uchar *buf, int buflen); extern char msgsizelookup(int msg); -extern void serverms(int mode, int numplayers, int minremain, char *smapname, int seconds); +extern void serverms(int mode, int numplayers, int minremain, char *smapname, int millis); extern void servermsinit(const char *master, char *ip, char *sdesc, bool listen); -extern bool serverpickup(uint i, int sec, int sender); +extern bool serverpickup(int i, int sender); diff --git a/source/src/rendergl.cpp b/source/src/rendergl.cpp index 7c2ce639..e8b01e17 100644 --- a/source/src/rendergl.cpp +++ b/source/src/rendergl.cpp @@ -238,36 +238,7 @@ COMMAND(trypitch, ARG_1INT); void recomputecamera() { - if(demoplayback && demoplayer) - { - static physent democam; - extern bool firstpersondemo; - - if(demoplayer == player1 && firstpersondemo) - { - democam = *(physent *)player1; - } - else - { - static playerent *lastplayer = NULL; - if(lastplayer != demoplayer) - { - lastplayer = demoplayer; - democam = *(physent *)demoplayer; - democam.type = ENT_CAMERA; - democam.reset(); - democam.roll = 0; - democam.move = -1; - camera1 = &democam; - } - democam.o = demoplayer->o; - democam.o.x -= (float)(cosf(RAD*(demoplayer->yaw-90)))*demoplayer->radius*1.5f; - democam.o.y -= (float)(sinf(RAD*(demoplayer->yaw-90)))*demoplayer->radius*1.5f; - democam.o.z += demoplayer->eyeheight/3.0f; - if(!demopaused) democam.yaw = demoplayer->yaw; - } - } - else if(editmode || player1->state!=CS_DEAD) + if(editmode || player1->state!=CS_DEAD) { camera1 = player1; } @@ -543,7 +514,7 @@ void drawhudgun(int w, int h, float aspect, int farplane) gluPerspective((float)100.0f*h/w, aspect, 0.3f, farplane); // fov fixed at 100° glMatrixMode(GL_MODELVIEW); - if(player1->state!=CS_DEAD && (!demoplayback || localdemoplayer1st())) renderhudgun(); + if(player1->state!=CS_DEAD) renderhudgun(); rendermenumdl(); glMatrixMode(GL_PROJECTION); diff --git a/source/src/renderhud.cpp b/source/src/renderhud.cpp index e1789bec..4bf493b0 100644 --- a/source/src/renderhud.cpp +++ b/source/src/renderhud.cpp @@ -49,7 +49,6 @@ VARP(hideradar, 0, 0, 1); VARP(radarres, 1, 64, 1024); VARP(radarentsize, 1, 4, 64); VARP(hidectfhud, 0, 0, 1); -VARP(hidedemohud, 0, 0, 1); VAR(showmap, 0, 0, 1); @@ -276,34 +275,30 @@ void gl_drawhud(int w, int h, int curfps, int nquads, int curvert, bool underwat glEnable(GL_TEXTURE_2D); - bool demo3rd = demoplayback && !localdemoplayer1st(); playerent *targetplayer = playerincrosshair(); bool didteamkill = player1->lastteamkill && player1->lastteamkill + 5000 > lastmillis; bool menuvisible = rendermenu(); bool command = getcurcommand() ? true : false; - if(!demo3rd) + if(player1->state==CS_ALIVE && !player1->reloading && !didteamkill && !menuvisible) { - if(player1->state==CS_ALIVE && !player1->reloading && !didteamkill && !menuvisible) - { - bool drawteamwarning = targetplayer ? (isteam(targetplayer->team, player1->team) && targetplayer->state!=CS_DEAD) : false; - if(player1->gunselect==GUN_SNIPER && scoped) drawscope(); - else if((player1->gunselect!=GUN_SNIPER || drawteamwarning)) drawcrosshair(drawteamwarning); - } - - drawequipicons(); - - glMatrixMode(GL_MODELVIEW); - if(!menuvisible && !hideradar) drawradar(w, h); - else drawteamicons(w, h); - glMatrixMode(GL_PROJECTION); - - char *infostr = editinfo(); - if(command) rendercommand(20, 1570); - else if(infostr) draw_text(infostr, 20, 1570); - else if(targetplayer) draw_text(colorname(targetplayer), 20, 1570); + bool drawteamwarning = targetplayer ? (isteam(targetplayer->team, player1->team) && targetplayer->state!=CS_DEAD) : false; + if(player1->gunselect==GUN_SNIPER && scoped) drawscope(); + else if((player1->gunselect!=GUN_SNIPER || drawteamwarning)) drawcrosshair(drawteamwarning); } + drawequipicons(); + + glMatrixMode(GL_MODELVIEW); + if(!menuvisible && !hideradar) drawradar(w, h); + else drawteamicons(w, h); + glMatrixMode(GL_PROJECTION); + + char *infostr = editinfo(); + if(command) rendercommand(20, 1570); + else if(infostr) draw_text(infostr, 20, 1570); + else if(targetplayer) draw_text(colorname(targetplayer), 20, 1570); + glLoadIdentity(); glOrtho(0, VIRTW*2, VIRTH*2, 0, -1, 1); @@ -319,21 +314,7 @@ void gl_drawhud(int w, int h, int curfps, int nquads, int curvert, bool underwat draw_textf("evt %d", left, top+320, xtraverts); } - if(!hidedemohud && demoplayback) - { - const int left = VIRTW*2/80, top = VIRTH*2*3/4; - int dmillis = demomillis(); - s_sprintfd(time)("%d:%02d", dmillis/1000/60, dmillis/1000%60); - s_sprintfd(following)("\f0Following %s", colorname(demoplayer)); - - draw_text(time, left, top-FONTH); - draw_text(following, left, top); - draw_text("jump to pause", left, top+2*FONTH); - draw_text("attack to change view", left, top+3*FONTH); - draw_text("reload for slow-motion", left, top+4*FONTH); - } - - if(player1->state==CS_ALIVE && !demo3rd) + if(player1->state==CS_ALIVE) { glLoadIdentity(); glOrtho(0, VIRTW/2, VIRTH/2, 0, -1, 1); diff --git a/source/src/rendermodel.cpp b/source/src/rendermodel.cpp index 809a664f..1c2631d4 100644 --- a/source/src/rendermodel.cpp +++ b/source/src/rendermodel.cpp @@ -368,8 +368,6 @@ void renderclient(playerent *d, char *mdlname, char *vwepname, int tex) rendermodel(mdlname, anim, tex, 1.5f, o, d->yaw+90, d->pitch/4, speed, basetime, d, vwepname); } -extern int democlientnum; - void renderclient(playerent *d) { if(!d) return; @@ -385,7 +383,7 @@ void renderclient(playerent *d) void renderclients() { playerent *d; - loopv(players) if((d = players[i]) && (!demoplayback || i!=democlientnum)) renderclient(d); - if(player1->state==CS_DEAD || (reflecting && !refracting) || (demoplayback && !localdemoplayer1st())) renderclient(player1); + loopv(players) if((d = players[i])) renderclient(d); + if(player1->state==CS_DEAD || (reflecting && !refracting)) renderclient(player1); } diff --git a/source/src/renderparticles.cpp b/source/src/renderparticles.cpp index 7c2cbfad..ea5deb04 100644 --- a/source/src/renderparticles.cpp +++ b/source/src/renderparticles.cpp @@ -264,7 +264,6 @@ static struct parttype { int type; float r, g, b; int gr, tex; float sz; } partt { PT_DECAL, 1.0f, 1.0f, 1.0f, 0, 5, 1.5f }, // scorch decal }; -VAR(demotracking, 0, 0, 1); VAR(particlesize, 20, 100, 500); VARP(blood, 0, 1, 1); @@ -272,12 +271,6 @@ VARP(bloodttl, 0, 10000, 30000); void render_particles(int time) { - if(demoplayback && demotracking) - { - vec nom(0, 0, 0); - newparticle(player1->o, nom, 100000000, 4); - } - bool rendered = false; for(int i = MAXPARTYPES-1; i>=0; i--) if(parlist[i]) { diff --git a/source/src/savegamedemo.cpp b/source/src/savegamedemo.cpp deleted file mode 100644 index 2e0116b6..00000000 --- a/source/src/savegamedemo.cpp +++ /dev/null @@ -1,623 +0,0 @@ -// loading and saving of savegames & demos, dumps the spawn state of all mapents, the full state of all dynents (monsters + player) - -#include "cube.h" - -enum { // demo file blocktypes (uchar) - SD_NULL = 0, SD_INIT1, SD_INIT2, SD_INIT3, SD_NETDATA = 3, SD_EXTRAS = 100, SD_PLAYER1, SD_EOF = 255 - }; - -gzFile f = NULL; -bool demorecording = false; -bool demoplayback = false; -bool demoloading = false; -vector playerhistory; -int democlientnum = 0; -int starttime = 0; - -static uchar *iobuf = (uchar *)calloc(1, 65536); // 64k should be enough for anybody -static ucharbuf dp(iobuf, 65535); - -void startdemo(); -void stopreset(); - -void gzcheck(int a, int b) { if(a!=b) fatal("savegame file corrupt (short)"); } - -void gzput(int i) { if(gzputc(f, i)==-1) fatal("gzputc"); } -void gzputw(int i) { if(gzputc(f, i & 0xff)==-1 || gzputc(f, (i>>8) & 0xff)==-1 || i >= 1<<16 || i < 0) fatal("gzputw"); } - -int gzget() { uchar c = gzgetc(f); return c; } -int gzgetw() { int w = gzgetc(f) | gzgetc(f)<<8; return w; } - -void putvec(ucharbuf &p, vec &d) {loopi(3) putfloat(p, d.v[i]);} -void getvec(vec &d, ucharbuf &p) {loopi(3) d.v[i] = getfloat(p);} - -int gzwritedp(int id) -{ - int len = dp.length(); - gzput(id); gzputw(len); - gzcheck(gzwrite(f, iobuf, len), len); - dp.forcelen(0); - return len; -} - -int gzreaddp(int id, bool seekid = false) -{ - int len, fid; - do { - fid = gzget(); - len = gzgetw(); - gzcheck(gzread(f, iobuf, len), len); - if (fid == SD_EOF) break; - } while (seekid && fid != id && !gzeof(f)); - dp.forcelen(0); dp.forcemaxlen(len); - if(fid != id && fid != SD_EOF) - conoutf("demo playback aborted: block id error found %d, expected %d", fid, id); - if(gzeof(f) || fid == SD_EOF || fid != id) - { - stopreset(); - return -1; - } - return len; -} - -void resetdp() { dp.forcelen(0); dp.forcemaxlen(65535); } - -void putplayerent(ucharbuf &p, playerent *d) -{ - // physent - putvec(p, d->o); putvec(p, d->vel); // origin, velocity - putfloat(p, d->yaw); putfloat(p, d->pitch); putfloat(p, d->roll); // used as vec in one place - putfloat(p, d->maxspeed); // cubes per second, 24 for player - putint(p, d->timeinair); // used for fake gravity - putfloat(p, d->radius); putfloat(p, d->eyeheight); putfloat(p, d->aboveeye); // bounding box size - p.put(d->inwater<<0 | d->onfloor<<1 | d->onladder<<2 | d->jumpnext<<3); - putint(p, d->move); putint(p, d->strafe); - putint(p, d->state); putint(p, d->type); - - // dynent - p.put(d->k_left<<0 | d->k_right<<1 | d->k_up<<2 | d->k_down<<3); // see input code - loopi(2) - { - putint(p, d->prev[i].anim); putint(p, d->prev[i].frame); - putint(p, d->prev[i].range); putint(p, d->prev[i].basetime); - putfloat(p, d->prev[i].speed); - putint(p, d->current[i].anim); putint(p, d->current[i].frame); - putint(p, d->current[i].range); putint(p, d->current[i].basetime); - putfloat(p, d->current[i].speed); - putint(p, d->lastanimswitchtime[i]); - } - // void *lastmodel[2]; seems to be unused... - - // playerent - putint(p, d->clientnum); putint(p, d->lastupdate); putint(p, d->plag); putint(p, d->ping); - putint(p, d->lifesequence); // sequence id for each respawn, used in damage test - putint(p, d->frags); putint(p, d->flagscore); - putint(p, d->health); putint(p, d->armour); - putint(p, d->gunselect); putint(p, d->gunwait); - putint(p, d->lastaction); putint(p, d->lastattackgun); putint(p, d->lastmove); - putint(p, d->lastpain); putint(p, d->lastteamkill); - putint(p, d->clientrole); - loopi(NUMGUNS) { putint(p, d->ammo[i]); putint(p, d->mag[i]); } - sendstring(d->name, p); sendstring(d->team, p); - putint(p, d->shots); //keeps track of shots from auto weapons - //p.put(d->attacking<<0 | d->reloading<<1 | d->hasarmour<<2 | d->weaponchanging<<3); - p.put(d->attacking<<0 | d->reloading<<1 | d->weaponchanging<<3); - putint(p, d->nextweapon); // weapon we switch to - putint(p, d->primary); //primary gun - putint(p, d->nextprimary); // primary after respawning - putint(p, d->skin); putint(p, d->nextskin); // skin after respawning - putint(p, d->thrownademillis); - // struct bounceent *inhandnade; stored pointer was evil, anyway... - putint(p, d->akimbo); - loopi(2) putint(p, d->akimbolastaction[i]); - putint(p, d->akimbomillis); - - // poshist Previous stored locations of this player - putint(p, d->history.nextupdate); putint(p, d->history.curpos); putint(p, d->history.numpos); - loopi(POSHIST_SIZE) putvec(p, d->history.pos[i]); -} - -void getplayerent(playerent *d, ucharbuf &p) -{ - // physent - getvec(d->o, p); getvec(d->vel, p); // origin, velocity - d->yaw = getfloat(p); d->pitch = getfloat(p); d->roll = getfloat(p); // used as vec in one place - d->maxspeed = getfloat(p); // cubes per second, 24 for player - d->timeinair = getint(p); // used for fake gravity - d->radius = getfloat(p); d->eyeheight = getfloat(p); d->aboveeye = getfloat(p); // bounding box size - int e = p.get(); - d->inwater = e & 1; d->onfloor = e>>1 & 1; d->onladder = e>>2 & 1; d->jumpnext = e>>3 & 1; - d->move = getint(p); d->strafe = getint(p); - d->state = getint(p); d->type = getint(p); - - // dynent - e = p.get(); - d->k_left = e & 1; d->k_right = e>>1 & 1; d->k_up = e>>2 & 1; d->k_down = e>>3 & 1; // see input code - loopi(2) - { - d->prev[i].anim = getint(p); d->prev[i].frame = getint(p); - d->prev[i].range = getint(p); d->prev[i].basetime = getint(p); - d->prev[i].speed = getfloat(p); - d->current[i].anim = getint(p); d->current[i].frame = getint(p); - d->current[i].range = getint(p); d->current[i].basetime = getint(p); - d->current[i].speed = getfloat(p); - d->lastanimswitchtime[i] = getint(p); - } - loopi(2) d->lastmodel[i] = NULL; // seems to be unused... - - // playerent - d->clientnum = getint(p); d->lastupdate = getint(p); d->plag = getint(p); d->ping = getint(p); - d->lifesequence = getint(p); // sequence id for each respawn, used in damage test - d->frags = getint(p); d->flagscore = getint(p); - d->health = getint(p); d->armour = getint(p); - d->gunselect = getint(p); d->gunwait = getint(p); - d->lastaction = getint(p); d->lastattackgun = getint(p); d->lastmove = getint(p); - d->lastpain = getint(p); d->lastteamkill = getint(p); - d->clientrole = getint(p); - loopi(NUMGUNS) { d->ammo[i] = getint(p); d->mag[i] = getint(p); } - getstring(d->name, p); getstring(d->team, p); - d->shots = getint(p); //keeps track of shots from auto weapons - e = p.get(); - //d->attacking = e & 1; d->reloading = e>>1 & 1; d->hasarmour = e>>2 & 1; d->weaponchanging = e>>3 & 1; - //fixme - d->nextweapon = getint(p); // weapon we switch to - d->primary = getint(p); //primary gun - d->nextprimary = getint(p); // primary after respawning - d->skin = getint(p); d->nextskin = getint(p); // skin after respawning - d->thrownademillis = getint(p); - d->inhandnade = NULL; // stored pointer was evil, anyway... - d->akimbo = getint(p); - loopi(2) d->akimbolastaction[i] = getint(p); - d->akimbomillis = getint(p); - - // poshist Previous stored locations of this player - d->history.nextupdate = getint(p); d->history.curpos = getint(p); d->history.numpos = getint(p); - loopi(POSHIST_SIZE) getvec(d->history.pos[i], p); -} - -void stop() -{ - if(f) - { - if(demorecording) - { - resetdp(); - putint(dp, lastmillis-starttime); // total recording time - gzwritedp(SD_EOF); - } - gzclose(f); - } - f = NULL; - demorecording = false; - demoplayback = false; - demoloading = false; - loopv(playerhistory) zapplayer(playerhistory[i]); - playerhistory.setsize(0); - setvar("gamespeed", 100); - extern void recomputecamera(); - recomputecamera(); -} - -void stopifrecording() { if(demorecording) stop(); } - -void savestate(char *fn) -{ - stop(); - f = opengzfile(fn, "wb9"); - if(!f) { conoutf("could not write %s", fn); return; } - gzwrite(f, (void *)"CUBESAVE", 8); - gzputc(f, SDL_BYTEORDER==SDL_LIL_ENDIAN ? 1 : 0); - gzputw(SAVEGAMEVERSION); gzputw(0); // compatibility, so far... - - resetdp(); - sendstring(getclientmap(), dp); - putint(dp, gamemode); - gzwritedp(SD_INIT1); - - putint(dp, ents.length()); - loopv(ents) putint(dp, ents[i].spawned); - putplayerent(dp, player1); - putint(dp, players.length()); - loopv(players) - { - int e = players[i]==NULL; - putint(dp, e); - if (!e) putplayerent(dp, players[i]); - } - gzwritedp(SD_INIT2); -} - -void savegame(char *name) -{ - conoutf("can only save classic sp games"); - return; -} - -void loadstate(char *fn) -{ - stop(); - if(multiplayer()) return; - f = opengzfile(fn, "rb9"); - if(!f) { conoutf("could not open %s", fn); return; } - - string buf; - gzread(f, buf, 8); - if(strncmp(buf, "CUBESAVE", 8)) goto out; - gzgetc(f); // who cares... - if(gzgetw()!=SAVEGAMEVERSION) goto out; - gzgetw(); // dummy - - if (gzreaddp(SD_INIT1) < 0) goto out; - string mapname; - getstring(mapname, dp, _MAXDEFSTR); - nextmode = getint(dp); - changemap(mapname); // continue below once map has been loaded and client & server have updated - if (dp.remaining() || dp.overread()) conoutf("SD_INIT1 packet size mismatch"); // debug only - return; - out: - conoutf("aborting: savegame/demo header malformed"); - stop(); -} - -void loadgame(char *name) -{ - s_sprintfd(fn)("savegames/%s.csgz", name); - loadstate(fn); -} - -void loadgameout() -{ - stop(); - conoutf("loadgame incomplete: savegame from a different version of this map"); -} - -void fixplayerstate(playerent *d) -{ - if(!d) return; - d->lastaction = d->lastpain = d->lastteamkill = 0; - d->resetanim(); -} - -void loadgamerest() -{ - if(demoplayback || !f) return; - - if (gzreaddp(SD_INIT2) < 0) return; - if(getint(dp)!=ents.length()) return loadgameout(); - loopv(ents) - { - ents[i].spawned = getint(dp)!=0; - } - restoreserverstate(ents); - - getplayerent(player1, dp); - fixplayerstate(player1); - - int nplayers = getint(dp); - loopi(nplayers) if(!getint(dp)) - { - playerent *d = newclient(i); - ASSERT(d); - getplayerent(d, dp); - fixplayerstate(d); - } - if (dp.remaining() || dp.overread()) conoutf("SD_INIT2 packet size mismatch"); // debug only - conoutf("savegame restored"); - if(demoloading) startdemo(); else stop(); -} - -// demo functions - -int playbacktime = 0; -int ddamage, bdamage; -vec dorig; - - -void record(char *name) -{ - int cn = getclientnum(); - if(cn<0) return; - s_sprintfd(fn)("demos/%s.cdgz", name); - savestate(fn); - resetdp(); - putint(dp, cn); - gzwritedp(SD_INIT3); - conoutf("started recording demo to %s", fn); - demorecording = true; - starttime = lastmillis; - ddamage = bdamage = 0; - //fixme - player1->lastaction = player1->lastanimswitchtime[0] = player1->lastanimswitchtime[1] = lastmillis; -} - -void demodamage(int damage, vec &o) { ddamage = damage; dorig = o; } -void demoblend(int damage) { bdamage = damage; } - -void incomingdemodata(int chan, uchar *buf, int len, bool extras) -{ - if(!demorecording) return; - resetdp(); - putint(dp, lastmillis-starttime); // playbacktime - putint(dp, len); - putint(dp, chan); - dp.put(buf, len); - putint(dp, extras ? 1 : 0); // pre-announce SD_PLAYER1 block for reading convenience - gzwritedp(SD_NETDATA); - if(extras) - { - putint(dp, player1->gunselect); - putint(dp, player1->lastattackgun); - putint(dp, player1->gunwait); - ASSERT(player1->lastaction == 0 || player1->lastaction-starttime >= 0); - putint(dp, player1->lastaction-starttime); - putint(dp, player1->lastanimswitchtime[0]-starttime); - putint(dp, player1->lastanimswitchtime[1]-starttime); - loopi(NUMGUNS) { putint(dp, player1->ammo[i]); putint(dp, player1->mag[i]); } - putint(dp, player1->akimbo ? 1 : 0 | (player1->reloading ? 1 : 0) << 1 | (player1->weaponchanging ? 1 : 0) << 2); - switch(player1->gunselect) - { - case GUN_GRENADE: - putint(dp, player1->thrownademillis-starttime); - break; - case GUN_PISTOL: - if(player1->akimbo) - { - putint(dp, player1->akimbolastaction[0]-starttime); - putint(dp, player1->akimbolastaction[1]-starttime); - } - break; - case GUN_SNIPER: - putint(dp, scoped ? 1 : 0); - break; - } - - putint(dp, player1->health); - putint(dp, player1->armour); - putint(dp, player1->state); - putint(dp, bdamage); - bdamage = 0; - putint(dp, ddamage); - if(ddamage) { putvec(dp, dorig); ddamage = 0; } - // FIXME: add all other client state which is not send through the network - gzwritedp(SD_PLAYER1); - } -} - -static bool hasbackup = false; - -void settingsbackup(bool save) -{ - static string name, team; - static int gunselect; - - if(save) - { - s_strcpy(name, player1->name); - s_strcpy(team, player1->team); - gunselect = player1->gunselect; - hasbackup = true; - } - else if (hasbackup) - { - s_strcpy(player1->name, name); - s_strcpy(player1->team, team); - player1->gunselect = gunselect; - player1->clientnum = 0; - } -} - -void demo(char *name) -{ - settingsbackup(true); - s_sprintfd(fn)("demos/%s.cdgz", name); - loadstate(fn); - demoloading = true; -} - -void stopreset() -{ - conoutf("demo stopped (%d msec elapsed)", lastmillis-starttime); - if (hasbackup) player1 = newplayerent(); - disconnect(0, 0); - settingsbackup(false); -} - -VAR(demoplaybackspeed, 10, 100, 1000); -int scaletime(int t) { return (int)(t*(100.0f/demoplaybackspeed))+starttime; } - -void readdemotime() -{ - if (gzreaddp(SD_NETDATA, true) < 0) // skip unneeded packets - return; // demo done - playbacktime = getint(dp); - playbacktime = scaletime(playbacktime); -} - -bool demopaused = false; -void togglepause() { demopaused = !demopaused; } - -playerent *demoplayer = player1; -bool firstpersondemo = true; -bool localdemoplayer1st() { return demoplayback && demoplayer == player1 && firstpersondemo; } - -void shiftdemoplayer(int i) -{ - if(!i) return; - if(demoplayer == player1) - { - firstpersondemo = !firstpersondemo; - if(!firstpersondemo) return; - } - vector plrs; - loopv(players) if(players[i] != NULL) plrs.add(players[i]); - int cur = plrs.find(demoplayer); - if(cur >= 0) - demoplayer = plrs[(cur+i) % plrs.length()]; - else - demoplayer = player1; -} - -void startdemo() -{ - if (gzreaddp(SD_INIT3) < 0) return; - democlientnum = getint(dp); - demoplayback = true; - starttime = lastmillis; - conoutf("now playing demo"); - while(democlientnum>=players.length()) players.add(NULL); - players[democlientnum] = demoplayer = player1; - readdemotime(); -} - -VAR(demodelaymsec, 0, 120, 500); - -void catmulrom(vec &z, vec &a, vec &b, vec &c, float s, vec &dest) // spline interpolation -{ - float s2 = s*s; - float s3 = s*s2; - - dest = a; - dest.mul(2*s3 - 3*s2 + 1); - dest.add(vec(b).mul(-2*s3 + 3*s2)); - dest.add(vec(b).sub(z).mul(0.5f).mul(s3 - 2*s2 + s)); - dest.add(vec(c).sub(a).mul(0.5f).mul(s3 - s2)); -} - -void fixwrap(dynent *a, dynent *b) -{ - while(b->yaw-a->yaw>180) a->yaw += 360; - while(b->yaw-a->yaw<-180) a->yaw -= 360; -} - -void demoplaybackstep() -{ - while(demoplayback && lastmillis>=playbacktime) - { - int len = getint(dp); - if(len<1 || len>MAXTRANS) - { - conoutf("error: huge packet during demo play (%d)", len); - stopreset(); - return; - } - int chan = getint(dp); - uchar *buf = dp.current(); - dp.advance(len); - int extras = getint(dp); // extras may be >1 in future... - if (dp.remaining() || dp.overread()) conoutf("SD_NETDATA packet size mismatch"); // debug only - bool updatehistory = (!extras && playerhistory.length()); - if (updatehistory) memcpy(player1, playerhistory.last(), sizeof(playerent)); - localservertoclient(chan, buf, len); // update game state - if (updatehistory) memcpy(playerhistory.last(), player1, sizeof(playerent)); - - playerent *target = (playerent *)players[democlientnum]; - ASSERT(target); - - if(extras) // read additional client side state not present in normal network stream - { - if (gzreaddp(SD_PLAYER1) < 0) return; - target->gunselect = getint(dp); - target->lastattackgun = getint(dp); - target->gunwait = getint(dp); - target->lastaction = scaletime(getint(dp)); - //ASSERT(target->lastaction >= 0); - if(target->lastaction < 0) target->lastaction = 0; - target->lastanimswitchtime[0] = scaletime(getint(dp)); - target->lastanimswitchtime[1] = scaletime(getint(dp)); - loopi(NUMGUNS) { target->ammo[i] = getint(dp); target->mag[i] = getint(dp); } - uchar flags = getint(dp); - target->akimbo = flags&1 ? true : false; - target->reloading = (flags>>1)&1 ? true : false; - target->weaponchanging = (flags>>2)&1 ? true : false; - switch(target->gunselect) - { - case GUN_GRENADE: - target->thrownademillis = scaletime(getint(dp)); - break; - case GUN_PISTOL: - if(player1->akimbo) - { - target->akimbolastaction[0] = scaletime(getint(dp)); - target->akimbolastaction[1] = scaletime(getint(dp)); - } - break; - case GUN_SNIPER: - scoped = getint(dp) ? true : false; - break; - } - - target->health = getint(dp); - target->armour = getint(dp); - target->state = getint(dp); - if((bdamage = getint(dp))) damageblend(bdamage); - if((ddamage = getint(dp))) { getvec(dorig, dp); particle_splash(3, ddamage, 1000, dorig); }; - // FIXME: set more client state here - target->lastmove = playbacktime; - if (dp.remaining() || dp.overread()) conoutf("SD_PLAYER1 packet size mismatch"); // debug only - } - - // insert latest copy of player into history - if(extras && (playerhistory.empty() || playerhistory.last()->lastupdate!=playbacktime)) - { - playerent *d = newplayerent(); - *d = *target; - d->lastupdate = playbacktime; - playerhistory.add(d); - if(playerhistory.length()>20) - { - zapplayer(playerhistory[0]); - playerhistory.remove(0); - } - } - - readdemotime(); - } - - if(demoplayback) - { - if(playerhistory.length()) memcpy(player1, playerhistory.last(), sizeof(playerent)); - int itime = lastmillis-demodelaymsec; - loopvrev(playerhistory) if(playerhistory[i]->lastupdateo = b->o; - player1->yaw = b->yaw; - player1->pitch = b->pitch; - if(a!=b) // interpolate pos & angles - { - dynent *c = b; - if(i+2=0) z = playerhistory[i-1]; - //if(a==z || b==c) printf("* %d\n", lastmillis); - float bf = (itime-a->lastupdate)/(float)(b->lastupdate-a->lastupdate); - fixwrap(a, player1); - fixwrap(c, player1); - fixwrap(z, player1); - if(z->o.dist(c->o)<16) // if teleport or spawn, dont't interpolate - { - catmulrom(z->o, a->o, b->o, c->o, bf, player1->o); - catmulrom(*(vec *)&z->yaw, *(vec *)&a->yaw, *(vec *)&b->yaw, *(vec *)&c->yaw, bf, *(vec *)&player1->yaw); - } - fixcamerarange(player1); - } - break; - } - //if(player1->state!=CS_DEAD) showscores(false); - } -} - -void stopn() { if(demoplayback) stopreset(); else stop(); conoutf("demo stopped"); } - -int demomillis() { return ((lastmillis-starttime)*demoplaybackspeed)/100; } - -COMMAND(record, ARG_1STR); -COMMAND(demo, ARG_1STR); -COMMANDN(stop, stopn, ARG_NONE); -COMMANDN(demopause, togglepause, ARG_NONE); -COMMANDN(demoplayer, shiftdemoplayer, ARG_1INT); - -COMMAND(savegame, ARG_1STR); -COMMAND(loadgame, ARG_1STR); diff --git a/source/src/scoreboard.cpp b/source/src/scoreboard.cpp index f2c40f70..4202e38b 100644 --- a/source/src/scoreboard.cpp +++ b/source/src/scoreboard.cpp @@ -77,7 +77,7 @@ void renderscores(void *menu, bool init) scorelines.setsize(0); vector scores; - if(!demoplayback) scores.add(player1); + scores.add(player1); loopv(players) if(players[i]) scores.add(players[i]); scores.sort(scorecmp); loopv(scores) renderscore(menu, scores[i], scores[i]->clientnum); @@ -109,7 +109,7 @@ void renderscores(void *menu, bool init) { teamscores.setsize(0); loopv(players) addteamscore(players[i]); - if(!demoplayback) addteamscore(player1); + addteamscore(player1); teamscores.sort(teamscorecmp); loopv(teamscores) { diff --git a/source/src/server.cpp b/source/src/server.cpp index 2f532d37..d9bab69f 100644 --- a/source/src/server.cpp +++ b/source/src/server.cpp @@ -6,25 +6,136 @@ #define valid_client(c) (clients.inrange(c) && clients[c]->type!=ST_EMPTY) #define valid_flag(f) (f >= 0 && f < 2) +static const int DEATHMILLIS = 250; + +enum { GE_NONE = 0, GE_SHOT, GE_EXPLODE, GE_HIT, GE_AKIMBO, GE_RELOAD, GE_SUICIDE, GE_PICKUP }; enum { ST_EMPTY, ST_LOCAL, ST_TCPIP }; enum { MM_OPEN, MM_PRIVATE, MM_NUM }; int mastermode = MM_OPEN; -struct clientscore +struct shotevent { - int frags, flags, lifesequence; + int type; + int millis; + int gun; + float from[3], to[3]; +}; + +struct explodeevent +{ + int type; + int millis; + int gun; +}; + +struct hitevent +{ + int type; + int target; + int lifesequence; + union + { + int info; + float dist; + }; + float dir[3]; +}; + +struct suicideevent +{ + int type; +}; + +struct pickupevent +{ + int type; + int ent; +}; + +struct akimboevent +{ + int type; + int millis; +}; + +struct reloadevent +{ + int type; + int millis; + int gun; +}; + +union gameevent +{ + int type; + shotevent shot; + explodeevent explode; + hitevent hit; + suicideevent suicide; + pickupevent pickup; + akimboevent akimbo; + reloadevent reload; +}; + +struct clientstate : playerstate +{ + vec o; + int state; + int lastdeath, lifesequence; + int lastshot; + int grenades; + int akimbos, akimbomillis; + int flagscore, frags; + + clientstate() : state(CS_DEAD) {} + + bool isalive(int gamemillis) + { + return state==CS_ALIVE || (state==CS_DEAD && gamemillis - lastdeath <= DEATHMILLIS); + } void reset() { - frags = flags = lifesequence = 0; + state = CS_DEAD; + lifesequence = 0; + grenades = akimbos = 0; + akimbomillis = 0; + flagscore = frags = 0; + + respawn(); + } + + void respawn() + { + playerstate::respawn(); + o = vec(-1e10f, -1e10f, -1e10f); + lastdeath = 0; + lastshot = 0; + akimbos = 0; + akimbomillis = 0; } }; -struct savedscore : clientscore +struct savedscore { string name; uint ip; + int frags, flagscore, lifesequence; + + void save(clientstate &cs) + { + frags = cs.frags; + flagscore = cs.flagscore; + lifesequence = cs.lifesequence; + } + + void restore(clientstate &cs) + { + cs.frags = frags; + cs.flagscore = flagscore; + cs.lifesequence = lifesequence; + } }; static vector scores; @@ -38,22 +149,29 @@ struct client // server side version of "dynent" type string mapvote; string name, team; int modevote; - uint pos[3]; - clientscore score; - bool arenadeath; int role; - bool isauthed; // for passworded servers + bool isauthed; // for passworded servers + int gameoffset; + clientstate state; + vector events; vector position, messages; + void mapchange() + { + mapvote[0] = 0; + state.reset(); + events.setsizenodelete(0); + gameoffset = -1; + } + void reset() { name[0] = 0; - score.reset(); - arenadeath = true; position.setsizenodelete(0); messages.setsizenodelete(0); isauthed = false; role = CR_DEFAULT; + mapchange(); } }; @@ -86,11 +204,11 @@ void cleanworldstate(ENetPacket *packet) int bsend = 0, brec = 0, laststatus = 0, lastsec = 0; -void sendpacket(int n, int chan, ENetPacket *packet) +void sendpacket(int n, int chan, ENetPacket *packet, int exclude = -1) { if(n<0) { - loopv(clients) sendpacket(i, chan, packet); + loopv(clients) if(i!=exclude) sendpacket(i, chan, packet); return; } switch(clients[n]->type) @@ -193,6 +311,13 @@ int numclients() return num; } +int numnonlocalclients() +{ + int nonlocalclients = 0; + loopv(clients) if(clients[i]->type==ST_TCPIP) nonlocalclients++; + return nonlocalclients; +} + void zapclient(int c) { if(!clients.inrange(c)) return; @@ -209,14 +334,19 @@ int freeteam() return teamsize[0] < teamsize[1] ? 0 : 1; } -clientscore *findscore(client &c, bool insert) +savedscore *findscore(client &c, bool insert) { if(c.type!=ST_TCPIP) return NULL; if(!insert) loopv(clients) { client &o = *clients[i]; if(o.type!=ST_TCPIP) continue; - if(o.clientnum!=c.clientnum && o.peer->address.host==c.peer->address.host && !strcmp(o.name, c.name)) return &o.score; + if(o.clientnum!=c.clientnum && o.peer->address.host==c.peer->address.host && !strcmp(o.name, c.name)) + { + static savedscore curscore; + curscore.save(o.state); + return &curscore; + } } loopv(scores) { @@ -225,20 +355,17 @@ clientscore *findscore(client &c, bool insert) } if(!insert) return NULL; savedscore &sc = scores.add(); - sc.reset(); s_strcpy(sc.name, c.name); sc.ip = c.peer->address.host; return ≻ } -void resetscores() +struct server_entity // server side version of "entity" type { - loopv(clients) if(clients[i]->type==ST_TCPIP) - { - clients[i]->score.reset(); - } - scores.setsize(0); -} + int type; + bool spawned; + int spawntime; +}; vector sents; @@ -253,14 +380,14 @@ void restoreserverstate(vector &ents) // hack: called from savegame co loopv(sents) { sents[i].spawned = ents[i].spawned; - sents[i].spawnsecs = 0; + sents[i].spawntime = 0; } } -int interm = 0, minremain = 0, mapend = 0; -bool mapreload = false, autoteam = true; +static int interm = 0, minremain = 0, gamemillis = 0, gamelimit = 0; +static bool mapreload = false, autoteam = true; -string serverpassword = ""; +static string serverpassword = ""; bool isdedicated; ENetHost *serverhost = NULL; @@ -269,6 +396,7 @@ void process(ENetPacket *packet, int sender, int chan); void sendf(int cn, int chan, const char *format, ...) { + int exclude = -1; bool reliable = false; if(*format=='r') { reliable = true; ++format; } ENetPacket *packet = enet_packet_create(NULL, MAXTRANS, reliable ? ENET_PACKET_FLAG_RELIABLE : 0); @@ -277,6 +405,18 @@ void sendf(int cn, int chan, const char *format, ...) va_start(args, format); while(*format) switch(*format++) { + case 'x': + exclude = va_arg(args, int); + break; + + case 'v': + { + int n = va_arg(args, int); + int *v = va_arg(args, int *); + loopi(n) putint(p, v[i]); + break; + } + case 'i': { int n = isdigit(*format) ? *format++-'0' : 1; @@ -284,10 +424,19 @@ void sendf(int cn, int chan, const char *format, ...) break; } case 's': sendstring(va_arg(args, const char *), p); break; + case 'm': + { + int n = va_arg(args, int); + enet_packet_resize(packet, packet->dataLength+n); + p.buf = packet->data; + p.maxlen += n; + p.put(va_arg(args, uchar *), n); + break; + } } va_end(args); enet_packet_resize(packet, p.length()); - sendpacket(cn, chan, packet); + sendpacket(cn, chan, packet, exclude); if(packet->referenceCount==0) enet_packet_destroy(packet); } @@ -296,11 +445,28 @@ void sendservmsg(char *msg, int client=-1) sendf(client, 1, "ris", SV_SERVMSG, msg); } +void spawnstate(client *c) +{ + clientstate &gs = c->state; + gs.spawnstate(smode); + gs.lifesequence++; +} + +void sendspawn(client *c) +{ + clientstate &gs = c->state; + spawnstate(c); + sendf(c->clientnum, 1, "ri6vv", SV_SPAWNSTATE, gs.lifesequence, + gs.health, gs.armour, + gs.primary, gs.gunselect, + NUMGUNS, gs.ammo, NUMGUNS, gs.mag); +} + struct sflaginfo { int state; int actor_cn; - int pos[3]; + float pos[3]; int lastupdate; } sflaginfos[2]; @@ -314,7 +480,7 @@ void sendflaginfo(int flag, int action, int cn = -1) putint(p, f.state); putint(p, action); if(f.state==CTFF_STOLEN || action==SV_FLAGRETURN) putint(p, f.actor_cn); - else if(f.state==CTFF_DROPPED) loopi(3) putint(p, f.pos[i]); + else if(f.state==CTFF_DROPPED) loopi(3) putint(p, int(f.pos[i]*DMF)); enet_packet_resize(packet, p.length()); sendpacket(cn, 1, packet); if(packet->referenceCount==0) enet_packet_destroy(packet); @@ -338,7 +504,7 @@ void flagaction(int flag, int action, int sender) { if(f.state!=CTFF_STOLEN || (sender != -1 && f.actor_cn != sender)) return; f.state = CTFF_DROPPED; - loopi(3) f.pos[i] = clients[sender]->pos[i]; + loopi(3) f.pos[i] = clients[sender]->state.o[i]; break; } case SV_FLAGRETURN: @@ -363,7 +529,7 @@ void flagaction(int flag, int action, int sender) default: return; } - f.lastupdate = lastsec; + f.lastupdate = gamemillis; sendflaginfo(flag, action); } @@ -377,24 +543,26 @@ void ctfreset() } } -int arenaround = 0; - -void arenareset() +bool canspawn(client *c, bool connecting = false) { - if(!m_arena) return; - - arenaround = 0; - loopv(clients) clients[i]->arenadeath = false; - sendf(-1, 1, "ri", SV_ARENASPAWN); + if(m_arena) + { + if(connecting && numnonlocalclients()<=2) return true; + return false; + } + return true; } -void arenacheck(int secs) +int arenaround = 0; + +void arenacheck() { - if(!m_arena || interm || secstype!=ST_EMPTY) sendspawn(clients[i]); return; } @@ -407,10 +575,10 @@ void arenacheck(int secs) if(players[i]->state==CS_DEAD) dead = true; else alive = true; } - if((dead && !alive) || clients[0]->arenadeath) + if((dead && !alive) || player1->state==CS_DEAD) { - sendf(-1, 1, "riis", SV_ARENAWIN, alive || player1->state!=CS_DEAD ? 1 : 0, player1->state==CS_DEAD ? "" : player1->name); - arenaround = secs+5; + sendf(-1, 1, "ri2", SV_ARENAWIN, player1->state==CS_ALIVE ? getclientnum() : (alive ? -2 : -1)); + arenaround = gamemillis+5000; } return; } @@ -421,13 +589,16 @@ void arenacheck(int secs) { client &c = *clients[i]; if(c.type==ST_EMPTY) continue; - if(c.arenadeath) dead = true; - else if(!alive) alive = &c; - else if(!m_teammode || strcmp(alive->team, c.team)) return; + if(c.state.state==CS_DEAD) dead = true; + else if(c.state.state==CS_ALIVE) + { + if(!alive) alive = &c; + else if(!m_teammode || strcmp(alive->team, c.team)) return; + } } if(!dead) return; - sendf(-1, 1, "riis", SV_ARENAWIN, alive ? 1 : 0, !alive ? "" : (m_teammode ? alive->team : alive->name)); - arenaround = secs+5; + sendf(-1, 1, "ri2", SV_ARENAWIN, !alive ? -1 : alive->clientnum); + arenaround = gamemillis+5000; } void sendteamtext(char *text, int sender) @@ -454,8 +625,8 @@ void disconnect_client(int n, int reason = -1) if(!clients.inrange(n) || clients[n]->type!=ST_TCPIP) return; if(m_ctf) loopi(2) if(sflaginfos[i].state==CTFF_STOLEN && sflaginfos[i].actor_cn==n) flagaction(i, SV_FLAGDROP, n); client &c = *clients[n]; - clientscore *sc = findscore(c, true); - if(sc) *sc = c.score; + savedscore *sc = findscore(c, true); + if(sc) sc->save(c.state); if(reason>=0) printf("disconnecting client (%s) [%s]\n", c.hostname, disc_reason(reason)); else printf("disconnected client (%s)\n", c.hostname); c.peer->data = (void *)-1; @@ -466,17 +637,241 @@ void disconnect_client(int n, int reason = -1) void resetitems() { sents.setsize(0); notgotitems = true; } -bool serverpickup(uint i, int sec, int sender) // server side item pickup, acknowledge first client that gets it +int spawntime(int type) { - if(i>=(uint)sents.length()) return false; - if(sents[i].spawned) + int np = 0; + loopv(clients) if(clients[i]) np++; + np = np<3 ? 4 : (np>4 ? 2 : 3); // spawn times are dependent on number of players + int sec = 0; + switch(type) { - sents[i].spawned = false; - sents[i].spawnsecs = sec; - if(sender>=0) sendf(sender, 1, "rii", SV_ITEMACC, i); - return true; + case I_CLIPS: + case I_AMMO: + case I_GRENADE: sec = np*2; break; + case I_HEALTH: sec = np*5; break; + case I_ARMOUR: sec = 20; break; + case I_AKIMBO: sec = 60; break; + } + return sec*1000; +} + +bool serverpickup(int i, int sender) // server side item pickup, acknowledge first client that gets it +{ + if(!sents.inrange(i)) return false; + server_entity &e = sents[i]; + if(!e.spawned) return false; + if(sender>=0) + { + client *cl = clients[sender]; + if(cl->type==ST_TCPIP) + { + if(cl->state.state!=CS_ALIVE || !cl->state.canpickup(e.type)) return false; + } + sendf(-1, 1, "ri3", SV_ITEMACC, i, sender); + cl->state.pickup(sents[i].type); + } + e.spawned = false; + e.spawntime = spawntime(e.type); + return true; +} + +void checkitemspawns(int diff) +{ + if(!diff) return; + loopv(sents) if(sents[i].spawntime) + { + sents[i].spawntime -= diff; + if(sents[i].spawntime<=0) + { + sents[i].spawntime = 0; + sents[i].spawned = true; + sendf(-1, 1, "ri2", SV_ITEMSPAWN, i); + } + } +} + +void serverdamage(client *target, client *actor, int damage, int gun, bool gib, const vec &hitpush = vec(0, 0, 0)) +{ + clientstate &ts = target->state; + ts.dodamage(damage); + sendf(-1, 1, "ri6", gib ? SV_GIBDAMAGE : SV_DAMAGE, target->clientnum, actor->clientnum, damage, ts.armour, ts.health); + if(target!=actor && !hitpush.iszero()) + { + vec v(hitpush); + if(!v.iszero()) v.normalize(); + sendf(target->clientnum, 1, "ri6", SV_HITPUSH, gun, damage, + int(v.x*DNF), int(v.y*DNF), int(v.z*DNF)); + } + if(ts.health<=0) + { + if(target!=actor && !isteam(target->team, actor->team)) actor->state.frags += gib ? 2 : 1; + else actor->state.frags--; + sendf(-1, 1, "ri4", gib ? SV_GIBDIED : SV_DIED, target->clientnum, actor->clientnum, actor->state.frags); + ts.state = CS_DEAD; + ts.lastdeath = gamemillis; + // don't issue respawn yet until DEATHMILLIS has elapsed + // ts.respawn(); + + if(actor->state.frags < scorethreshold) disconnect_client(actor->clientnum, DISC_AUTOKICK); + } +} + +void processevent(client *c, explodeevent &e) +{ + clientstate &gs = c->state; + switch(e.gun) + { + case GUN_GRENADE: + if(gs.grenades<1) return; + gs.grenades--; + break; + + default: + return; + } + for(int i = 1; ievents.length() && c->events[i].type==GE_HIT; i++) + { + hitevent &h = c->events[i].hit; + client *target = clients[h.target]; + if(target->type==ST_EMPTY) continue; + if(target->state.state!=CS_ALIVE || h.lifesequence!=target->state.lifesequence || h.dist<0 || h.dist>=EXPDAMRAD) continue; + + int j = 1; + for(j = 1; jevents[j].hit.target==h.target) break; + if(jstate; + int wait = e.millis - gs.lastshot; + if(!gs.isalive(gamemillis) || + (gs.gunwait && wait=NUMGUNS || + gs.mag[e.gun]<=0) + return; + if(e.gun!=GUN_KNIFE) gs.mag[e.gun]--; + gs.lastshot = e.millis; + gs.gunwait = attackdelay(e.gun); + if(e.gun==GUN_PISTOL && gs.akimbomillis>gamemillis) gs.gunwait /= 2; + sendf(-1, 1, "ri9x", SV_SHOTFX, c->clientnum, e.gun, + int(e.from[0]*DMF), int(e.from[1]*DMF), int(e.from[2]*DMF), + int(e.to[0]*DMF), int(e.to[1]*DMF), int(e.to[2]*DMF), + c->clientnum); + switch(e.gun) + { + case GUN_GRENADE: gs.grenades = min(gs.grenades+1, 8); break; + default: + { + int totalrays = 0, maxrays = e.gun==GUN_SHOTGUN ? SGRAYS : 1; + for(int i = 1; ievents.length() && c->events[i].type==GE_HIT; i++) + { + hitevent &h = c->events[i].hit; + client *target = clients[h.target]; + if(target->type==ST_EMPTY) continue; + if(target->state.state!=CS_ALIVE || h.lifesequence!=target->state.lifesequence) continue; + + int rays = e.gun==GUN_SHOTGUN ? h.info : 1; + if(rays<1) continue; + totalrays += rays; + if(totalrays>maxrays) continue; + + bool gib = false; + if(e.gun==GUN_KNIFE) gib = true; + else if(e.gun==GUN_SNIPER) gib = h.info!=0; + int damage = rays*guns[e.gun].damage; + if(e.gun==GUN_SNIPER && gib) damage *= 3; + serverdamage(target, c, damage, e.gun, gib, h.dir); + } + break; + } + } +} + +void processevent(client *c, suicideevent &e) +{ + clientstate &gs = c->state; + if(gs.state!=CS_ALIVE) return; + gs.frags--; + sendf(-1, 1, "ri4", SV_DIED, c->clientnum, c->clientnum, gs.frags); + gs.state = CS_DEAD; + gs.respawn(); +} + +void processevent(client *c, pickupevent &e) +{ + clientstate &gs = c->state; + if(!gs.isalive(gamemillis)) return; + serverpickup(e.ent, c->clientnum); +} + +void processevent(client *c, reloadevent &e) +{ + clientstate &gs = c->state; + if(!gs.isalive(gamemillis) || + e.gun=NUMGUNS || + !reloadable_gun(e.gun) || + gs.ammo[e.gun]<=0) + return; + + bool akimbo = e.gun==GUN_PISTOL && gs.akimbomillis>e.millis; + int mag = (akimbo ? 2 : 1) * magsize(e.gun), numbullets = min(gs.ammo[e.gun], mag - gs.mag[e.gun]); + if(numbullets<=0) return; + + gs.mag[e.gun] += numbullets; + gs.ammo[e.gun] -= numbullets; + + int wait = e.millis - gs.lastshot; + if(gs.gunwait && waitstate; + if(!gs.isalive(gamemillis) || gs.akimbos<=0) return; + gs.akimbos--; + gs.akimbomillis = e.millis+30000; +} + +void clearevent(client *c) +{ + int n = 1; + while(nevents.length() && c->events[n].type==GE_HIT) n++; + c->events.remove(0, n); +} + +void processevents() +{ + loopv(clients) + { + client *c = clients[i]; + if(c->type==ST_EMPTY) continue; + while(c->events.length()) + { + gameevent &e = c->events[0]; + if(e.typegamemillis) break; + switch(e.type) + { + case GE_SHOT: processevent(c, e.shot); break; + case GE_EXPLODE: processevent(c, e.explode); break; + case GE_AKIMBO: processevent(c, e.akimbo); break; + case GE_RELOAD: processevent(c, e.reload); break; + // untimed events + case GE_SUICIDE: processevent(c, e.suicide); break; + case GE_PICKUP: processevent(c, e.pickup); break; + } + clearevent(c); + } } - return false; } struct configset @@ -570,20 +965,26 @@ void resetmap(const char *newname, int newmode, int newtime = -1, bool notify = { bool lastteammode = m_teammode; smode = newmode; - minremain = newtime >= 0 ? newtime : (m_teammode ? 15 : 10); s_strcpy(smapname, newname); - mapend = lastsec+minremain*60; + minremain = newtime >= 0 ? newtime : (m_teammode ? 15 : 10); + gamemillis = 0; + gamelimit = minremain*60000; + mapreload = false; interm = 0; laststatus = lastsec-61; resetvotes(); resetitems(); - resetscores(); + scores.setsize(0); ctfreset(); - if(notify) sendf(-1, 1, "risi", SV_MAPCHANGE, smapname, smode); + if(notify) + { + sendf(-1, 1, "risi", SV_MAPCHANGE, smapname, smode); + if(smode>1 || (smode==0 && numnonlocalclients()>0)) sendf(-1, 1, "ri2", SV_TIMEUP, minremain); + } if(m_teammode && !lastteammode) shuffleteams(); - arenareset(); + if(m_arena) arenaround = 0; } void nextcfgset(bool notify = true) // load next maprotation set @@ -736,13 +1137,6 @@ void serveropcmd(int sender, int cmd, int a) sendf(-1, 1, "riii", SV_SERVOPCMD, cmd, a); } -int numnonlocalclients() -{ - int nonlocalclients = 0; - loopv(clients) if(clients[i]->type==ST_TCPIP) nonlocalclients++; - return nonlocalclients; -} - // sending of maps between clients string copyname; @@ -772,6 +1166,96 @@ ENetPacket *getmapserv(int n) return packet; } +void sendwelcome(int n) +{ + ENetPacket *packet = enet_packet_create(NULL, MAXTRANS, ENET_PACKET_FLAG_RELIABLE); + ucharbuf p(packet->data, packet->dataLength); + putint(p, SV_INITS2C); + putint(p, n); + putint(p, PROTOCOL_VERSION); + if(!smapname[0] && configsets.length()) nextcfgset(false); + int numcl = numclients(); + putint(p, smapname[0] ? numcl : -1); + putint(p, serverpassword[0] ? 1 : 0); + if(smapname[0]) + { + putint(p, SV_MAPCHANGE); + sendstring(smapname, p); + putint(p, smode); + if(smode>1 || (smode==0 && numnonlocalclients()>0)) + { + putint(p, SV_TIMEUP); + putint(p, minremain); + } + if(!configsets.length() || numcl > 1) + { + putint(p, SV_ITEMLIST); + loopv(sents) if(sents[i].spawned) + { + putint(p, i); + putint(p, sents[i].type); + } + putint(p, -1); + } + } + client *c = clients[n]; + if(c->type == ST_TCPIP && serveroperator() != -1) sendserveropinfo(n); + if(autoteam && numcl>1) + { + putint(p, SV_FORCETEAM); + putint(p, freeteam()); + } + if(m_mp(smode)) + { + if(!canspawn(c, true)) + { + putint(p, SV_FORCEDEATH); + putint(p, n); + sendf(-1, 1, "ri2x", SV_FORCEDEATH, n, n); + } + else + { + clientstate &gs = c->state; + spawnstate(c); + putint(p, SV_SPAWNSTATE); + putint(p, gs.lifesequence); + putint(p, gs.health); + putint(p, gs.armour); + putint(p, gs.primary); + putint(p, gs.gunselect); + loopi(NUMGUNS) putint(p, gs.ammo[i]); + loopi(NUMGUNS) putint(p, gs.mag[i]); + } + } + if(clients.length()>1) + { + putint(p, SV_RESUME); + loopv(clients) + { + client &c = *clients[i]; + if(c.type!=ST_TCPIP || c.clientnum==n) continue; + putint(p, c.clientnum); + putint(p, c.state.state); + putint(p, c.state.lifesequence); + putint(p, c.state.gunselect); + putint(p, c.state.flagscore); + putint(p, c.state.frags); + } + putint(p, -1); + } + putint(p, SV_SERVOPCMD); + putint(p, SOPCMD_AUTOTEAM); + putint(p, autoteam); + if(motd) + { + putint(p, SV_TEXT); + sendstring(motd, p); + } + enet_packet_resize(packet, p.length()); + sendpacket(n, 1, packet); + if(smapname[0] && m_ctf) loopi(2) sendflaginfo(i, -1, n); +} + int checktype(int type, client *cl) { if(cl && cl->type==ST_LOCAL) return type; @@ -779,7 +1263,7 @@ int checktype(int type, client *cl) static int edittypes[] = { SV_EDITENT, SV_EDITH, SV_EDITT, SV_EDITS, SV_EDITD, SV_EDITE }; if(cl && smode!=1) loopi(sizeof(edittypes)/sizeof(int)) if(type == edittypes[i]) return -1; // server only messages - static int servtypes[] = { SV_INITS2C, SV_MAPRELOAD, SV_SERVMSG, SV_ITEMACC, SV_ITEMSPAWN, SV_TIMEUP, SV_CDIS, SV_PONG, SV_RESUME, SV_FLAGINFO, SV_ARENASPAWN, SV_ARENAWIN, SV_CLIENT }; + static int servtypes[] = { SV_INITS2C, SV_MAPRELOAD, SV_SERVMSG, SV_GIBDAMAGE, SV_DAMAGE, SV_HITPUSH, SV_SHOTFX, SV_DIED, SV_SPAWNSTATE, SV_FORCEDEATH, SV_ITEMACC, SV_ITEMSPAWN, SV_TIMEUP, SV_CDIS, SV_PONG, SV_RESUME, SV_FLAGINFO, SV_ARENAWIN, SV_CLIENT }; if(cl) loopi(sizeof(servtypes)/sizeof(int)) if(type == servtypes[i]) return -1; return type; } @@ -796,21 +1280,19 @@ void process(ENetPacket *packet, int sender, int chan) // sender may be -1 if(cl && !cl->isauthed) { - int nonlocalclients = numnonlocalclients(); - int primaryreason = serverpassword[0] ? DISC_WRONGPW : (mastermode!=MM_OPEN ? DISC_MASTERMODE : (nonlocalclients>maxclients ? DISC_MAXCLIENTS : (isbanned(sender) ? DISC_MBAN : DISC_NONE))); - - if(primaryreason == DISC_NONE) cl->isauthed = true; - else if(chan==0) return; - else if(chan!=1 || getint(p)!=SV_PWD) disconnect_client(sender, primaryreason); + if(chan==0) return; + else if(chan!=1 || getint(p)!=SV_CONNECT) disconnect_client(sender, DISC_TAGT); else { + int nonlocalclients = numnonlocalclients(); getstring(text, p); - if(adminpasswd[0] && !strcmp(text, adminpasswd)) // pass admins always through + cl->state.nextprimary = getint(p); + if(adminpasswd && adminpasswd[0] && !strcmp(text, adminpasswd)) // pass admins always through { cl->isauthed = true; changeclientrole(sender, CR_ADMIN, NULL, true); loopv(bans) if(bans[i].address.host == cl->peer->address.host) { bans.remove(i); break; } // remove admin bans - if(nonlocalclients>maxclients) for(int i = 0; i < nonlocalclients; i++) if(i != sender && clients[i]->type==ST_TCPIP) + if(nonlocalclients>maxclients) loopi(nonlocalclients) if(i != sender && clients[i]->type==ST_TCPIP) { disconnect_client(i, DISC_MAXCLIENTS); // disconnect someone else to fit maxclients again break; @@ -823,9 +1305,11 @@ void process(ENetPacket *packet, int sender, int chan) // sender may be -1 } else if(mastermode==MM_PRIVATE) disconnect_client(sender, DISC_MASTERMODE); else if(nonlocalclients>maxclients) disconnect_client(sender, DISC_MAXCLIENTS); - else disconnect_client(sender, DISC_MBAN); + else if(isbanned(sender)) disconnect_client(sender, DISC_MBAN); + else cl->isauthed = true; } if(!cl->isauthed) return; + sendwelcome(sender); } if(packet->flags&ENET_PACKET_FLAG_RELIABLE) reliablemessages = true; @@ -837,10 +1321,6 @@ void process(ENetPacket *packet, int sender, int chan) // sender may be -1 int curmsg; while((curmsg = p.length()) < p.maxlen) switch(type = checktype(getint(p), cl)) { - case SV_PWD: - getstring(text, p); - break; - case SV_TEAMTEXT: getstring(text, p); filtertext(text, text); @@ -866,18 +1346,17 @@ void process(ENetPacket *packet, int sender, int chan) // sender may be -1 s_strncpy(cl->name, text, MAXNAMELEN+1); if(newclient && cl->type==ST_TCPIP) { - clientscore *sc = findscore(*cl, false); + savedscore *sc = findscore(*cl, false); if(sc) { - cl->score = *sc; - sendf(-1, 1, "ri5", SV_RESUME, sender, sc->frags, sc->flags, sc->lifesequence); + sc->restore(cl->state); + sendf(-1, 1, "ri8", SV_RESUME, sender, cl->state.state, cl->state.lifesequence, cl->state.gunselect, sc->flagscore, sc->frags, -1); } } getstring(text, p); filtertext(cl->team, text, false, MAXTEAMLEN); QUEUE_STR(text); getint(p); - getint(p); QUEUE_MSG; break; } @@ -892,13 +1371,13 @@ void process(ENetPacket *packet, int sender, int chan) // sender may be -1 resetmap(text, reqmode); break; } - + case SV_ITEMLIST: { int n; while((n = getint(p))!=-1) if(notgotitems) { - server_entity se = { false, 0 }; + server_entity se = { getint(p), false, 0 }; while(sents.length()<=n) sents.add(se); sents[n].spawned = true; } @@ -910,11 +1389,114 @@ void process(ENetPacket *packet, int sender, int chan) // sender may be -1 case SV_ITEMPICKUP: { int n = getint(p); - serverpickup(n, getint(p), sender); + if(cl->type==ST_LOCAL) serverpickup(n, sender); + else + { + gameevent &pickup = cl->events.add(); + pickup.type = GE_PICKUP; + pickup.pickup.ent = n; + } + break; + } + + case SV_WEAPCHANGE: + { + int gunselect = getint(p); + cl->state.gunselect = gunselect; QUEUE_MSG; break; } - + + case SV_TRYSPAWN: + if(cl->state.state!=CS_DEAD || !canspawn(cl)) break; + if(cl->state.lastdeath) cl->state.respawn(); + sendspawn(cl); + break; + + case SV_SPAWN: + { + int ls = getint(p), gunselect = getint(p); + if(cl->state.state!=CS_DEAD || ls!=cl->state.lifesequence || !canspawn(cl)) break; + if(cl->state.lastdeath) cl->state.respawn(); + cl->state.state = CS_ALIVE; + cl->state.gunselect = gunselect; + QUEUE_MSG; + break; + } + + case SV_SUICIDE: + { + gameevent &suicide = cl->events.add(); + suicide.type = GE_SUICIDE; + break; + } + + case SV_SHOOT: + { + gameevent &shot = cl->events.add(); + shot.type = GE_SHOT; + #define seteventmillis(event) \ + { \ + if(cl->gameoffset<0) \ + { \ + cl->gameoffset = gamemillis - getint(p); \ + event.millis = gamemillis; \ + } \ + else event.millis = cl->gameoffset + getint(p); \ + } + seteventmillis(shot.shot); + shot.shot.gun = getint(p); + loopk(3) shot.shot.from[k] = getint(p)/DMF; + loopk(3) shot.shot.to[k] = getint(p)/DMF; + int hits = getint(p); + loopk(hits) + { + gameevent &hit = cl->events.add(); + hit.type = GE_HIT; + hit.hit.target = getint(p); + hit.hit.lifesequence = getint(p); + hit.hit.info = getint(p); + loopk(3) hit.hit.dir[k] = getint(p)/DNF; + } + break; + } + + case SV_EXPLODE: + { + gameevent &exp = cl->events.add(); + exp.type = GE_EXPLODE; + seteventmillis(exp.explode); + exp.explode.gun = getint(p); + int hits = getint(p); + loopk(hits) + { + gameevent &hit = cl->events.add(); + hit.type = GE_HIT; + hit.hit.target = getint(p); + hit.hit.lifesequence = getint(p); + hit.hit.dist = getint(p)/DMF; + loopk(3) hit.hit.dir[k] = getint(p)/DNF; + } + break; + } + + case SV_AKIMBO: + { + gameevent &akimbo = cl->events.add(); + akimbo.type = GE_AKIMBO; + seteventmillis(akimbo.akimbo); + break; + } + + case SV_RELOAD: + { + gameevent &reload = cl->events.add(); + reload.type = GE_RELOAD; + seteventmillis(reload.reload); + reload.reload.gun = getint(p); + break; + } + case SV_PING: sendf(sender, 1, "ii", SV_PONG, getint(p)); break; @@ -930,7 +1512,7 @@ void process(ENetPacket *packet, int sender, int chan) // sender may be -1 #endif return; } - loopi(3) clients[cn]->pos[i] = getuint(p); + loopi(3) clients[cn]->state.o[i] = getuint(p)/DMF; getuint(p); loopi(6) getint(p); if(cl->type==ST_TCPIP) @@ -996,24 +1578,11 @@ void process(ENetPacket *packet, int sender, int chan) // sender may be -1 break; } - case SV_FRAGS: - cl->score.frags = getint(p); - if(cl->score.frags < scorethreshold) disconnect_client(sender, DISC_AUTOKICK); - QUEUE_MSG; - break; - case SV_FLAGS: - cl->score.flags = getint(p); + cl->state.flagscore = getint(p); QUEUE_MSG; break; - case SV_GIBDIED: - case SV_DIED: - getint(p); - if(m_arena) cl->arenadeath = true; - cl->score.lifesequence++; - QUEUE_MSG; - break; default: { int size = msgsizelookup(type); @@ -1027,60 +1596,6 @@ void process(ENetPacket *packet, int sender, int chan) // sender may be -1 if(p.overread() && sender>=0) disconnect_client(sender, DISC_EOP); } -void send_welcome(int n) -{ - ENetPacket *packet = enet_packet_create(NULL, MAXTRANS, ENET_PACKET_FLAG_RELIABLE); - ucharbuf p(packet->data, packet->dataLength); - putint(p, SV_INITS2C); - putint(p, n); - putint(p, PROTOCOL_VERSION); - if(!smapname[0] && configsets.length()) nextcfgset(false); - int numcl = numclients(); - putint(p, smapname[0] ? numcl : -1); - putint(p, serverpassword[0] ? 1 : 0); - if(smapname[0]) - { - putint(p, SV_MAPCHANGE); - sendstring(smapname, p); - putint(p, smode); - if(!configsets.length() || numcl > 1) - { - putint(p, SV_ITEMLIST); - loopv(sents) if(sents[i].spawned) putint(p, i); - putint(p, -1); - } - } - if(clients[n]->type == ST_TCPIP && serveroperator() != -1) sendserveropinfo(n); - loopv(clients) - { - client &c = *clients[i]; - if(c.type!=ST_TCPIP || c.clientnum==n) continue; - if(!c.score.frags && !c.score.flags) continue; - putint(p, SV_RESUME); - putint(p, c.clientnum); - putint(p, c.score.frags); - putint(p, c.score.flags); - putint(p, c.score.lifesequence); - } - if(autoteam && numcl>1) - { - putint(p, SV_FORCETEAM); - putint(p, freeteam()); - } - putint(p, SV_SERVOPCMD); - putint(p, SOPCMD_AUTOTEAM); - putint(p, autoteam); - if(motd) - { - putint(p, SV_TEXT); - sendstring(motd, p); - } - enet_packet_resize(packet, p.length()); - sendpacket(n, 1, packet); - if(smapname[0] && m_ctf) loopi(2) sendflaginfo(i, -1, n); - if(m_arena && numcl<=2) clients[n]->arenadeath = false; -} - void localclienttoserver(int chan, ENetPacket *packet) { process(packet, 0, chan); @@ -1103,12 +1618,12 @@ client &addclient() void checkintermission() { - if(!minremain) + if(minremain>0) { - interm = lastsec+10; - mapend = lastsec+1000; + minremain = gamemillis>=gamelimit ? 0 : (gamelimit - gamemillis + 60000 - 1)/60000; + sendf(-1, 1, "ri2", SV_TIMEUP, minremain); } - sendf(-1, 1, "rii", SV_TIMEUP, minremain--); + if(!interm && minremain<=0) interm = gamemillis+10000; } /*void startintermission() { minremain = 0; checkintermission(); }*/ @@ -1133,31 +1648,36 @@ void sendworldstate() if(flush) enet_host_flush(serverhost); } -void serverslice(int seconds, unsigned int timeout) // main server update, called from cube main loop in sp, or dedicated server loop +static int servmillis = 0; + +void serverslice(uint timeout) // main server update, called from cube main loop in sp, or dedicated server loop { - loopv(sents) // spawn entities when timer reached - { - if(sents[i].spawnsecs && (sents[i].spawnsecs -= seconds-lastsec)<=0) - { - sents[i].spawnsecs = 0; - sents[i].spawned = true; - sendf(-1, 1, "rii", SV_ITEMSPAWN, i); - } - } +#ifdef STANDALONE + int nextmillis = (int)enet_time_get(); +#else + int nextmillis = isdedicated ? (int)enet_time_get() : lastmillis; +#endif + int diff = nextmillis - servmillis; + gamemillis += diff; + servmillis = nextmillis; - if(m_ctf) loopi(2) + if(minremain>0) { - sflaginfo &f = sflaginfos[i]; - if(f.state==CTFF_DROPPED && seconds-f.lastupdate>30) flagaction(i, SV_FLAGRESET, -1); + processevents(); + checkitemspawns(diff); + if(m_ctf) loopi(2) + { + sflaginfo &f = sflaginfos[i]; + if(f.state==CTFF_DROPPED && gamemillis-f.lastupdate>30000) flagaction(i, SV_FLAGRESET, -1); + } + if(m_arena) arenacheck(); } - if(m_arena) arenacheck(seconds); - - lastsec = seconds; int nonlocalclients = numnonlocalclients(); - if((smode>1 || (smode==0 && nonlocalclients)) && seconds>mapend-minremain*60) checkintermission(); - if(interm && seconds>interm) + if((smode>1 || (gamemode==0 && nonlocalclients)) && gamemillis-diff>0 && gamemillis/60000!=(gamemillis-diff)/60000) + checkintermission(); + if(interm && gamemillis>interm) { interm = 0; @@ -1174,11 +1694,11 @@ void serverslice(int seconds, unsigned int timeout) // main server update, cal if(!isdedicated) return; // below is network only - serverms(smode, numclients(), minremain, smapname, seconds); + serverms(smode, numclients(), minremain, smapname, servmillis); - if(seconds-laststatus>60) // display bandwidth stats, useful for server ops + if(servmillis-laststatus>60*1000) // display bandwidth stats, useful for server ops { - laststatus = seconds; + laststatus = servmillis; if(nonlocalclients || bsend || brec) { printf("status: %d remote clients, %.1f send, %.1f rec (K/sec)\n", nonlocalclients, bsend/60.0f/1024, brec/60.0f/1024); @@ -1203,7 +1723,6 @@ void serverslice(int seconds, unsigned int timeout) // main server update, cal char hn[1024]; s_strcpy(c.hostname, (enet_address_get_host_ip(&c.peer->address, hn, sizeof(hn))==0) ? hn : "unknown"); printf("client connected (%s)\n", c.hostname); - send_welcome(c.clientnum); break; } @@ -1236,6 +1755,7 @@ void cleanupserver() if(serverhost) enet_host_destroy(serverhost); } +#ifndef STANDALONE void localdisconnect() { loopv(clients) if(clients[i]->type==ST_LOCAL) zapclient(i); @@ -1246,8 +1766,9 @@ void localconnect() client &c = addclient(); c.type = ST_LOCAL; s_strcpy(c.hostname, "local"); - send_welcome(c.clientnum); + sendintro(); } +#endif void initserver(bool dedicated, int uprate, char *sdesc, char *ip, char *master, char *passwd, int maxcl, char *maprot, char *adminpwd, char *srvmsg, int scthreshold) { @@ -1279,7 +1800,7 @@ void initserver(bool dedicated, int uprate, char *sdesc, char *ip, char *master, printf("dedicated server started, waiting for clients...\nCtrl-C to exit\n\n"); atexit(enet_deinitialize); atexit(cleanupserver); - for(;;) serverslice(/*enet_time_get_sec()*/time(NULL), 5); + for(;;) serverslice(5); } } diff --git a/source/src/serverms.cpp b/source/src/serverms.cpp index c0e26ccb..8f3b7a02 100644 --- a/source/src/serverms.cpp +++ b/source/src/serverms.cpp @@ -73,15 +73,15 @@ uchar *stripheader(uchar *b) ENetSocket mssock = ENET_SOCKET_NULL; ENetAddress msaddress = { ENET_HOST_ANY, ENET_PORT_ANY }; ENetAddress masterserver = { ENET_HOST_ANY, 80 }; -int updmaster = 0; +int lastupdmaster = 0; string masterbase; string masterpath; uchar masterrep[MAXTRANS]; ENetBuffer masterb; -void updatemasterserver(int seconds) +void updatemasterserver(int millis) { - if(seconds>updmaster) // send alive signal to masterserver every hour of uptime + if(millis/(60*60*1000)!=lastupdmaster) // send alive signal to masterserver every hour of uptime { s_sprintfd(path)("%sregister.do?action=add", masterpath); s_sprintfd(agent)("AssaultCube Server %d", AC_VERSION); @@ -89,7 +89,7 @@ void updatemasterserver(int seconds) masterrep[0] = 0; masterb.data = masterrep; masterb.dataLength = MAXTRANS-1; - updmaster = seconds+60*60; + lastupdmaster = millis/(60*60*1000); } } @@ -150,10 +150,10 @@ uchar *retrieveservers(uchar *buf, int buflen) ENetSocket pongsock = ENET_SOCKET_NULL; string serverdesc; -void serverms(int mode, int numplayers, int minremain, char *smapname, int seconds) +void serverms(int mode, int numplayers, int minremain, char *smapname, int millis) { checkmasterreply(); - updatemasterserver(seconds); + updatemasterserver(millis); // reply all server info requests ENetBuffer buf; diff --git a/source/src/sound.cpp b/source/src/sound.cpp index 76ba2df1..3ae3af51 100644 --- a/source/src/sound.cpp +++ b/source/src/sound.cpp @@ -207,7 +207,6 @@ void updatevol() void playsoundc(int n) { - if(demoplayback) return; addmsg(SV_SOUND, "i", n); playsound(n); } diff --git a/source/src/tools.h b/source/src/tools.h index 6b5ffc1f..cf69992a 100644 --- a/source/src/tools.h +++ b/source/src/tools.h @@ -118,12 +118,6 @@ extern bool cmpb(void *b, int n, enet_uint32 c); extern bool cmpf(char *fn, enet_uint32 c); extern void endianswap(void *, int, int); -#if SDL_BYTEORDER == SDL_BIG_ENDIAN -#define CMPB(b, c) (true) -#else -#define CMPB(b, c) (cmpb(b, sizeof(b), ENET_HOST_TO_NET_32(c))) -#endif - #define loopv(v) if(false) {} else for(int i = 0; i<(v).length(); i++) #define loopvj(v) if(false) {} else for(int j = 0; j<(v).length(); j++) #define loopvk(v) if(false) {} else for(int k = 0; k<(v).length(); k++) @@ -184,28 +178,6 @@ struct databuf len = maxlen; flags |= OVERREAD; } - - void forcelen(int nl) - { - len = nl < 0 ? 0 : (nl > maxlen ? maxlen : nl); - flags = 0; - } - - void forcemaxlen(int nl) - { - maxlen = nl < 0 ? 0 : nl; - flags = 0; - } - - int advance(int sz) - { - sz = min(sz, maxlen-len); - len += sz; - return sz; - } - - T* current() { return buf + len; } - }; typedef databuf charbuf; diff --git a/source/src/weapon.cpp b/source/src/weapon.cpp index bc54b23c..7033ce05 100644 --- a/source/src/weapon.cpp +++ b/source/src/weapon.cpp @@ -3,27 +3,11 @@ #include "cube.h" #include "bot/bot.h" -struct guninfo { short sound, reload, reloadtime, attackdelay, damage, projspeed, part, spread, recoil, magsize, mdl_kick_rot, mdl_kick_back; bool isauto; }; - -const int SGRAYS = 21; //down from 21, must be 32 or less (default) -const float SGSPREAD = 2; vec sg[SGRAYS]; char *gunnames[NUMGUNS] = { "knife", "pistol", "shotgun", "subgun", "sniper", "assault", "grenade" }; -guninfo guns[NUMGUNS] = -{ - { S_KNIFE, S_NULL, 0, 500, 50, 0, 0, 1, 1, 1, 0, 0, false }, - { S_PISTOL, S_RPISTOL, 1400, 170, 19, 0, 0, 80, 10, 8, 6, 5, false }, // *SGRAYS - { S_SHOTGUN, S_RSHOTGUN, 2400, 1000, 5, 0, 0, 1, 35, 7, 9, 9, false }, //reload time is for 1 shell from 7 too powerful to 6 - { S_SUBGUN, S_RSUBGUN, 1650, 80, 16, 0, 0, 70, 15, 30, 1, 2, true }, - { S_SNIPER, S_RSNIPER, 1950, 1500, 85, 0, 0, 60, 50, 5, 4, 4, false }, - { S_ASSAULT, S_RASSAULT, 2000, 130, 24, 0, 0, 20, 40, 15, 0, 2, true }, //recoil was 44 - { S_NULL, S_NULL, 1000, 1200, 150, 20, 6, 1, 1, 1, 3, 1, false }, -}; - int nadetimer = 2000; // detonate after $ms -bool gun_changed = false; void checkweaponswitch() { @@ -31,7 +15,7 @@ void checkweaponswitch() int timeprogress = lastmillis-player1->lastaction; if(timeprogress>WEAPONCHANGE_TIME) { - gun_changed = true; + addmsg(SV_WEAPCHANGE, "ri", player1->gunselect); player1->weaponchanging = false; if(player1->gunwait) player1->gunwait = max(player1->gunwait - (lastmillis-player1->lastaction), 0); player1->lastaction = player1->gunwait ? lastmillis : 0; @@ -114,7 +98,6 @@ COMMAND(setscope, ARG_1INT); void reload(playerent *d) { - if(demoplayback) { setvar("gamespeed", getvar("gamespeed") != 100 ? 100 : 35); return; } if(!d || d->state!=CS_ALIVE || d->reloading || d->weaponchanging) return; if(!reloadable_gun(d->gunselect) || d->ammo[d->gunselect]<=0) return; if(d->mag[d->gunselect] >= (has_akimbo(d) ? 2 : 1)*guns[d->gunselect].magsize) return; @@ -127,9 +110,6 @@ void reload(playerent *d) d->gunwait += guns[d->gunselect].reloadtime; int numbullets = (has_akimbo(d) ? 2 : 1)*guns[d->gunselect].magsize - d->mag[d->gunselect]; - - if(has_akimbo(d)) numbullets = guns[d->gunselect].magsize * 2 - d->mag[d->gunselect]; - if(numbullets > d->ammo[d->gunselect]) numbullets = d->ammo[d->gunselect]; d->mag[d->gunselect] += numbullets; d->ammo[d->gunselect] -= numbullets; @@ -137,17 +117,13 @@ void reload(playerent *d) if(has_akimbo(d)) playsoundc(S_RAKIMBO); else if(d->type==ENT_BOT) playsound(guns[d->gunselect].reload, &d->o); else playsoundc(guns[d->gunselect].reload); + + if(d==player1) addmsg(SV_RELOAD, "ri2", lastmillis, d->gunselect); } void selfreload() { reload(player1); } COMMANDN(reload, selfreload, ARG_NONE); -int reloadtime(int gun) { return guns[gun].reloadtime; } -int attackdelay(int gun) { return guns[gun].attackdelay; } -int magsize(int gun) { return guns[gun].magsize; } -int kick_rot(int gun) { return guns[gun].mdl_kick_rot; } -int kick_back(int gun) { return guns[gun].mdl_kick_back; } - void createrays(vec &from, vec &to) // create random spread of rays for the shotgun { float f = to.dist(from)*SGSPREAD/1000; @@ -161,9 +137,10 @@ void createrays(vec &from, vec &to) // create random spread of rays } } -bool intersect(dynent *d, vec &from, vec &to, vec *end) // if lineseg hits entity bounding box +static inline bool intersect(const vec &o, const vec &rad, const vec &from, const vec &to, vec *end) // if lineseg hits entity bounding box { - vec v = to, w = d->o, *p; + const vec *p; + vec v = to, w = o; v.sub(from); w.sub(from); float c1 = w.dot(v); @@ -181,15 +158,36 @@ bool intersect(dynent *d, vec &from, vec &to, vec *end) // if lineseg hits ent } } - return p->x <= d->o.x+d->radius - && p->x >= d->o.x-d->radius - && p->y <= d->o.y+d->radius - && p->y >= d->o.y-d->radius - && p->z <= d->o.z+d->aboveeye - && p->z >= d->o.z-d->eyeheight; + if(p->x <= o.x+rad.x + && p->x >= o.x-rad.x + && p->y <= o.y+rad.y + && p->y >= o.y-rad.y + && p->z <= o.z+rad.z + && p->z >= o.z-rad.z) + { + if(end) *end = *p; + return true; + } + return false; } -playerent *intersectclosest(vec &from, vec &to, int &n, playerent *at) +bool intersect(dynent *d, const vec &from, const vec &to, vec *end) +{ + vec o(d->o); + o.z += (d->aboveeye - d->eyeheight)/2; + return intersect(o, vec(d->radius, d->radius, (d->aboveeye + d->eyeheight)/2), from, to, end); +} + +bool intersect(entity *e, const vec &from, const vec &to, vec *end) +{ + mapmodelinfo &mmi = getmminfo(e->attr2); + if(!&mmi || !mmi.h) return false; + + float lo = float(S(e->x, e->y)->floor+mmi.zoff+e->attr3); + return intersect(vec(e->x, e->y, lo+mmi.h/2.0f), vec(mmi.rad, mmi.rad, mmi.h/2.0f), from, to, end); +} + +playerent *intersectclosest(vec &from, vec &to, playerent *at) { playerent *best = NULL; float bestdist = 1e16f; @@ -197,7 +195,6 @@ playerent *intersectclosest(vec &from, vec &to, int &n, playerent *at) { best = player1; bestdist = at->o.dist(player1->o); - n = getclientnum(); } loopv(players) { @@ -209,7 +206,6 @@ playerent *intersectclosest(vec &from, vec &to, int &n, playerent *at) { best = o; bestdist = dist; - n = i; } } return best; @@ -217,68 +213,103 @@ playerent *intersectclosest(vec &from, vec &to, int &n, playerent *at) playerent *playerincrosshair() { - if(demoplayback) return NULL; - int n; - return intersectclosest(player1->o, worldpos, n, player1); + return intersectclosest(player1->o, worldpos, player1); } -const int MAXPROJ = 100; -struct projectile { vec o, to; float speed; playerent *owner; int gun; bool inuse, local; }; -projectile projs[MAXPROJ]; +struct projectile { vec o, to; float speed; playerent *owner; int gun; bool local; }; +vector projs; -void projreset() { loopi(MAXPROJ) projs[i].inuse = false; } +void projreset() { projs.setsize(0); } void newprojectile(vec &from, vec &to, float speed, bool local, playerent *owner, int gun) { - loopi(MAXPROJ) - { - projectile *p = &projs[i]; - if(p->inuse) continue; - p->inuse = true; - p->o = from; - p->to = to; - p->speed = speed; - p->local = local; - p->owner = owner; - p->gun = gun; - return; - } + projectile &p = projs.add(); + p.o = from; + p.to = to; + p.speed = speed; + p.local = local; + p.owner = owner; + p.gun = gun; } -void hit(int target, int damage, playerent *d, playerent *at, bool gib=false) +void removeprojectiles(playerent *owner) { - if(d==player1 || d->type==ENT_BOT) dodamage(damage, -1, at, gib, d); + loopv(projs) if(projs[i].owner==owner) projs.remove(i--); +} + +void damageeffect(int damage, playerent *d) +{ + particle_splash(3, damage/10, 1000, d->o); +} + +struct hitmsg +{ + int target, lifesequence, info; + ivec dir; +}; +vector hits; + +void hit(int damage, playerent *d, playerent *at, const vec &vel, int gun, bool gib, int info) +{ + if(d==player1 || d->type==ENT_BOT || !m_mp(gamemode)) d->hitpush(damage, vel, at, gun); + + if(!m_mp(gamemode)) dodamage(damage, d, at, gib); else { - addmsg(gib ? SV_GIBDAMAGE : SV_DAMAGE, "ri3", target, damage, d->lifesequence); - playsound(S_PAIN1+rnd(5), &d->o); + hitmsg &h = hits.add(); + h.target = d->clientnum; + h.lifesequence = d->lifesequence; + h.info = info; + if(d==player1) + { + h.dir = ivec(0, 0, 0); + d->damageroll(damage); + damageblend(damage); + playsound(S_PAIN6); + } + else + { + h.dir = ivec(int(vel.x*DNF), int(vel.y*DNF), int(vel.z*DNF)); + damageeffect(damage, d); + playsound(S_PAIN1+rnd(5), &d->o); + } } - particle_splash(3, damage/10, 1000, d->o); - demodamage(damage, d->o); } -const float RL_DAMRAD = 10; +void hitpush(int damage, playerent *d, playerent *at, vec &from, vec &to, int gun, bool gib, int info) +{ + vec v(to); + v.sub(from); + v.normalize(); + hit(damage, d, at, v, gun, gib, info); +} -void radialeffect(playerent *o, vec &v, int cn, int qdam, playerent *at) +float expdist(playerent *o, vec &dir, const vec &v) +{ + vec middle = o->o; + middle.z += (o->aboveeye-o->eyeheight)/2; + float dist = middle.dist(v, dir); + dir.div(dist); + if(dist<0) dist = 0; + return dist; +} + +void radialeffect(playerent *o, vec &v, int qdam, playerent *at, int gun) { if(o->state!=CS_ALIVE) return; - vec temp; - float dist = o->o.dist(v, temp); - dist -= 2; // account for eye distance imprecision - if(distvel.add(temp.mul((RL_DAMRAD-dist)*damage/800)); + int damage = (int)(qdam*(1-dist/EXPDAMRAD)); + hit(damage, o, at, dir, gun, true, int(dist*DMF)); } } -void splash(projectile *p, vec &v, vec &vold, int notthisplayer, int qdam) +void splash(projectile &p, vec &v, vec &vold, playerent *notthis, int qdam) { particle_splash(0, 50, 300, v); - p->inuse = false; - if(p->gun!=GUN_GRENADE) + if(p.gun!=GUN_GRENADE) { playsound(S_FEXPLODE, &v); // no push? @@ -288,67 +319,76 @@ void splash(projectile *p, vec &v, vec &vold, int notthisplayer, int qdam) //playsound(S_RLHIT, &v); particle_fireball(5, v); addscorchmark(v); - dodynlight(vold, v, 0, 0, p->owner); - if(!p->local) return; - radialeffect(player1, v, -1, qdam, p->owner); + dodynlight(vold, v, 0, 0, p.owner); + if(!p.local) return; + radialeffect(player1, v, qdam, p.owner, p.gun); loopv(players) { - if(i==notthisplayer) continue; playerent *o = players[i]; - if(!o) continue; - radialeffect(o, v, i, qdam, p->owner); + if(!o || o==notthis) continue; + radialeffect(o, v, qdam, p.owner, p.gun); } } } -inline void projdamage(playerent *o, projectile *p, vec &v, int i, int qdam) +bool projdamage(playerent *o, projectile &p, vec &v, int qdam) { - if(o->state!=CS_ALIVE) return; - if(intersect(o, p->o, v)) - { - splash(p, v, p->o, i, qdam); - hit(i, qdam, o, p->owner, true); //fixme - } + if(o->state!=CS_ALIVE || !intersect(o, p.o, v)) return false; + splash(p, v, p.o, o, qdam); + vec dir; + expdist(o, dir, v); + hit(qdam, o, p.owner, dir, p.gun, true, 0); + return true; } void moveprojectiles(float time) { - loopi(MAXPROJ) + loopv(projs) { - projectile *p = &projs[i]; - if(!p->inuse) continue; - //int qdam = guns[p->gun].damage*(p->owner->quadmillis ? 4 : 1); - int qdam = guns[p->gun].damage; + projectile &p = projs[i]; + int qdam = guns[p.gun].damage; vec v; - float dist = p->to.dist(p->o, v); - float dtime = dist*1000/p->speed; + float dist = p.to.dist(p.o, v); + float dtime = dist*1000/p.speed; if(time>dtime) dtime = time; - v.mul(time/dtime).add(p->o); - if(p->local) + v.mul(time/dtime).add(p.o); + bool exploded = false; + hits.setsizenodelete(0); + if(p.local) { loopvj(players) { playerent *o = players[j]; - if(!o) continue; - projdamage(o, p, v, j, qdam); + if(!o || p.owner==o || o->o.reject(v, 10.0f)) continue; + if(projdamage(o, p, v, qdam)) exploded = true; } - if(p->owner!=player1) projdamage(player1, p, v, -1, qdam); + if(p.owner!=player1 && projdamage(player1, p, v, qdam)) exploded = true; } - if(p->inuse) + if(!exploded) { - if(time==dtime) splash(p, v, p->o, -1, qdam); + if(time==dtime) + { + splash(p, v, p.o, NULL, qdam); + exploded = true; + } else { - if(p->gun==GUN_GRENADE) { dodynlight(p->o, v, 0, 255, p->owner); particle_splash(5, 2, 200, v); } - else { particle_splash(1, 1, 200, v); particle_splash(guns[p->gun].part, 1, 1, v); } + if(p.gun==GUN_GRENADE) { dodynlight(p.o, v, 0, 255, p.owner); particle_splash(5, 2, 200, v); } + else { particle_splash(1, 1, 200, v); particle_splash(guns[p.gun].part, 1, 1, v); } // Added by Rick traceresult_s tr; - TraceLine(p->o, v, p->owner, true, &tr); - if (tr.collided) splash(p, v, p->o, -1, qdam); + TraceLine(p.o, v, p.owner, true, &tr); + if(tr.collided) splash(p, v, p.o, NULL, qdam); // End add } } - p->o = v; + if(exploded) + { + addmsg(SV_EXPLODE, "ri2iv", lastmillis, p.gun, + hits.length(), hits.length()*sizeof(hitmsg)/sizeof(int), hits.getbuf()); + projs.remove(i--); + } + else p.o = v; } } @@ -361,6 +401,11 @@ bounceent *newbounceent() return p; } +void removebounceents(playerent *owner) +{ + loopv(bounceents) if(bounceents[i]->owner==owner) { delete bounceents[i]; bounceents.remove(i--); } +} + void explode_nade(bounceent *i); void movebounceents() @@ -473,7 +518,7 @@ void thrownade(playerent *d, const vec &vel, bounceent *p) if(d==player1) { player1->lastaction = lastmillis; - addmsg(SV_SHOT, "ri8", d->gunselect, (int)(d->o.x*DMF), (int)(d->o.y*DMF), (int)(d->o.z*DMF), (int)(vel.x*DMF), (int)(vel.y*DMF), (int)(vel.z*DMF), lastmillis-p->millis); + addmsg(SV_THROWNADE, "ri7", int(d->o.x*DMF), int(d->o.y*DMF), int(d->o.z*DMF), int(vel.x*DMF), int(vel.y*DMF), int(vel.z*DMF), lastmillis-p->millis); } } @@ -498,7 +543,7 @@ bounceent *new_nade(playerent *d, int millis = 0) d->inhandnade = p; d->thrownademillis = 0; - if(d==player1 && !demoplayback) playsoundc(S_GRENADEPULL); + if(d==player1) playsoundc(S_GRENADEPULL); return p; } @@ -536,13 +581,12 @@ void shootv(int gun, vec &from, vec &to, playerent *d, bool local, int nademilli addbullethole(from, to); addshotline(d, from, to); particle_splash(0, 5, 250, to); - if(!local && !CMPB(guns, 0xc7d70531)) player1->clientnum += 250*(int)to.dist(from); break; } case GUN_GRENADE: { - if(d!=player1 || demoplayback) + if(d!=player1) { bounceent *p = new_nade(d, nademillis); thrownade(d, to, p); @@ -561,15 +605,6 @@ void shootv(int gun, vec &from, vec &to, playerent *d, bool local, int nademilli } } -void hitpush(int target, int damage, playerent *d, playerent *at, vec &from, vec &to, bool gib = false) -{ - hit(target, damage, d, at, gib); - vec v; - float dist = to.dist(from, v); - v.mul(damage/dist/50); - d->vel.add(v); -} - void shorten(vec &from, vec &to, vec &target) { target.sub(from).normalize().mul(from.dist(to)).add(from); @@ -580,35 +615,33 @@ vec hitpos; void raydamage(vec &from, vec &to, playerent *d) { - int i = -1, gdam = guns[d->gunselect].damage; + int gdam = guns[d->gunselect].damage; playerent *o = NULL; if(d->gunselect==GUN_SHOTGUN) { uint done = 0; playerent *cl = NULL; - int n = -1; for(;;) { bool raysleft = false; - int damage = 0; + int hitrays = 0; o = NULL; - loop(r, SGRAYS) if((done&(1<o, sg[r]); } else raysleft = true; } - if(damage) hitpush(i, damage, o, d, from, to); + if(hitrays) hitpush(hitrays*gdam, o, d, from, to, d->gunselect, false, hitrays); if(!raysleft) break; } } - else if((o = intersectclosest(from, to, i, d))) + else if((o = intersectclosest(from, to, d))) { bool gib = false; if(d->gunselect==GUN_KNIFE) gib = true; @@ -620,7 +653,7 @@ void raydamage(vec &from, vec &to, playerent *d) gib = true; } - hitpush(i, gdam, o, d, from, to, gib); + hitpush(gdam, o, d, from, to, d->gunselect, gib, gib ? 1 : 0); shorten(from, o->o, to); } } @@ -628,7 +661,7 @@ void raydamage(vec &from, vec &to, playerent *d) void spreadandrecoil(vec &from, vec &to, playerent *d) { //nothing special for a knife - if (d->gunselect==GUN_KNIFE || d->gunselect==GUN_GRENADE) return; + if(d->gunselect==GUN_KNIFE || d->gunselect==GUN_GRENADE) return; //spread vec unitv; @@ -681,7 +714,6 @@ bool hasammo(playerent *d) // bot mod VARP(autoreload, 0, 1, 1); -const int grenadepulltime = 650; bool akimboside = false; void shoot(playerent *d, vec &targ) @@ -712,11 +744,16 @@ void shoot(playerent *d, vec &targ) if(!hasammo(d)) return; d->mag[d->gunselect]--; new_nade(d); - d->gunwait = grenadepulltime; + d->gunwait = attackdelay(d->gunselect); d->lastaction = lastmillis; d->lastattackgun = d->gunselect; + + addmsg(SV_SHOOT, "ri2i6i", lastmillis, d->gunselect, + (int)(from.x*DMF), (int)(from.y*DMF), (int)(from.z*DMF), + (int)(to.x*DMF), (int)(to.y*DMF), (int)(to.z*DMF), + 0); } - else if(!d->attacking && d->inhandnade && attacktime>grenadepulltime) thrownade(d, d->inhandnade); + else if(!d->attacking && d->inhandnade && attacktime>attackdelay(d->gunselect)) thrownade(d, d->inhandnade); return; } else @@ -759,14 +796,21 @@ void shoot(playerent *d, vec &targ) to.add(unitv); } if(d->gunselect==GUN_SHOTGUN) createrays(from, to); + + hits.setsizenodelete(0); + + if(!guns[d->gunselect].projspeed) raydamage(from, to, d); - if(has_akimbo(d)) d->gunwait = guns[d->gunselect].attackdelay / 2; //make akimbo pistols shoot twice as fast as normal pistol - else d->gunwait = guns[d->gunselect].attackdelay; + d->gunwait = attackdelay(d->gunselect); + if(has_akimbo(d)) d->gunwait /= 2; // make akimbo pistols shoot twice as fast as normal pistol shootv(d->gunselect, from, to, d, true, 0); - if(d->type==ENT_PLAYER) addmsg(SV_SHOT, "ri8", d->gunselect, (int)(from.x*DMF), (int)(from.y*DMF), (int)(from.z*DMF), (int)(to.x*DMF), (int)(to.y*DMF), (int)(to.z*DMF), 0); - - if(guns[d->gunselect].projspeed || d->gunselect==GUN_GRENADE) return; - - raydamage(from, to, d); + if(d==player1) + { + addmsg(SV_SHOOT, "ri2i6iv", lastmillis, d->gunselect, + (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()); + } } + diff --git a/source/src/worldio.cpp b/source/src/worldio.cpp index 5343a06f..5fc047d9 100644 --- a/source/src/worldio.cpp +++ b/source/src/worldio.cpp @@ -210,7 +210,6 @@ extern void preparectf(bool cleanonly = false); void load_world(char *mname) // still supports all map formats that have existed since the earliest cube betas! { preparectf(true); - stopifrecording(); cleardlights(); pruneundos(); setnames(mname);