Skip to content

Commit d7e1ec0

Browse files
authored
Merge pull request #74 from WofWca/fix-check-exit-rules-order
fix: win condition on frag + suicide
2 parents c20d355 + 3327411 commit d7e1ec0

2 files changed

Lines changed: 86 additions & 12 deletions

File tree

code/game/g_local.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,12 @@ typedef struct {
408408
int numSpawnVarChars;
409409
char spawnVarChars[MAX_SPAWN_VARS_CHARS];
410410

411+
#ifndef OLD_CHECK_EXIT_RULES
412+
// `CheckExitRulesLater()` has been called, and we'll need to
413+
// `CheckExitRules()` when we're done doing stuff.
414+
qboolean needToCheckExitRules;
415+
#endif
416+
411417
// intermission state
412418
int intermissionQueued; // intermission was qualified, but
413419
// wait INTERMISSION_DELAY_TIME before

code/game/g_main.c

Lines changed: 80 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ static void G_InitGame( int levelTime, int randomSeed, int restart );
3939
static void G_RunFrame( int levelTime );
4040
static void G_ShutdownGame( int restart );
4141
static void CheckExitRules( void );
42+
#ifndef OLD_CHECK_EXIT_RULES
43+
static void CheckExitRulesLater( void );
44+
#endif
4245
static void SendScoreboardMessageToAllClients( void );
4346

4447
// extension interface
@@ -59,40 +62,92 @@ This must be the very first function compiled into the .q3vm file
5962
================
6063
*/
6164
DLLEXPORT intptr_t vmMain( int command, int arg0, int arg1, int arg2 ) {
65+
int ret;
66+
6267
switch ( command ) {
6368
case GAME_INIT:
6469
G_InitGame( arg0, arg1, arg2 );
65-
return 0;
70+
ret = 0;
71+
break;
6672
case GAME_SHUTDOWN:
6773
G_ShutdownGame( arg0 );
68-
return 0;
74+
ret = 0;
75+
break;
6976
case GAME_CLIENT_CONNECT:
70-
return (intptr_t)ClientConnect( arg0, arg1, arg2 );
77+
ret = (intptr_t)ClientConnect( arg0, arg1, arg2 );
78+
break;
7179
case GAME_CLIENT_THINK:
7280
ClientThink( arg0 );
73-
return 0;
81+
ret = 0;
82+
break;
7483
case GAME_CLIENT_USERINFO_CHANGED:
7584
ClientUserinfoChanged( arg0 );
76-
return 0;
85+
ret = 0;
86+
break;
7787
case GAME_CLIENT_DISCONNECT:
7888
ClientDisconnect( arg0 );
79-
return 0;
89+
ret = 0;
90+
break;
8091
case GAME_CLIENT_BEGIN:
8192
ClientBegin( arg0 );
82-
return 0;
93+
ret = 0;
94+
break;
8395
case GAME_CLIENT_COMMAND:
8496
ClientCommand( arg0 );
85-
return 0;
97+
ret = 0;
98+
break;
8699
case GAME_RUN_FRAME:
87100
G_RunFrame( arg0 );
88-
return 0;
101+
ret = 0;
102+
break;
89103
case GAME_CONSOLE_COMMAND:
90-
return ConsoleCommand();
104+
ret = ConsoleCommand();
105+
break;
91106
case BOTAI_START_FRAME:
92-
return BotAIStartFrame( arg0 );
107+
ret = BotAIStartFrame( arg0 );
108+
break;
109+
default:
110+
ret = -1;
111+
}
112+
113+
#ifndef OLD_CHECK_EXIT_RULES
114+
// In vanilla we did `CheckExitRules()` after each frag,
115+
// which there can be multiple of when dealing with e.g. splash damage.
116+
// And the order in which players get killed is not well-defined:
117+
// e.g. for `G_RadiusDamage` and telefrag, kills get performed
118+
// in the order returned from `trap_EntitiesInBox`;
119+
// for the shotgun each pellet deals damage by itself,
120+
// and the order is random;
121+
// for the railgun the closest enemy gets damaged first.
122+
// That resulted in inconsistent rules when there is one frag left
123+
// and someone frags both a enemy and themself (or a teammate):
124+
// if the enemy is processed first then the attacker wins.
125+
// If a teammate is processed first then the game continues.
126+
// Let's fix this by only doing `CheckExitRules()`
127+
// when we're done processing the command.
128+
//
129+
// Note that this new behavior can result in e.g. two players
130+
// hitting the frag limit at the same time.
131+
// This can happen with missiles, which we run in `G_RunFrame()`.
132+
// In such a case `ScoreIsTied()` will kick in, and the game will continue
133+
// until the score gets untied, so this new behavior doesn't introduce
134+
// a "multiple winners" situation.
135+
//
136+
// One might argue that in case of two missiles exploding on the same frame,
137+
// the old behavoir is more fair, because `G_RunMissile()` is run
138+
// in the order in which the missiles were created,
139+
// so whoever shot first should win.
140+
// However, this isn't entirely obviously fair,
141+
// because two missiles can explode on the same frame
142+
// even if they were shot a long time apart.
143+
// For example, grenade launcher vs rocket launcher.
144+
// In this case let's just tie the score.
145+
if ( level.needToCheckExitRules ) {
146+
CheckExitRules();
93147
}
148+
#endif
94149

95-
return -1;
150+
return ret;
96151
}
97152

98153

@@ -856,7 +911,11 @@ void CalculateRanks( void ) {
856911
}
857912

858913
// see if it is time to end the level
914+
#ifndef OLD_CHECK_EXIT_RULES
915+
CheckExitRulesLater();
916+
#else
859917
CheckExitRules();
918+
#endif
860919

861920
// if we are at the intermission, send the new info to everyone
862921
if ( level.intermissiontime ) {
@@ -1323,6 +1382,10 @@ static void CheckExitRules( void ) {
13231382
int i;
13241383
gclient_t *cl;
13251384

1385+
#ifndef OLD_CHECK_EXIT_RULES
1386+
level.needToCheckExitRules = qfalse;
1387+
#endif
1388+
13261389
// if at the intermission, wait for all non-bots to
13271390
// signal ready, then go to next level
13281391
if ( level.intermissiontime ) {
@@ -1410,6 +1473,11 @@ static void CheckExitRules( void ) {
14101473
}
14111474
}
14121475
}
1476+
#ifndef OLD_CHECK_EXIT_RULES
1477+
static void CheckExitRulesLater( void ) {
1478+
level.needToCheckExitRules = qtrue;
1479+
}
1480+
#endif
14131481

14141482

14151483
static void ClearBodyQue( void ) {

0 commit comments

Comments
 (0)