diff --git a/data/maps.cfg b/data/maps.cfg index 2288efe0..cb453437 100644 --- a/data/maps.cfg +++ b/data/maps.cfg @@ -1,9 +1,10 @@ _currentmaps = "cosmic duabo flux fz_burn illusion indust1 neo_falls neo_noir precipice realm retrograde ruins" _shadowmaps = "color" _parkourmaps = "ftdeluxe secondevermap" +_racemaps = "race_test" _ctfmaps = "hr retrograde zigguraut" -_allmaps = (concat $_currentmaps $_shadowmaps $_ctfmaps $_parkourmaps) +_allmaps = (concat $_currentmaps $_shadowmaps $_ctfmaps $_parkourmaps $_racemaps) _genmapitems = [ looplist curmap $arg1 [ @@ -63,6 +64,13 @@ newgui maps [ guilist [ guistrut 15 1; _genmapitems $_parkourmaps ] guilist [ guistrut 15 1 ] _showmapshot + ] + guitab "t:gui.map.race" + guilist [ + guistrut 17 1 + guilist [ guistrut 15 1; _genmapitems $_racemaps ] + guilist [ guistrut 15 1 ] + _showmapshot ] guitab "t:gui.map.custom" guilist [ diff --git a/data/menus.cfg b/data/menus.cfg index 6829e610..b8b77551 100644 --- a/data/menus.cfg +++ b/data/menus.cfg @@ -310,8 +310,9 @@ newgui gamemode [ guibutton "Insta-Tactics" "mode 18; showgui maps" guibutton "Team Instagib Tactics" "mode 19; showgui maps" guibutton "Grenade Battle" "mode 20; showgui maps" - guibutton "Last Man Standing (WIP)" "mode 22; showgui maps" - guibutton "Explosive CTF (WIP)" "mode 23; showgui maps" + guibutton "Last Man Standing (WIP)" "mode 21; showgui maps" + guibutton "Explosive CTF (WIP)" "mode 22; showgui maps" + guibutton "Race Mode (^fs^f6DEV^fr)" "mode 25; showgui maps" ] ] ] "t:gui.gamemode" @@ -1692,6 +1693,9 @@ newentgui jumppad "Z Y X" "0 200 0 200 0 200" newentgui sound "Type Radius Size" "0 20 0 500 0 500" newentgui particles "Type" "0 13" newentgui flag "Direction Team" "0 360 0 2" +newentgui start "Direction" "0 360" +newentgui checkpoint "Direction ID" "0 360 0 50" +newentgui finish "Direction" "0 360" contexteditgui = [ if $hmapedit [showgui brushes] [ diff --git a/data/stdedit.cfg b/data/stdedit.cfg index 4505e466..8f6916e2 100644 --- a/data/stdedit.cfg +++ b/data/stdedit.cfg @@ -61,6 +61,7 @@ enttypelist = [ base spotlight flag + start checkpoint finish ] enttypeselect = [ @@ -85,6 +86,9 @@ ent_action_particles = [ entproperty 0 ( * $arg1 1 ) ] ent_action_sound = [ entproperty 0 ( * $arg1 1 ) ] ent_action_cycle = [ entset ( if ( > $arg1 -1 ) [ result $arg2 ] [ result $arg3 ] ) ] ent_action_flag = [ entproperty 0 ( * $arg1 1 ) ] +ent_action_start = [ entproperty 0 ( * $arg1 15 ) ] +ent_action_checkpoint = [ entproperty 0 ( * $arg1 15 ) ] +ent_action_finish = [ entproperty 0 ( * $arg1 15 ) ] //////// Copy and Paste ////////////// @@ -266,15 +270,15 @@ genenthudmapmodel = [ genenthudplayerstart = [ if (= (ea 1) 0) [ - format "yaw: %1; team: %2 (neutral)" (ea 0) (ea 1) // general + format "direction: %1; team: %2 (neutral)" (ea 0) (ea 1) // general ] [ if (= (ea 1) 1) [ - format "yaw: %1; team: ^f3%2 (red)" (ea 0) (ea 1) // red + format "direction: %1; team: ^f3%2 (red)" (ea 0) (ea 1) // red ] [ if (= (ea 1) 2) [ - format "yaw: %1; team: ^f1%2 (blue)" (ea 0) (ea 1) // blue + format "direction: %1; team: ^f1%2 (blue)" (ea 0) (ea 1) // blue ] [ - format "yaw: %1; team: %2" (ea 0) (ea 1) // invalid team + format "direction: %1; team: %2" (ea 0) (ea 1) // invalid team ] ] ] @@ -282,15 +286,15 @@ genenthudplayerstart = [ genenthudflag = [ if (= (ea 1) 0) [ - format "yaw: %1; team: %2 (neutral)" (ea 0) (ea 1) // general + format "direction: %1; team: %2 (neutral)" (ea 0) (ea 1) // general ] [ if (= (ea 1) 1) [ - format "yaw: %1; team: ^f3%2 (red)" (ea 0) (ea 1) // red + format "direction: %1; team: ^f3%2 (red)" (ea 0) (ea 1) // red ] [ if (= (ea 1) 2) [ - format "yaw: %1; team: ^f1%2 (blue)" (ea 0) (ea 1) // blue + format "direction: %1; team: ^f1%2 (blue)" (ea 0) (ea 1) // blue ] [ - format "yaw: %1; team: %2" (ea 0) (ea 1) // invalid team + format "direction: %1; team: %2" (ea 0) (ea 1) // invalid team ] ] ] @@ -335,6 +339,18 @@ genenthudenvmap = [ format "radius: %1" $radius ] +genenthudstart = [ + format "direction: %1" (ea 0) +] + +genenthudcheckpoint = [ + format "direction: %1; id: ^f2%2" (ea 0) (ea 1) +] + +genenthudfinish = [ + format "direction: %1" (ea 0) +] + genenthud = [ if (strcmp (getalias (concatword "genenthud" (et))) "") [ result "No Information" @@ -439,6 +455,7 @@ echovshaderparam = [echo ***vshaderparam (getvshaderparamnames $getseltex) ( // echovshaderparamspecscale = [echo ***shaderparam specscale (getvshaderparam $getseltex specscale)] findpickupents = [entfind ammo;entfind health;] +findraceents = [entfind start;entfind checkpoint;entfind finish;] prettysky = [ skybox "skyboxes/remus/sky01" diff --git a/packages/lang/en_US.cfg b/packages/lang/en_US.cfg index 4ea5f350..b2dcddc7 100644 --- a/packages/lang/en_US.cfg +++ b/packages/lang/en_US.cfg @@ -227,6 +227,7 @@ ds "en_US" [ "gui.map.shadow" "Shadow" "gui.map.ctf" "CTF" "gui.map.parkour" "Parkour" + "gui.map.race" "Race" "gui.map.custom" "Custom maps" "gui.texgui.textures" "Textures" diff --git a/packages/models/race/gem/gem.obj b/packages/models/race/gem/gem.obj new file mode 100644 index 00000000..061a967c --- /dev/null +++ b/packages/models/race/gem/gem.obj @@ -0,0 +1,34 @@ +# Blender 3.3.1 +# www.blender.org +o Gem +v -1.000000 0.000000 1.000000 +v 1.000000 0.000000 1.000000 +v -1.000000 0.000000 -1.000000 +v 1.000000 0.000000 -1.000000 +v 0.000000 1.500000 0.000000 +v 0.000000 -1.500000 0.000000 +vn -0.0000 -0.5547 -0.8321 +vn -0.0000 0.5547 -0.8321 +vn -0.0000 0.5547 0.8321 +vn 0.8321 0.5547 -0.0000 +vn -0.8321 0.5547 -0.0000 +vn -0.0000 -0.5547 0.8321 +vn 0.8321 -0.5547 -0.0000 +vn -0.8321 -0.5547 -0.0000 +vt 0.166667 0.500000 +vt 0.833333 0.500000 +vt 0.166667 0.500000 +vt 0.166667 0.500000 +vt 0.833333 0.500000 +vt 0.833333 0.500000 +vt 0.500000 1.000000 +vt 0.500000 0.000000 +s 0 +f 3/4/1 4/6/1 6/8/1 +f 4/6/2 3/4/2 5/7/2 +f 1/1/3 2/2/3 5/7/3 +f 2/3/4 4/6/4 5/7/4 +f 3/5/5 1/1/5 5/7/5 +f 2/2/6 1/1/6 6/8/6 +f 4/6/7 2/3/7 6/8/7 +f 1/1/8 3/5/8 6/8/8 diff --git a/packages/models/race/gem/gem.pdn b/packages/models/race/gem/gem.pdn new file mode 100644 index 00000000..61b956a6 Binary files /dev/null and b/packages/models/race/gem/gem.pdn differ diff --git a/packages/models/race/gem/gem.png b/packages/models/race/gem/gem.png new file mode 100644 index 00000000..61f269e5 Binary files /dev/null and b/packages/models/race/gem/gem.png differ diff --git a/packages/models/race/gem/gem_mask.png b/packages/models/race/gem/gem_mask.png new file mode 100644 index 00000000..049650eb Binary files /dev/null and b/packages/models/race/gem/gem_mask.png differ diff --git a/packages/models/race/gem/obj.cfg b/packages/models/race/gem/obj.cfg new file mode 100644 index 00000000..863f69f1 --- /dev/null +++ b/packages/models/race/gem/obj.cfg @@ -0,0 +1,3 @@ +objload gem.obj +objskin * gem.png gem_mask.png +mdlscale 1000 diff --git a/src/engine/physics.cpp b/src/engine/physics.cpp index 1beac1bb..edf4d8ec 100644 --- a/src/engine/physics.cpp +++ b/src/engine/physics.cpp @@ -675,7 +675,7 @@ static inline bool plcollide(physent *d, const vec &dir, physent *o) bool plcollide(physent *d, const vec &dir, bool insideplayercol) // collide with player { - if(d->type==ENT_CAMERA || d->state!=CS_ALIVE) return false; + if(d->type==ENT_CAMERA || d->state!=CS_ALIVE || d->racing) return false; int lastinside = collideinside; physent *insideplayer = NULL; loopdynentcache(x, y, d->o, d->radius) diff --git a/src/fpsgame/entities.cpp b/src/fpsgame/entities.cpp index 9f5f653a..a747e1d4 100644 --- a/src/fpsgame/entities.cpp +++ b/src/fpsgame/entities.cpp @@ -47,15 +47,13 @@ namespace entities static const char * const entmdlnames[] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, // health, ammo + NULL, NULL, "race/gem", // race start, finish, checkpoint NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, - NULL, - NULL, NULL, - NULL, - NULL, NULL, - NULL, NULL, - NULL + NULL, NULL, // teleport, teledest + NULL, // base + NULL, NULL, NULL, NULL, NULL, + NULL // flag }; return entmdlnames[type]; } @@ -79,7 +77,7 @@ namespace entities case I_AMMO: case I_HEALTH: break; case RACE_START: case RACE_FINISH: case RACE_CHECKPOINT: - if (!m_race) continue; + if (!m_race || !m_edit) continue; break; } const char *mdl = entmdlname(i); @@ -111,6 +109,8 @@ namespace entities case TELEPORT: if(e.attr2 < 0) continue; break; + case RACE_CHECKPOINT: + break; default: if(!e.spawned() || e.type < I_AMMO || e.type > I_HEALTH) continue; } @@ -594,6 +594,14 @@ namespace entities } } + void raceyaw(extentity& e) + { + gle::colorf(0, 1, 1); + vec dir; + vecfromyawpitch(e.attr1, 0, 1, 0, dir); + renderentarrow(e, dir, 4); + } + void entradius(extentity &e, bool color) { int maxcheckpoints = 0; @@ -613,19 +621,24 @@ namespace entities break; case RACE_START: + gle::colorf(1, 0, 0); loopv(ents) { // successor if(ents[i]->type == RACE_CHECKPOINT && ents[i]->attr2 == 1) { renderentarrow(e, vec(ents[i]->o).sub(e.o).normalize(), e.o.dist(ents[i]->o)); } // precessor + /* if(ents[i]->type == RACE_FINISH) { renderentarrow(*ents[i], vec(e.o).sub(ents[i]->o).normalize(), ents[i]->o.dist(e.o)); } + */ } + raceyaw(e); break; case RACE_CHECKPOINT: + gle::colorf(1, 0, 0); loopv(ents) { // successor if(ents[i]->type == RACE_CHECKPOINT && (e.attr2+1) == ents[i]->attr2) { @@ -635,21 +648,29 @@ namespace entities if(ents[i]->type == RACE_CHECKPOINT && (e.attr2-1) == ents[i]->attr2) { renderentarrow(*ents[i], vec(e.o).sub(ents[i]->o).normalize(), ents[i]->o.dist(e.o)); } + else if (ents[i]->type == RACE_START && e.attr2 == 1) { + renderentarrow(*ents[i], vec(e.o).sub(ents[i]->o).normalize(), ents[i]->o.dist(e.o)); + } } + raceyaw(e); break; case RACE_FINISH: + gle::colorf(1, 0, 0); loopv(ents) if(ents[i]->type == RACE_CHECKPOINT && ents[i]->attr2 > maxcheckpoints) { maxcheckpoints = ents[i]->attr2; } // successor + /* loopv(ents) if(ents[i]->type == RACE_START) { renderentarrow(e, vec(ents[i]->o).sub(e.o).normalize(), e.o.dist(ents[i]->o)); } + */ // precessor loopv(ents) if(ents[i]->type == RACE_CHECKPOINT && ents[i]->attr2 == maxcheckpoints) { renderentarrow(*ents[i], vec(e.o).sub(ents[i]->o).normalize(), ents[i]->o.dist(e.o)); } + raceyaw(e); break; case FLAG: diff --git a/src/fpsgame/fps.cpp b/src/fpsgame/fps.cpp index 403d92f4..00c796aa 100644 --- a/src/fpsgame/fps.cpp +++ b/src/fpsgame/fps.cpp @@ -342,6 +342,7 @@ namespace game { int tag = cmode ? cmode->getspawngroup(d) : 0; if(m_teammode && !cmode) tag = (!strcmp(d->team, "red") ? 1 : (!strcmp(d->team, "blue") ? 2 : 0)); + else if (m_race && cmode) { cmode->pickspawn(d); return; } //conoutf("spawn tag: %d, team: %s, cmode: %s", tag, d->team, cmode ? "true" : "false"); findplayerspawn(d, -1, tag); } diff --git a/src/fpsgame/game.h b/src/fpsgame/game.h index 3f01cb57..e652b3a4 100644 --- a/src/fpsgame/game.h +++ b/src/fpsgame/game.h @@ -129,7 +129,7 @@ static struct gamemodeinfo { "Explosive CTF", M_INSTA | M_BOTTOMLESS | M_TEAM | M_CTF | M_NOITEMS | M_ECTF, "Rockets! Grenades! Instagib! CTF! Exciting!"}, // 22 { "Test Mode", M_TEST, "It might be something stupid, or it might be cool. It also might crash your game."}, // 23 { "Reverse One Flag CTF", M_CTF | M_TEAM | M_R1CTF, "Capture the center flag and return it to the enemy base to score points!"}, // 24 - { "Race Mode", M_RACE | M_BOTTOMLESS | M_PARKOUR, "Race: Be the first who completes 3 laps. Kill people to repulse them." }, // 25 + { "Race Mode", M_RACE | M_BOTTOMLESS | M_PARKOUR, "Get to the finish as fast as you can!" }, // 25 }; #define STARTGAMEMODE (-1) @@ -776,6 +776,7 @@ namespace game virtual void setup() {} virtual void checkitems(fpsent *d) {} virtual int respawnwait(fpsent *d, int delay = 0) { return 0; } + virtual void pickspawn(fpsent* d) { findplayerspawn(d); } virtual int getspawngroup(fpsent *d) { return 0; } virtual float ratespawn(fpsent *d, const extentity &e) { return 1.0f; } virtual void senditems(packetbuf &p) {} diff --git a/src/fpsgame/race.h b/src/fpsgame/race.h index 45b29b3f..49645f1d 100644 --- a/src/fpsgame/race.h +++ b/src/fpsgame/race.h @@ -1,7 +1,12 @@ // race.h: client and server state for race gamemode #ifndef PARSEMESSAGES +#define raceteamname(s) (!strcmp(s, "red") ? 1 : (!strcmp(s, "blue") ? 2 : 0)) + #ifdef SERVMODE + +extern void sendspawn(clientinfo *ci); + struct raceservmode : servmode #else @@ -45,6 +50,7 @@ struct raceclientmode : clientmode d->racetime = 0; d->racerank = -1; d->racestate = 0; + d->racing = true; } } @@ -68,14 +74,14 @@ struct raceclientmode : clientmode temp.index = i; v.add(temp); } - /*putint(p, N_SPAWNLOC); + putint(p, N_SPAWNLOC); putint(p, v.length()); loopv(v){ spawnloc &sploc = v[i]; loopk(3) putint(p, int(sploc.o[k]*DMF)); putint(p, sploc.team); putint(p, sploc.index); - }*/ + } } void drawblip(fpsent *d, float x, float y, float s, const vec &pos, float size_factor) @@ -111,7 +117,7 @@ struct raceclientmode : clientmode { fpsent *p = players[i]; if(p == player1 || p->state!=CS_ALIVE) continue; - if(!m_teammode || strcmp(p->team, player1->team) != 0) settexture("packages/hud/blip_red.png", 3); + if(!m_teammode || raceteamname(p->team)) settexture("packages/hud/blip_red.png", 3); else settexture("packages/hud/blip_blue.png", 3); drawblip(d, x, y, s, p->o, 2.0f); } @@ -151,6 +157,8 @@ struct raceclientmode : clientmode pickspawnbyenttype(d, PLAYERSTART); } else if (d->racestate == 1) { pickspawnbyenttype(d, RACE_CHECKPOINT); + } else if (d->racestate == 0) { + pickspawnbyenttype(d, RACE_START); } else { if(entities::ents.inrange(myspawnloc)) { extentity& e = *entities::ents[myspawnloc]; @@ -215,7 +223,7 @@ struct raceclientmode : clientmode // int gotcheckpoints[]; // int timepenalties[]; -#define COUNTDOWNSECONDS 3 +#define COUNTDOWNSECONDS 5 void setup() { maxcheckpoint = getmaxcheckpoint(); @@ -247,15 +255,15 @@ struct raceclientmode : clientmode m = e.attr2; } } - conoutf("MAX CHECKPOINT: %d", m); + //conoutf("MAX CHECKPOINT: %d", m); return m; } bool parsespawnloc(ucharbuf &p, bool commit) { int numsploc = getint(p); - for(int i = 0; i < int(numsploc); ++i){ + loopi(numsploc){ vec o; - for(int k = 0; k < 3; ++k) o[k] = max(getint(p)/DMF, 0.0f); + loopk(3) o[k] = max(getint(p)/DMF, 0.0f); int team = getint(p), index = getint(p); if(p.overread()) break; if(m_teammode ? team < 1 || team > 2 : team) return false; @@ -272,7 +280,7 @@ struct raceclientmode : clientmode if(notgotspawnlocations) return; switch(sequence){ case 0: - if(totalmillis - timecounter >= 10000) { + if(totalmillis - timecounter >= 1000) { sendservmsg("Map load complete (grannies left behind)."); } else { loopv(spawnlocs){ @@ -292,12 +300,12 @@ struct raceclientmode : clientmode int remaining = COUNTDOWNSECONDS*1000 - (totalmillis - timecounter); if(remaining <= 0){ sequence = 2; - sendservmsg("GO!"); + sendservmsg("\f4GO!"); timestarted = totalmillis; forcepaused(false); } else if(remaining/1000 != countdown){ - defformatstring(msg, "- %d -", countdown--); + defformatstring(msg, "\f3- %d -", countdown--); sendservmsg(msg); } break; @@ -315,13 +323,18 @@ struct raceclientmode : clientmode bool checkfinished() { if(interm) return false; - int allplayersfinished = true; - loopv(raceinfos) if (raceinfos[i]->timefinished == -1 && raceinfos[i]->cn != -1) allplayersfinished = false; + bool allplayersfinished = true; + loopv(raceinfos) { + //defformatstring(msg, "raceinfo, timefinished:%d cn:%d", raceinfos[i]->timefinished, raceinfos[i]->cn); + //sendservmsg(msg); + if (raceinfos[i]->timefinished == -1 && raceinfos[i]->cn != -1) + allplayersfinished = false; + } return allplayersfinished; } void update() { - if (totalmillis > lastupdatecheck + 1000) { + if (totalmillis > lastupdatecheck + 10) { lastupdatecheck = totalmillis; if(checkfinished()) server::startintermission(); loopv(clients) { @@ -352,7 +365,7 @@ struct raceclientmode : clientmode timepenalties = raceinfos[i]->timepenalties; } loopv(raceinfos) if(raceinfos[i]->cn != cn) { - conoutf("cn:%d gotcheckpoints:%d", raceinfos[i]->cn, raceinfos[i]->gotcheckpoints); + //conoutf("cn:%d gotcheckpoints:%d", raceinfos[i]->cn, raceinfos[i]->gotcheckpoints); if(raceinfos[i]->timefinished > timefinished || raceinfos[i]->gotcheckpoints > gotcheckpoints || (raceinfos[i]->timefinished == timefinished && raceinfos[i]->gotcheckpoints == gotcheckpoints && raceinfos[i]->timepenalties > timepenalties)) rank++; } return rank; @@ -370,9 +383,11 @@ struct raceclientmode : clientmode return totalmillis - ci->state.racetime; } + + void sendfinishannounce(clientinfo *ci) { int rank = ci->state.racerank; - defformatstring(msg, "%d%s", rank, (rank==1 ? "st" : (rank==2 ? "nd" : (rank==3 ? "rd": "th")))); + defformatstring(msg, "%d%s", rank, getordinal(rank)); sendservmsg(msg); } @@ -406,8 +421,6 @@ struct raceclientmode : clientmode raceinfos.deletecontents(); } - #define raceteamname(s) (!strcmp(s, "red") ? 1 : (!strcmp(s, "blue") ? 2 : 0)) - void sendspawnlocs(bool resuscitate = false){ vector activepl; loopv(clients){ @@ -417,7 +430,7 @@ struct raceclientmode : clientmode } vector pool[3]; loopv(spawnlocs) pool[spawnlocs[i]->team].add(spawnlocs[i]); - /*loopi(3) pool[i].shuffle(); + loopi(3) pool[i].shuffle(); for(int i = 0; i < activepl.length(); i++){ vector& tpool = pool[m_teammode ? raceteamname(activepl[i]->team) : 0]; if(tpool.length()){ @@ -426,7 +439,7 @@ struct raceclientmode : clientmode if(resuscitate) sendspawn(activepl[i]); tpool.removeunordered(0); } - }*/ + } } bool canspawn(clientinfo *ci, bool connecting) { @@ -434,7 +447,7 @@ struct raceclientmode : clientmode /* loopv(raceinfos) if(raceinfos[i]->cn == ci->clientnum){ if(raceinfos[i]->timefinished > 0) return false; } */ - if (connecting) return true; + if (connecting || ci->state.aitype > 0) return true; if(notgotspawnlocations) {conoutf("not got spawn locations yet"); return false; } int i = 0; for(; i < spawnlocs.length(); i++) if(spawnlocs[i]->cn == ci->clientnum) break; @@ -471,7 +484,7 @@ extern raceclientmode racemode; #elif SERVMODE -#define RACELAPS 3 +#define RACELAPS 1 case N_RACEFINISH: { if(smode==&racemode && cq) { @@ -480,14 +493,14 @@ case N_RACEFINISH: cq->state.racelaps++; cq->state.racerank = racemode.getrank(cq->clientnum); sendf(-1, 1, "ri7", N_RACEINFO, cq->clientnum, cq->state.racestate, cq->state.racelaps, cq->state.racecheckpoint, racemode.getracetime(cq), cq->state.racerank); - conoutf("racelabs:%d RACELAPS:%d timefinished:%d", cq->state.racelaps, RACELAPS, racemode.raceinfos[i]->timefinished); + conoutf("laps:%d RACELAPS:%d timefinished:%d cn:%d", cq->state.racelaps, RACELAPS, racemode.raceinfos[i]->timefinished, racemode.raceinfos[i]->cn); if (cq->state.racelaps == RACELAPS) { cq->state.racestate = 2; racemode.raceinfos[i]->timefinished = totalmillis - racemode.timestarted; racemode.sendfinishannounce(cq); - sendf(-1, 1, "ri ", N_RACEFINISH, cq->clientnum); + sendf(-1, 1, "ri", N_RACEFINISH, cq->clientnum); // cq->state.state = CS_FINISHED; - // sendf(-1, 1, "ri4", N_DIED, cq->clientnum, cq->clientnum, cq->state.frags); + sendf(-1, 1, "ri4", N_DIED, cq->clientnum, cq->clientnum, cq->state.frags); } else { defformatstring(msg, "%d %s REMAINING", RACELAPS - cq->state.racelaps, RACELAPS - cq->state.racelaps != 1 ? "LAPS" : "LAP"); sendservmsg(msg); @@ -505,7 +518,7 @@ case N_RACESTART: cq->state.racetime = totalmillis; cq->state.racerank = racemode.getrank(cq->clientnum); cq->state.racestate = 1; - sendf(-1, 1, "ri ", N_RACESTART, cq->clientnum); + sendf(-1, 1, "ri", N_RACESTART, cq->clientnum); sendf(-1, 1, "ri7", N_RACEINFO, cq->clientnum, cq->state.racestate, cq->state.racelaps, cq->state.racecheckpoint, 0, cq->state.racerank); } } @@ -526,6 +539,14 @@ case N_RACECHECKPOINT: break; } +case N_SPAWNLOC: +{ + if (smode == &racemode) + if (!racemode.parsespawnloc(p, (ci->state.state != CS_SPECTATOR || ci->privilege || ci->local) && !strcmp(ci->clientmap, smapname))) + disconnect_client(sender, DISC_MSGERR); + break; +} + #else case N_RACEINFO: @@ -536,7 +557,7 @@ case N_RACEINFO: int checkpoint = getint(p); int time = getint(p); int rank = getint(p); - conoutf("N_RACEINFO cn:%d lap:%d checkpoint:%d", rcn, lap, checkpoint, time); + //conoutf("N_RACEINFO state:%d cn:%d lap:%d checkpoint:%d", state, rcn, lap, checkpoint, time); fpsent *d = rcn==player1->clientnum ? player1 : getclient(rcn); if(m_race && d) { d->racestate = state; @@ -570,4 +591,10 @@ case N_RACEFINISH: } } +case N_SPAWNLOC: +{ + racemode.myspawnloc = getint(p); + break; +} + #endif diff --git a/src/fpsgame/scoreboard.cpp b/src/fpsgame/scoreboard.cpp index a789e59e..a6320e3a 100644 --- a/src/fpsgame/scoreboard.cpp +++ b/src/fpsgame/scoreboard.cpp @@ -383,17 +383,18 @@ namespace game loopscoregroup(o, { switch (o->racestate) { case 0: - rightjustified(g.textf("%s", 0xFFFFDD, NULL, "")); + rightjustified(g.textf("%s", 0xFFFFDD, NULL, " ")); break; case 1: case 2: - rightjustified(g.textf("%02d", 0xFFFFDD, NULL, o->racerank)); + rightjustified(g.textf("%d%s", 0xFFFFDD, NULL, o->racerank, getordinal(o->racerank))); break; } }); g.poplist(); } + /* if (showracelaps) { g.space(2); @@ -415,6 +416,7 @@ namespace game }); g.poplist(); } + */ if (showracecheckpoints) { @@ -422,32 +424,54 @@ namespace game g.pushlist(); g.strut(5); rightjustified(g.text("check", COL_GRAY)) + loopscoregroup(o, { + switch (o->racestate) { + case 0: + rightjustified(g.textf("%s", 0xFFFFDD, NULL, "start")); + break; + case 1: + rightjustified(g.textf("%02d", 0xFFFFDD, NULL, o->racecheckpoint)); + break; + case 2: + rightjustified(g.textf("%s", 0xFFFFDD, NULL, "finished")); + break; + } + }); + /* loopscoregroup(o, { if (o->racestate == 1) { rightjustified(g.textf("%02d", 0xFFFFDD, NULL, o->racecheckpoint)); } else { - rightjustified(g.textf("%s", 0xFFFFDD, NULL, "")); + rightjustified(g.textf("%s", 0xFFFFDD, NULL, " ")); } }); + */ g.poplist(); } - // why is this broken -Y if (showracetime) { g.space(2); g.pushlist(); g.strut(7); + rightjustified(g.text("time", COL_GRAY)) loopscoregroup(o, { if (o->racestate >= 1) { - int secs = max(o->racetime, 0) / 1000; + int ms = max(o->racetime, 0); + int secs = ms / 1000; int mins = secs / 60; + ms %= 1000; secs %= 60; - rightjustified(g.textf("%d:%02d", 0xFFFFDD, NULL, mins, secs)); + if (o->racestate == 2) { + rightjustified(g.textf("%d:%02d.%03d", COL_GREEN, NULL, mins, secs, ms)); + } + else { + rightjustified(g.textf("%d:%02d", 0xFFFFDD, NULL, mins, secs)); + } } else { - rightjustified(g.textf("%s", 0xFFFFDD, NULL, "")); + rightjustified(g.textf("%s", 0xFFFFDD, NULL, " ")); } }); g.poplist(); @@ -705,6 +729,29 @@ namespace game score = g->score; if(numgroups > 1) score2 = groups[1]->score; } + else if (m_race) { + int color = hudscoreplayercolor; + vec2 offset = vec2(hudscorex, hudscorey).mul(vec2(w, h).div(hudscorescale)); + defformatstring(buf, "%d%s", p->racerank, getordinal(p->racerank)); + int tw = 0, th = 0; + text_bounds(buf, tw, th); + + int fw = 0, fh = 0; + text_bounds("000nd", fw, fh); + fw = max(fw, tw); + + offset.x -= tw / 2.0f; + offset.y -= th / 2.0f; + + pushhudmatrix(); + hudmatrix.scale(hudscorescale, hudscorescale, 1); + flushhudmatrix(); + + if(p->racestate == 1) draw_text(buf, int(offset.x), int(offset.y), (color >> 16) & 0xFF, (color >> 8) & 0xFF, color & 0xFF, hudscorealpha); + + pophudmatrix(); + return; + } else { if(hudscoreself) score = m_parkour ? p->deaths : p->frags; diff --git a/src/fpsgame/server.cpp b/src/fpsgame/server.cpp index 26dd4839..3ab2934c 100644 --- a/src/fpsgame/server.cpp +++ b/src/fpsgame/server.cpp @@ -779,6 +779,7 @@ namespace server virtual void changeteam(clientinfo *ci, const char *oldteam, const char *newteam) {} virtual void initclient(clientinfo *ci, packetbuf &p, bool connecting) {} virtual void update() {} + virtual void updatelimbo() {} virtual void cleanup() {} virtual void setup() {} virtual void newmap() {} @@ -2462,6 +2463,7 @@ namespace server if(smode) smode->update(); } } + else if (smode) smode->updatelimbo(); while(bannedips.length() && bannedips[0].expire-totalmillis <= 0) bannedips.remove(0); loopv(connects) if(totalmillis-connects[i]->connectmillis>15000) disconnect_client(connects[i]->clientnum, DISC_TIMEOUT); diff --git a/src/shared/ents.h b/src/shared/ents.h index 92ecc8aa..89e158ab 100644 --- a/src/shared/ents.h +++ b/src/shared/ents.h @@ -87,7 +87,9 @@ struct physent // base entity type, can be affe uchar physstate; // one of PHYS_* above uchar state, editstate; // one of CS_* above uchar type; // one of ENT_* above - uchar collidetype; // one of COLLIDE_* above + uchar collidetype; // one of COLLIDE_* above + + bool racing; // used to disable player collisions in racing modes bool blocked; // used by physics to signal ai @@ -95,6 +97,7 @@ struct physent // base entity type, can be affe radius(4.1f), eyeheight(14), aboveeye(1), xradius(4.1f), yradius(4.1f), zmargin(0), state(CS_ALIVE), editstate(CS_ALIVE), type(ENT_PLAYER), collidetype(COLLIDE_ELLIPSE), + racing(false), blocked(false) { reset(); } diff --git a/src/shared/tools.h b/src/shared/tools.h index 7fdb64cd..e6192c4c 100644 --- a/src/shared/tools.h +++ b/src/shared/tools.h @@ -508,6 +508,17 @@ static inline bool htcmp(const char *x, const char *y) return !strcmp(x, y); } +static inline const char *getordinal(int num) { + // this code has no reason to be this robust, but it is, because why not -Y + const char* ordinal; + if (num % 100 >= 11 && num % 100 <= 19) ordinal = "th"; + else { + int lastdigit = num % 10; + ordinal = (lastdigit == 1 ? "st" : (lastdigit == 2 ? "nd" : (lastdigit == 3 ? "rd" : "th"))); + } + return ordinal; +} + struct stringslice { const char *str; @@ -669,6 +680,16 @@ template struct vector void sort() { sort(sortless()); } void sortname() { sort(sortnameless()); } + void shuffle() { + extern uint randomMT(); + for (int i = 0; i < ulen; i++) { + int indx = rnd(ulen); + T temp = buf[i]; + buf[i] = buf[indx]; + buf[indx] = temp; + } + } + void growbuf(int sz) { int olen = alen;