From 14e2e962458336628a9656fa35b855450dc5bc08 Mon Sep 17 00:00:00 2001 From: div0 Date: Sat, 11 Jul 2009 12:55:08 +0000 Subject: [PATCH] experimental race penalty time system (entity fields race_penalty and race_penalty_reason, entities trigger_race_checkpoint and trigger_race_penalty) git-svn-id: svn://svn.icculus.org/nexuiz/trunk@7188 f962a42d-fe04-0410-a3ab-8c8b0445ebaa --- data/qcsrc/client/Defs.qc | 4 ++ data/qcsrc/client/Main.qc | 22 ++++++ data/qcsrc/client/sbar.qc | 34 +++++++-- data/qcsrc/common/constants.qh | 2 + data/qcsrc/server/cl_client.qc | 57 ++------------- data/qcsrc/server/cl_physics.qc | 24 ++++++- data/qcsrc/server/cl_weaponsystem.qc | 7 +- data/qcsrc/server/clientcommands.qc | 5 ++ data/qcsrc/server/race.qc | 101 ++++++++++++++++++++++++--- data/qcsrc/server/race.qh | 1 + data/scripts/entities.def | 11 +++ 11 files changed, 198 insertions(+), 70 deletions(-) diff --git a/data/qcsrc/client/Defs.qc b/data/qcsrc/client/Defs.qc index 711f73adb..e5c0bb600 100644 --- a/data/qcsrc/client/Defs.qc +++ b/data/qcsrc/client/Defs.qc @@ -188,6 +188,10 @@ string race_previousbestname; float race_nextcheckpoint; float race_nextbesttime; string race_nextbestname; +float race_penaltyaccumulator; // qualifying: total penalty time in tenths +float race_penaltyeventtime; // time when the player got the penalty +float race_penaltytime; // duration of penalty time, in tenths +string race_penaltyreason; // reason for penalty // RACE float race_mycheckpoint; diff --git a/data/qcsrc/client/Main.qc b/data/qcsrc/client/Main.qc index 041ee1a13..c4299b4c1 100644 --- a/data/qcsrc/client/Main.qc +++ b/data/qcsrc/client/Main.qc @@ -919,7 +919,11 @@ void Net_ReadRace() race_checkpointtime = time; if(race_checkpoint == 0 || race_checkpoint == 254) + { + race_penaltytime = 0; + race_penaltyaccumulator = 0; race_laptime = time; // valid + } break; @@ -964,6 +968,24 @@ void Net_ReadRace() strunzone(race_othercheckpointenemy); race_othercheckpointenemy = strzone(ColorTranslateRGB(ReadString())); break; + + case RACE_NET_PENALTY_RACE: + race_penaltyeventtime = time; + race_penaltytime = ReadByte(); + //race_penaltyaccumulator += race_penaltytime; + if(race_penaltyreason) + strunzone(race_penaltyreason); + race_penaltyreason = strzone(ReadString()); + break; + + case RACE_NET_PENALTY_QUALIFYING: + race_penaltyeventtime = time; + race_penaltytime = ReadByte(); + race_penaltyaccumulator += race_penaltytime; + if(race_penaltyreason) + strunzone(race_penaltyreason); + race_penaltyreason = strzone(ReadString()); + break; } } diff --git a/data/qcsrc/client/sbar.qc b/data/qcsrc/client/sbar.qc index c4a8ca88f..399dd7b08 100644 --- a/data/qcsrc/client/sbar.qc +++ b/data/qcsrc/client/sbar.qc @@ -1460,7 +1460,7 @@ void Sbar_Score(float margin) if(gametype == GAME_RACE) { drawfont = sbar_bigfont; - float a; + float a, t; vector m; string s, forcetime; @@ -1487,10 +1487,10 @@ void Sbar_Score(float margin) { if(race_laptime && race_nextbesttime && race_nextcheckpoint != 254) { - a = bound(0, 2 - ((race_laptime + race_nextbesttime/10) - time), 1); + a = bound(0, 2 - ((race_laptime + race_nextbesttime/10) - (time + race_penaltyaccumulator/10)), 1); if(a > 0) // next one? { - s = MakeRaceString(race_nextcheckpoint, time - race_laptime, race_nextbesttime / 10, 0, race_nextbestname); + s = MakeRaceString(race_nextcheckpoint, (time + race_penaltyaccumulator/10) - race_laptime, race_nextbesttime / 10, 0, race_nextbestname); } } } @@ -1501,6 +1501,17 @@ void Sbar_Score(float margin) drawcolorcodedstring(m - '0 16 0' - '8 0 0' * stringwidth(s, TRUE), s, '16 16 0', sbar_alpha_fg * a, DRAWFLAG_NORMAL); } + if(race_penaltytime) + { + a = bound(0, 2 - (time - race_penaltyeventtime), 1); + if(a > 0) + { + s = strcat("^1PENALTY: ", ftos_decimals(race_penaltytime * 0.1, 1), " (", race_penaltyreason, ")"); + dummyfunction(0, 0, 0, 0, 0, 0, 0, 0); // work around DP bug (set OFS_PARAM5 to 0) + drawcolorcodedstring(m - '0 32 0' - '8 0 0' * stringwidth(s, TRUE), s, '16 16 0', sbar_alpha_fg * a, DRAWFLAG_NORMAL); + } + } + if(forcetime != "") { a = bound(0, (time - race_checkpointtime) / 0.5, 1); @@ -1511,7 +1522,7 @@ void Sbar_Score(float margin) if(race_laptime && race_checkpoint != 255) { - s = mmsss(10*(time - race_laptime)); + s = mmsss(10*(time + race_penaltyaccumulator/10 - race_laptime)); drawstring(m - '16 0 0' * stringwidth(s, FALSE), s, '32 32 0', '1 1 1', sbar_alpha_fg * a, DRAWFLAG_NORMAL); } } @@ -1531,6 +1542,21 @@ void Sbar_Score(float margin) dummyfunction(0, 0, 0, 0, 0, 0, 0, 0); // work around DP bug (set OFS_PARAM5 to 0) drawcolorcodedstring(m - '0 0 0' - '8 0 0' * stringwidth(s, TRUE), s, '16 16 0', sbar_alpha_fg * a, DRAWFLAG_NORMAL); } + + if(race_penaltytime && !race_penaltyaccumulator) + { + t = race_penaltytime * 0.1 + race_penaltyeventtime; + a = bound(0, (1 + t - time), 1); + if(a > 0) + { + if(time < t) + s = strcat("^1PENALTY: ", ftos_decimals(t - time, 1), " (", race_penaltyreason, ")"); + else + s = strcat("^2PENALTY: 0.0 (", race_penaltyreason, ")"); + dummyfunction(0, 0, 0, 0, 0, 0, 0, 0); // work around DP bug (set OFS_PARAM5 to 0) + drawcolorcodedstring(m - '0 32 0' - '8 0 0' * stringwidth(s, TRUE), s, '16 16 0', sbar_alpha_fg * a, DRAWFLAG_NORMAL); + } + } } drawfont = sbar_font; diff --git a/data/qcsrc/common/constants.qh b/data/qcsrc/common/constants.qh index 5b984d677..e0129e293 100644 --- a/data/qcsrc/common/constants.qh +++ b/data/qcsrc/common/constants.qh @@ -56,6 +56,8 @@ const float RACE_NET_CHECKPOINT_NEXT_QUALIFYING = 2; // byte nextcheckpoint, sho const float RACE_NET_CHECKPOINT_HIT_RACE = 3; // byte checkpoint, short delta, byte lapsdelta, string opponent const float RACE_NET_CHECKPOINT_HIT_RACE_BY_OPPONENT = 4; // byte checkpoint, short delta, byte lapsdelta, string opponent const float RACE_NET_CHECKPOINT_NEXT_SPEC_QUALIFYING = 5; // byte nextcheckpoint, float laptime, short recordtime, string recordholder +const float RACE_NET_PENALTY_RACE = 6; // byte penaltytime, string reason +const float RACE_NET_PENALTY_QUALIFYING = 7; // byte penaltytime, string reason const float ENT_CLIENT = 0; const float ENT_CLIENT_DEAD = 1; diff --git a/data/qcsrc/server/cl_client.qc b/data/qcsrc/server/cl_client.qc index 06c0101d6..a483ae2d3 100644 --- a/data/qcsrc/server/cl_client.qc +++ b/data/qcsrc/server/cl_client.qc @@ -2745,9 +2745,6 @@ Called every frame for each client after the physics are run ============= */ .float idlekick_lasttimeleft; -.float race_penalty; -.float race_penalty_nagged; -.float race_penalty_nagtime; void PlayerPostThink (void) { // Savage: Check for nameless players @@ -2841,45 +2838,9 @@ void PlayerPostThink (void) //if (TetrisPostFrame()) return; // restart countdown - if(time < game_starttime) { - if (!cvar("sv_ready_restart_after_countdown")) - { - if(self.movement != '0 0 0' && g_race && !g_race_qualifying) - { - if(time < game_starttime - 2) - { - if(!self.race_penalty_nagged) - { - // TODO better notification for this! - self.race_penalty_nagtime = 0; - self.race_penalty_nagged = 1; - } - } - else if(!self.race_penalty) - { - self.race_penalty_nagtime = 0; - self.race_penalty = time + 5; - } - } - if(time > self.race_penalty_nagtime) - { - if(self.race_penalty > time) - { - centerprint_atprio(self, CENTERPRIO_IDLEKICK, "^1FIVE SECONDS PENALTY."); - } - else if(self.race_penalty_nagged && time < game_starttime - 2) - { - centerprint_atprio(self, CENTERPRIO_IDLEKICK, "^1DO NOT MOVE DURING THE COUNTDOWN."); - } - self.race_penalty_nagtime = time + self.cvar_scr_centertime * 0.6; - } - self.movetype = MOVETYPE_NONE; - self.velocity = '0 0 0'; - self.avelocity = '0 0 0'; - self.movement = '0 0 0'; - } - } - else if (time < self.race_penalty) + if (!cvar("sv_ready_restart_after_countdown")) + { + if(time < game_starttime) { self.movetype = MOVETYPE_NONE; self.velocity = '0 0 0'; @@ -2889,16 +2850,10 @@ void PlayerPostThink (void) else { //allow the player to move again if sv_ready_restart_after_countdown is not used and countdown is over - if (!cvar("sv_ready_restart_after_countdown")) - { - if(self.movetype == MOVETYPE_NONE) - { - self.movetype = MOVETYPE_WALK; - } - self.race_penalty = 0; - self.race_penalty_nagged = 0; - } + if(self.movetype == MOVETYPE_NONE) + self.movetype = MOVETYPE_WALK; } + } GetPressedKeys(); } else if (self.classname == "observer") { //do nothing diff --git a/data/qcsrc/server/cl_physics.qc b/data/qcsrc/server/cl_physics.qc index cafe1d06e..464bcd00d 100644 --- a/data/qcsrc/server/cl_physics.qc +++ b/data/qcsrc/server/cl_physics.qc @@ -1,3 +1,5 @@ +.float race_penalty; + float sv_accelerate; float sv_friction; float sv_maxspeed; @@ -524,7 +526,27 @@ void SV_PlayerPhysics() self.items &~= IT_USING_JETPACK; - if (self.movetype == MOVETYPE_NONE && self.disableclientprediction != 2) + if(self.race_penalty) + { + if(time > self.race_penalty) + { + if(self.disableclientprediction == 2) + { + self.movetype = MOVETYPE_WALK; + self.disableclientprediction = 0; + } + self.race_penalty = 0; + } + } + + if(self.race_penalty) + { + self.velocity = '0 0 0'; + self.movetype = MOVETYPE_NONE; + self.disableclientprediction = 2; + } + + if (self.movetype == MOVETYPE_NONE) return; if (self.punchangle != '0 0 0') diff --git a/data/qcsrc/server/cl_weaponsystem.qc b/data/qcsrc/server/cl_weaponsystem.qc index b700a1325..fbcbb919e 100644 --- a/data/qcsrc/server/cl_weaponsystem.qc +++ b/data/qcsrc/server/cl_weaponsystem.qc @@ -993,10 +993,9 @@ float weapon_prepareattack(float secondary, float attacktime) { //if sv_ready_restart_after_countdown is set, don't allow the player to shoot //if all players readied up and the countdown is running - if (cvar("sv_ready_restart_after_countdown")) - if(time < game_starttime || time < self.race_penalty) { - return FALSE; - } + if(time < game_starttime || time < self.race_penalty) { + return FALSE; + } if not(self.items & IT_UNLIMITED_WEAPON_AMMO) if (!weapon_action(self.weapon, WR_CHECKAMMO1 + secondary)) diff --git a/data/qcsrc/server/clientcommands.qc b/data/qcsrc/server/clientcommands.qc index 71073d9a5..4d704d95e 100644 --- a/data/qcsrc/server/clientcommands.qc +++ b/data/qcsrc/server/clientcommands.qc @@ -383,6 +383,11 @@ void SV_ParseClientCommand(string s) { } else sprint(self, "Usage: sv_cheats 1; restart; cmd make models/... 0/1/2\n"); + } else if(argv(0) == "penalty") { + if((sv_cheats || self.maycheat) && tokens == 3) + race_ImposePenaltyTime(self, stof(argv(1)), argv(2)); + else + sprint(self, "Usage: sv_cheats 1; restart; cmd penalty 5.0 AHAHAHAHAHAHAH))\n"); } else { //if(ctf_clientcommand()) // return; diff --git a/data/qcsrc/server/race.qc b/data/qcsrc/server/race.qc index e905ad2ff..7cd64a565 100644 --- a/data/qcsrc/server/race.qc +++ b/data/qcsrc/server/race.qc @@ -1,7 +1,11 @@ #define MAX_CHECKPOINTS 255 +.float race_penalty; +.float race_penalty_accumulator; +.string race_penalty_reason; .float race_checkpoint; // player: next checkpoint that has to be reached .float race_laptime; +.entity race_lastpenalty; .entity sprite; @@ -75,7 +79,7 @@ void race_SendNextCheckpoint(entity e, float spec) if(spec) { WriteByte(MSG_ONE, RACE_NET_CHECKPOINT_NEXT_SPEC_QUALIFYING); - WriteCoord(MSG_ONE, e.race_laptime); + WriteCoord(MSG_ONE, e.race_laptime - e.race_penalty_accumulator); } else WriteByte(MSG_ONE, RACE_NET_CHECKPOINT_NEXT_QUALIFYING); @@ -97,6 +101,9 @@ void race_SendTime(entity e, float cp, float t, float tvalid) float snew, l; entity p; + if(g_race_qualifying) + t += e.race_penalty_accumulator; + t = floor(0.4 + 10 * t); // make integer // adding just 0.4 so it rounds down in the .5 case (matching the timer display) @@ -114,7 +121,7 @@ void race_SendTime(entity e, float cp, float t, float tvalid) else { s = PlayerScore_Add(e, SP_RACE_TIME, 0); - snew = floor(0.5 + 10 * (time - game_starttime)); + snew = floor(0.4 + 10 * (time - game_starttime)); PlayerScore_Add(e, SP_RACE_TIME, snew - s); l = PlayerTeamScore_Add(e, SP_RACE_LAPS, ST_RACE_LAPS, 1); @@ -284,6 +291,8 @@ void race_ClearTime(entity e) { e.race_checkpoint = -1; e.race_laptime = 0; + e.race_penalty_accumulator = 0; + e.race_lastpenalty = world; msg_entity = e; WRITESPECTATABLE_MSG_ONE({ @@ -366,12 +375,18 @@ void checkpoint_passed() self.message = oldmsg; } + race_ImposePenaltyTime(other, self.race_penalty, self.race_penalty_reason); + other.race_checkpoint = race_NextCheckpoint(self.race_checkpoint); race_SendTime(other, self.race_checkpoint, time - other.race_laptime, !!other.race_laptime); if(!self.race_checkpoint) // start line + { other.race_laptime = time; + other.race_penalty_accumulator = 0; + other.race_lastpenalty = world; + } if(g_race_qualifying) race_SendNextCheckpoint(other, 0); @@ -462,10 +477,13 @@ void trigger_race_checkpoint_verify() if(race_timed_checkpoint) for(cp = world; (cp = find(cp, classname, "trigger_race_checkpoint")); ) - if(cp.race_checkpoint == 0) - WaypointSprite_UpdateSprites(cp.sprite, "race-start", "", ""); - else if(cp.race_checkpoint == race_timed_checkpoint) - WaypointSprite_UpdateSprites(cp.sprite, "race-finish", "", ""); + if(cp.sprite) + { + if(cp.race_checkpoint == 0) + WaypointSprite_UpdateSprites(cp.sprite, "race-start", "", ""); + else if(cp.race_checkpoint == race_timed_checkpoint) + WaypointSprite_UpdateSprites(cp.sprite, "race-finish", "", ""); + } remove(self); self = oldself; @@ -495,6 +513,8 @@ void spawnfunc_trigger_race_checkpoint() self.message = "went backwards"; if (!self.message2) self.message2 = "was pushed backwards by"; + if (!self.race_penalty_reason) + self.race_penalty_reason = "missing a checkpoint"; self.race_checkpoint = self.cnt; @@ -507,10 +527,13 @@ void spawnfunc_trigger_race_checkpoint() race_timed_checkpoint = 0; } - if(self.race_checkpoint) - WaypointSprite_SpawnFixed("race-checkpoint", o, self, sprite); - else - WaypointSprite_SpawnFixed("race-finish", o, self, sprite); + if(!self.race_penalty) + { + if(self.race_checkpoint) + WaypointSprite_SpawnFixed("race-checkpoint", o, self, sprite); + else + WaypointSprite_SpawnFixed("race-finish", o, self, sprite); + } self.sprite.waypointsprite_visible_for_player = race_waypointsprite_visible_for_player; @@ -643,3 +666,61 @@ void race_ReadyRestart() ScoreRules_race(); } } + +void race_ImposePenaltyTime(entity pl, float penalty, string reason) +{ + if(g_race_qualifying) + { + pl.race_penalty_accumulator += penalty; + msg_entity = pl; + WRITESPECTATABLE_MSG_ONE({ + WriteByte(MSG_ONE, SVC_TEMPENTITY); + WriteByte(MSG_ONE, TE_CSQC_RACE); + WriteByte(MSG_ONE, RACE_NET_PENALTY_QUALIFYING); + WriteByte(MSG_ONE, floor(0.4 + 10 * penalty)); + WriteString(MSG_ONE, reason); + }); + } + else + { + pl.race_penalty = time + penalty; + msg_entity = pl; + WRITESPECTATABLE_MSG_ONE_VARNAME(dummy, { + WriteByte(MSG_ONE, SVC_TEMPENTITY); + WriteByte(MSG_ONE, TE_CSQC_RACE); + WriteByte(MSG_ONE, RACE_NET_PENALTY_RACE); + WriteByte(MSG_ONE, floor(0.4 + 10 * penalty)); + WriteString(MSG_ONE, reason); + }); + } +} + +void penalty_touch() +{ + EXACTTRIGGER_TOUCH; + race_ImposePenaltyTime(other, self.race_penalty, self.race_penalty_reason); +} + +void penalty_use() +{ + other = activator; + if(other.race_lastpenalty != self) + { + other.race_lastpenalty = self; + race_ImposePenaltyTime(other, self.race_penalty, self.race_penalty_reason); + } +} + +void spawnfunc_trigger_race_penalty() +{ + EXACTTRIGGER_INIT; + + self.use = penalty_use; + if not(self.spawnflags & 1) + self.touch = penalty_touch; + + if (!self.race_penalty_reason) + self.race_penalty_reason = "missing a checkpoint"; + if (!self.race_penalty) + self.race_penalty = 5; +} diff --git a/data/qcsrc/server/race.qh b/data/qcsrc/server/race.qh index 7c28a791f..c3bdda7ca 100644 --- a/data/qcsrc/server/race.qh +++ b/data/qcsrc/server/race.qh @@ -16,3 +16,4 @@ float race_leadlimit; .float race_place; .float race_completed; float race_completing; +void race_ImposePenaltyTime(entity pl, float penalty, string reason); diff --git a/data/scripts/entities.def b/data/scripts/entities.def index 594a65dd4..471732841 100644 --- a/data/scripts/entities.def +++ b/data/scripts/entities.def @@ -1228,6 +1228,8 @@ message: Death message, when touching checkpoints in the wrong order. message2: Death message when someone gets pushed into this (default: "was thrown into a world of hurt by"). The # character is replaced by the attacker name if present (and it instead does not get appended to the end) targetname: Name of the checkpoint. info_player_race can target this to assign a spawn to a checkpoint. Also used for triggering a checkpoint by an event. target: when the checkpoint is passed, these entities are triggered. Useful for forcing items in certain areas using trigger_items +race_penalty: when set, this penalty time is given if passing this checkpoint, and the checkpoint does not show up with a sprite. Useful for invisible checkpoints to detect driving around the intended checkpoint +race_penalty_reason: reason to display when the penalty time is imposed. Default: "missing a checkpoint" -------- SPAWNFLAGS -------- NOTOUCH: the checkpoint will not become active when touched, it HAS to be targeted STRICTTRIGGER: only trigger the targets when the checkpoint actually was reached in a valid way (that is, not when going back) @@ -1235,6 +1237,15 @@ CRUSH: the checkpoint kills when used at the wrong time FINISH: when set on the last checkpoint (i.e. the one with highest cnt), it is marked as finish line and the CP with cnt=0 is the start line */ +/*QUAKED trigger_race_penalty (0 1 0) ? NOTOUCH +A penalty trigger. +-------- KEYS -------- +race_penalty: this penalty time is given if passing this trigger +race_penalty_reason: reason to display when the penalty time is imposed. Default: "leaving the track" +-------- SPAWNFLAGS -------- +NOTOUCH: the trigger will not become active when touched, it HAS to be targeted +*/ + /*QUAKED info_player_race (1 0.5 0) (-16 -16 -24) (16 16 45) Race spawn point. NOTE for race_place: when the race starts after the qualifying, the player with the fastest map ends up at the info_player_race with race_place 1, and so on. If there are too many players, or if someone comes in later, he will spawn at an info_player_race with race_place not set. So for each trigger_race_checkpoint, there must be at least one corresponding info_player_race with race_place NOT set. -- 2.39.2