From 6d4da37b5a523e55f45e5e397de154f8e6c52564 Mon Sep 17 00:00:00 2001 From: div0 Date: Fri, 8 Aug 2008 14:48:43 +0000 Subject: [PATCH] "race" game mode (beginning) git-svn-id: svn://svn.icculus.org/nexuiz/trunk@4059 f962a42d-fe04-0410-a3ab-8c8b0445ebaa --- data/qcsrc/client/Defs.qc | 5 +++ data/qcsrc/client/Main.qc | 19 ++++++++++ data/qcsrc/client/sbar.qc | 56 ++++++++++++++++++++++++++++- data/qcsrc/common/constants.qh | 13 ++++++- data/qcsrc/common/mapinfo.qc | 9 ++++- data/qcsrc/common/mapinfo.qh | 1 + data/qcsrc/common/util.qc | 11 ++++++ data/qcsrc/common/util.qh | 1 + data/qcsrc/server/bots.qc | 1 + data/qcsrc/server/cl_client.qc | 2 ++ data/qcsrc/server/defs.qh | 2 +- data/qcsrc/server/havocbot_roles.qc | 40 +++++++++++++++++++++ data/qcsrc/server/progs.src | 4 +++ data/qcsrc/server/scores.qc | 14 ++++++++ data/qcsrc/server/scores_rules.qc | 11 ++++++ data/qcsrc/server/teamplay.qc | 13 +++++++ data/scripts/entities.def | 7 ++++ 17 files changed, 205 insertions(+), 4 deletions(-) diff --git a/data/qcsrc/client/Defs.qc b/data/qcsrc/client/Defs.qc index 6571b1e4a..742d98068 100644 --- a/data/qcsrc/client/Defs.qc +++ b/data/qcsrc/client/Defs.qc @@ -174,3 +174,8 @@ float vid_conwidth, vid_conheight; float caps_team1, caps_team2; float configdb; string shortmapname; + +float race_checkpoint; +float race_time; +float race_laptime; +float race_checkpointtime; diff --git a/data/qcsrc/client/Main.qc b/data/qcsrc/client/Main.qc index 756985e09..595f1ee49 100644 --- a/data/qcsrc/client/Main.qc +++ b/data/qcsrc/client/Main.qc @@ -565,6 +565,21 @@ void Net_Config() db_put(configdb, strcat("/s/", key), "1"); } +void Net_ReadRace() +{ + float checkpoint, t; + + race_checkpoint = ReadByte(); + race_time = ReadShort(); + + race_checkpointtime = time; + + if(race_checkpoint == 0) + race_laptime = time; // valid + else if(race_checkpoint == 255) + race_laptime = 0; // invalid +} + // CSQC_Parse_TempEntity : Handles all temporary entity network data in the CSQC layer. // You must ALWAYS first acquire the temporary ID, which is sent as a byte. // Return value should be 1 if CSQC handled the temporary entity, otherwise return 0 to have the engine process the event. @@ -595,6 +610,10 @@ float CSQC_Parse_TempEntity() Net_ReadScoresInfo(); bHandled = true; break; + case TE_CSQC_RACE: + Net_ReadRace(); + bHandled = true; + break; default: // No special logic for this temporary entity; return 0 so the engine can handle it bHandled = false; diff --git a/data/qcsrc/client/sbar.qc b/data/qcsrc/client/sbar.qc index abc9f4ead..d0ad1312c 100644 --- a/data/qcsrc/client/sbar.qc +++ b/data/qcsrc/client/sbar.qc @@ -204,6 +204,13 @@ float Sbar_ComparePlayerScores(entity left, entity right) vl = left.scores[ps_primary]; vr = right.scores[ps_primary]; + if(scores_flags[ps_primary] & SFL_ZERO_IS_WORST) + { + if(vl == 0 && vr != 0) + return 1; + if(vl != 0 && vr == 0) + return 0; + } if(vl > vr) return IS_INCREASING(scores_flags[ps_primary]); if(vl < vr) @@ -211,6 +218,13 @@ float Sbar_ComparePlayerScores(entity left, entity right) vl = left.scores[ps_secondary]; vr = right.scores[ps_secondary]; + if(scores_flags[ps_secondary] & SFL_ZERO_IS_WORST) + { + if(vl == 0 && vr != 0) + return 1; + if(vl != 0 && vr == 0) + return 0; + } if(vl > vr) return IS_INCREASING(scores_flags[ps_secondary]); if(vl < vr) @@ -548,7 +562,7 @@ string Sbar_GetField(entity pl, float field) else sbar_field_rgb = '1 1 1'; if(!tmp) - if(f & (SFL_HIDE_ZERO | SFL_RANK)) + if(f & (SFL_HIDE_ZERO | SFL_RANK | SFL_TIME)) return ""; if(f & SFL_RANK) { @@ -565,6 +579,10 @@ string Sbar_GetField(entity pl, float field) else return strcat(str, "th"); } + else if(f & SFL_TIME) + { + return mmsss(tmp); + } return ftos(tmp); } //return "error"; @@ -1017,6 +1035,42 @@ void Sbar_Score(float margin) drawpic(sbar + '-36 32 0', "gfx/num_colon", '12 12 0', '1 1 1', sbar_alpha_fg, 0); Sbar_DrawXNum('-24 32 0', seconds, -2, 12, '1 1 1', 1, DRAWFLAG_NORMAL); } + + if(gametype == GAME_RACE) + { + if(race_checkpointtime) + if(race_checkpoint != 255) + { + float a; + vector m; + string s; + + m = '0.5 0 0' * vid_conwidth + '0 0.5 0' * vid_conheight; + + a = bound(0, 2 - (time - race_checkpointtime), 1); + drawfont = sbar_bigfont; + if(a >= 0) + { + if(race_checkpoint) + s = strcat("Intermediate ", ftos(race_checkpoint)); + else + s = strcat("Finish line"); + drawstring(m - '0 88 0' - '12 0 0' * stringwidth(s, FALSE), s, '24 24 0', '0 1 0', sbar_alpha_fg * a, 0); + if(race_time) + { + s = mmsss(race_time); + drawstring(m - '0 64 0' - '16 0 0' * stringwidth(s, FALSE), s, '32 32 0', '0 1 0', sbar_alpha_fg * a, 0); + } + } + + if(race_laptime) + { + s = mmsss(10*(time - race_laptime)); + drawstring(m - '0 64 0' - '16 0 0' * stringwidth(s, FALSE), s, '32 32 0', '1 1 1', sbar_alpha_fg * (1 - a), 0); + } + } + } + sbar = sbar_save; } diff --git a/data/qcsrc/common/constants.qh b/data/qcsrc/common/constants.qh index cde4d005c..a58215b3a 100644 --- a/data/qcsrc/common/constants.qh +++ b/data/qcsrc/common/constants.qh @@ -6,7 +6,8 @@ // Revision 5: mapvote time fix // Revision 6: more robust against packet loss/delays, also show not yet connected clients // Revision 7: packet loss column -#define CSQC_REVISION 7 +// Revision 8: race +#define CSQC_REVISION 8 // probably put these in common/ // so server/ and client/ can be synced better @@ -20,6 +21,7 @@ const float GAME_ARENA = 7; const float GAME_KEYHUNT = 8; const float GAME_ASSAULT = 9; const float GAME_ONSLAUGHT = 10; +const float GAME_RACE = 11; const float AS_STRING = 1; const float AS_INT = 2; @@ -183,6 +185,7 @@ const float TE_CSQC_PICTURE = 105; const float TE_CSQC_MAPVOTE = 106; const float TE_CSQC_CONFIG = 107; const float TE_CSQC_SCORESINFO = 108; +const float TE_CSQC_RACE = 109; const float STAT_KH_KEYS = 32; const float STAT_CTF_STATE = 33; @@ -218,6 +221,14 @@ const float MAPVOTE_NET_OWNVOTE = 3; */ #define SFL_RANK 32 +/** + * Display as mm:ss.s, value is stored as 10ths of a second (AND 0 is the worst possible value!) + */ +#define SFL_TIME 64 + +// not an extra constant yet +#define SFL_ZERO_IS_WORST SFL_TIME + /** * Scoring priority (NOTE: PRIMARY is used for fraglimit) */ diff --git a/data/qcsrc/common/mapinfo.qc b/data/qcsrc/common/mapinfo.qc index b157b587a..cea446bd7 100644 --- a/data/qcsrc/common/mapinfo.qc +++ b/data/qcsrc/common/mapinfo.qc @@ -421,6 +421,8 @@ float _MapInfo_Generate(string pFilename) // 0: failure, 1: ok ent, 2: ok bsp ++spawnpoints; else if(v == "info_player_deathmatch") ++spawnpoints; + else if(v == "trigger_race_checkpoint") + MapInfo_Map_supportedGametypes |= MAPINFO_TYPE_RACE; else if(v == "weapon_nex") { } else if(v == "weapon_railgun") @@ -437,7 +439,7 @@ float _MapInfo_Generate(string pFilename) // 0: failure, 1: ok ent, 2: ok bsp } diameter = vlen(mapMaxs - mapMins); - twoBaseModes = MapInfo_Map_supportedGametypes & (MAPINFO_TYPE_CTF | MAPINFO_TYPE_ASSAULT); + twoBaseModes = MapInfo_Map_supportedGametypes & (MAPINFO_TYPE_CTF | MAPINFO_TYPE_ASSAULT | MAPINFO_TYPE_RACE); if(twoBaseModes && (MapInfo_Map_supportedGametypes == twoBaseModes)) { // we have a CTF-only or Assault-only map. Don't add other modes then, @@ -522,6 +524,7 @@ float MapInfo_Type_FromString(string t) else if(t == "kh") return MAPINFO_TYPE_KEYHUNT; else if(t == "as") return MAPINFO_TYPE_ASSAULT; else if(t == "ons") return MAPINFO_TYPE_ONSLAUGHT; + else if(t == "race") return MAPINFO_TYPE_RACE; else if(t == "all") return MAPINFO_TYPE_ALL; else return 0; } @@ -567,6 +570,7 @@ float MapInfo_Get_ByName(string pFilename, float pAllowGenerate, float pGametype if(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_ARENA) fputs(fh, "type arena 10 20\n"); if(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_KEYHUNT) fputs(fh, "type kh 1000 20 3\n"); if(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_ASSAULT) fputs(fh, "type as 20\n"); + if(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_RACE) fputs(fh, "type race 5 20 0\n"); if(MapInfo_Map_supportedGametypes & MAPINFO_TYPE_ONSLAUGHT) fputs(fh, "type ons 20\n"); fh2 = fopen(strcat("scripts/", pFilename, ".arena"), FILE_READ); @@ -752,6 +756,8 @@ float MapInfo_CurrentGametype() return MAPINFO_TYPE_KEYHUNT; else if(cvar("g_onslaught")) return MAPINFO_TYPE_ONSLAUGHT; + else if(cvar("g_race")) + return MAPINFO_TYPE_RACE; else return MAPINFO_TYPE_DEATHMATCH; } @@ -788,6 +794,7 @@ void MapInfo_SwitchGameType(float t) cvar_set("g_keyhunt", (t == MAPINFO_TYPE_KEYHUNT) ? "1" : "0"); cvar_set("g_assault", (t == MAPINFO_TYPE_ASSAULT) ? "1" : "0"); cvar_set("g_onslaught", (t == MAPINFO_TYPE_ONSLAUGHT) ? "1" : "0"); + cvar_set("g_race", (t == MAPINFO_TYPE_RACE) ? "1" : "0"); } void MapInfo_LoadMap(string s) diff --git a/data/qcsrc/common/mapinfo.qh b/data/qcsrc/common/mapinfo.qh index 28a59612f..ee1796428 100644 --- a/data/qcsrc/common/mapinfo.qh +++ b/data/qcsrc/common/mapinfo.qh @@ -8,6 +8,7 @@ float MAPINFO_TYPE_ARENA = 64; float MAPINFO_TYPE_KEYHUNT = 128; float MAPINFO_TYPE_ASSAULT = 256; float MAPINFO_TYPE_ONSLAUGHT = 512; +float MAPINFO_TYPE_RACE = 1024; float MAPINFO_TYPE_ALL = 65535; // this has to include all above bits float MAPINFO_FEATURE_WEAPONS = 1; // not defined for minstagib-only maps diff --git a/data/qcsrc/common/util.qc b/data/qcsrc/common/util.qc index 71d8f4264..2f7436f56 100644 --- a/data/qcsrc/common/util.qc +++ b/data/qcsrc/common/util.qc @@ -422,6 +422,17 @@ string GametypeNameFromType(float g) else if (g == GAME_KEYHUNT) return "kh"; else if (g == GAME_ONSLAUGHT) return "ons"; else if (g == GAME_ASSAULT) return "as"; + else if (g == GAME_RACE) return "race"; return "dm"; } +string mmsss(float tenths) +{ + float minutes; + string s; + tenths = floor(tenths + 0.5); + minutes = floor(tenths / 600); + tenths -= minutes * 600; + s = ftos(1000 + tenths); + return strcat(ftos(minutes), ":", substring(s, 1, 2), ".", substring(s, 3, 1)); +} diff --git a/data/qcsrc/common/util.qh b/data/qcsrc/common/util.qh index ec38c189e..859757619 100644 --- a/data/qcsrc/common/util.qh +++ b/data/qcsrc/common/util.qh @@ -57,3 +57,4 @@ float mod(float a, float b) { return a - (floor(a / b) * b); } #endif string GametypeNameFromType(float g); +string mmsss(float t); diff --git a/data/qcsrc/server/bots.qc b/data/qcsrc/server/bots.qc index 6fbaef41e..edf17f729 100644 --- a/data/qcsrc/server/bots.qc +++ b/data/qcsrc/server/bots.qc @@ -1240,6 +1240,7 @@ void navigation_routerating(entity e, float f, float rangebias) e.nearestwaypointtimeout = time + random() * 3 + 5; } //dprint(e.classname, " ", ftos(f)); + //dprint("-- checking ", e.classname, " (with cost ", ftos(e.nearestwaypoint.wpcost), ")\n"); if (e.nearestwaypoint) if (e.nearestwaypoint.wpcost < 10000000) { diff --git a/data/qcsrc/server/cl_client.qc b/data/qcsrc/server/cl_client.qc index 83799cf01..97e47b887 100644 --- a/data/qcsrc/server/cl_client.qc +++ b/data/qcsrc/server/cl_client.qc @@ -691,6 +691,8 @@ void PutClientInServer (void) self.lms_traveled_distance = 0; self.speedrunning = FALSE; + race_PreparePlayer(); + if(cvar("spawn_debug")) { sprint(self, strcat("spawnpoint origin: ", vtos(spot.origin), "\n")); diff --git a/data/qcsrc/server/defs.qh b/data/qcsrc/server/defs.qh index 3b792db43..dae897ce6 100644 --- a/data/qcsrc/server/defs.qh +++ b/data/qcsrc/server/defs.qh @@ -15,7 +15,7 @@ float require_spawnfunc_prefix; // if this float exists, only functions with spa // Globals -float g_domination, g_ctf, g_tdm, g_keyhunt, g_onslaught, g_assault, g_arena, g_lms, g_runematch; +float g_domination, g_ctf, g_tdm, g_keyhunt, g_onslaught, g_assault, g_arena, g_lms, g_runematch, g_race; float g_cloaked, g_footsteps, g_jump_grunt, g_grappling_hook, g_instagib, g_laserguided_missile, g_midair, g_minstagib, g_nixnex, g_nixnex_with_laser, g_norecoil, g_rocketarena, g_vampire, g_minstagib_invis_alpha; float g_tourney; float g_ctf_win_mode; diff --git a/data/qcsrc/server/havocbot_roles.qc b/data/qcsrc/server/havocbot_roles.qc index 90a54af57..60d5dfbab 100644 --- a/data/qcsrc/server/havocbot_roles.qc +++ b/data/qcsrc/server/havocbot_roles.qc @@ -532,11 +532,49 @@ void havocbot_role_dm() } }; +//Race: +//go to next checkpoint, and annoy enemies +.float race_checkpoint; +void havocbot_role_race() +{ + entity e; + if (self.bot_strategytime < time) + { + self.bot_strategytime = time + cvar("bot_ai_strategyinterval"); + navigation_goalrating_start(); + /* + havocbot_goalrating_items(100, self.origin, 10000); + havocbot_goalrating_enemyplayers(500, self.origin, 20000); + */ + + for(e = world; (e = find(e, classname, "trigger_race_checkpoint")) != world; ) + { + if(e.cnt == self.race_checkpoint) + { + print("found good checkpoint\n"); + navigation_routerating(e, 1000000, 5000); + } + else if(self.race_checkpoint == -1) + { + print("found good checkpoint -1\n"); + navigation_routerating(e, 1000000, 5000); + } + } + + navigation_goalrating_end(); + } +}; + void havocbot_chooserole_dm() { self.havocbot_role = havocbot_role_dm; }; +void havocbot_chooserole_race() +{ + self.havocbot_role = havocbot_role_race; +}; + void havocbot_chooserole_dom() { self.havocbot_role = havocbot_role_dom; @@ -759,6 +797,8 @@ void havocbot_chooserole() havocbot_chooserole_dom(); else if (g_keyhunt) havocbot_chooserole_kh(); + else if (g_race) + havocbot_chooserole_race(); else // assume anything else is deathmatch havocbot_chooserole_dm(); }; diff --git a/data/qcsrc/server/progs.src b/data/qcsrc/server/progs.src index 5ea95b301..7752506d0 100644 --- a/data/qcsrc/server/progs.src +++ b/data/qcsrc/server/progs.src @@ -22,6 +22,8 @@ scores.qh ipban.qh +race.qh + keyhunt.qh antilag.qh @@ -122,6 +124,8 @@ t_quake3.qc t_halflife.qc t_quake.qc +race.qc + //// tZork Turrets //// tturrets/include/turret_tturrets.qh diff --git a/data/qcsrc/server/scores.qc b/data/qcsrc/server/scores.qc index 4616a829b..37efbb2c0 100644 --- a/data/qcsrc/server/scores.qc +++ b/data/qcsrc/server/scores.qc @@ -29,6 +29,20 @@ vector ScoreField_Compare(entity t1, entity t2, .float field, float fieldflags, previous_y = fieldflags & SFL_SORT_PRIO_MASK; + if(fieldflags & SFL_ZERO_IS_WORST) + { + if(t1.field == 0) + { + previous_x = -1; + return previous; + } + else if(t2.field == 0) + { + previous_x = +1; + return previous; + } + } + if(fieldflags & SFL_LOWER_IS_BETTER) previous_x = (t2.field - t1.field); else diff --git a/data/qcsrc/server/scores_rules.qc b/data/qcsrc/server/scores_rules.qc index de72fda5f..ea89433c9 100644 --- a/data/qcsrc/server/scores_rules.qc +++ b/data/qcsrc/server/scores_rules.qc @@ -119,3 +119,14 @@ void ScoreRules_kh(float teams) ScoreInfo_SetLabel_PlayerScore(SP_KH_LOSSES, "losses", SFL_LOWER_IS_BETTER); ScoreRules_basics_end(); } + +// Race stuff +#define SP_RACE_LAPS 4 +#define SP_RACE_FASTEST 5 +void ScoreRules_race() +{ + ScoreRules_basics(0, 0); + ScoreInfo_SetLabel_PlayerScore(SP_RACE_LAPS, "laps", SFL_SORT_PRIO_PRIMARY); + ScoreInfo_SetLabel_PlayerScore(SP_RACE_FASTEST, "fastest", SFL_SORT_PRIO_SECONDARY | SFL_LOWER_IS_BETTER | SFL_TIME); + ScoreRules_basics_end(); +} diff --git a/data/qcsrc/server/teamplay.qc b/data/qcsrc/server/teamplay.qc index aa8ef60fd..9d68becaa 100644 --- a/data/qcsrc/server/teamplay.qc +++ b/data/qcsrc/server/teamplay.qc @@ -97,6 +97,7 @@ void ResetGameCvars() cvar_set("g_keyhunt", "0"); cvar_set("g_assault", "0"); cvar_set("g_onslaught", "0"); + cvar_set("g_race", "0"); cvar_set("teamplay", "0"); } @@ -263,6 +264,17 @@ void InitGameplayMode() gamemode_name = "Onslaught"; teams_matter = 1; } + else if(game == GAME_RACE || cvar("g_race")) + { + ResetGameCvars(); + game = GAME_RACE; + cvar_set("g_race", "1"); + fraglimit_override = cvar("g_race_laps_limit"); + gamemode_name = "Race"; + teams_matter = 0; + + ScoreRules_race(); + } else { // we can only assume... @@ -298,6 +310,7 @@ void InitGameplayMode() g_keyhunt = cvar("g_keyhunt"); g_onslaught = cvar("g_onslaught"); g_assault = cvar("g_assault"); + g_race = cvar("g_race"); g_arena = cvar("g_arena"); cache_mutatormsg = strzone(""); diff --git a/data/scripts/entities.def b/data/scripts/entities.def index 0bc2e594d..daab21b22 100644 --- a/data/scripts/entities.def +++ b/data/scripts/entities.def @@ -885,3 +885,10 @@ _receiveshadows: Allows per-entity control over shadow reception. Defaults to 1 _celshader: Sets the cel shader used for this geometry. Note: omit the "textures/" prefix. */ +/*QUAKED trigger_race_checkpoint (0 1 0) ? +A checkpoint, for the race game mode. Be sure to make them quite long, so they actually catch a player reliably! +-------- KEYS -------- +cnt: Number of the checkpoint. 0 for finish line, and at least two other checkpoints have to exist. They MUST be touched in sequential order! +message: Death message, when touching checkpoints in the wrong order. +*/ + -- 2.39.2