Skip to content

Fix two latent Ground Zero bugs: random-respawn dmflag filters and tesla retargeting - #151

Merged
Yamagi merged 2 commits into
yquake2:masterfrom
Niehztog:fix-ground-zero-latent-bugs
Sep 6, 2026
Merged

Yamagi merged 2 commits into
yquake2:masterfrom
Niehztog:fix-ground-zero-latent-bugs

Conversation

@Niehztog

Copy link
Copy Markdown
Contributor

Two latent bugs from the original Ground Zero source, both in code that
silently does the opposite of what it says. Neither shows up as a compiler
diagnostic, and the tree builds clean with and without the changes.

1. FindSubstituteItem tests the wrong item (src/g_newdm.c)

The DF_NO_SPHERES, DF_NO_NUKES and DF_NO_MINES filters test
ent->classname — the item being replaced — inside the loop that is choosing
the item to replace it with. The condition does not depend on the loop, so it
fires for every candidate or for none, and the effect is the inverse of the
comment above the sphere filter: a disabled sphere respawns as itself, while
every other item can still be substituted into one. SpawnItem's own
DF_NO_SPHERES guard then frees that substitute, and DoRandomRespawn returns
the freed edict to DoRespawn, which relinks it as a solid trigger.

The third sphere classname is also misspelled "item_spehre_defender", so of
the three the Defender is the one DF_NO_SPHERES never recognised.

The 2023 remaster tests the candidate item in all three filters and spells the
Defender correctly.

The sphere filter only existed in the counting pass. Once the filters depend on
the candidate, both passes have to apply the same ones or they disagree on how
many items are eligible and the pick walks off the count — so both now share one
predicate, which identifies spheres by Pickup_Sphere, the same test
SpawnItem already uses for this dmflag.

2. A monster already angry at a tesla is retargeted anyway (src/monster/misc/move.c)

SV_movestep originally had four cases: no valid enemy, retarget; the enemy is
already a tesla, do nothing; the enemy is a player, retarget only when it cannot
be seen; any other enemy, retarget. The second was guarded by
strcmp(ent->enemy->classname, "telsa") — a misspelling — so it never ran and
fell through to the last case, retargeting after all.

Collapsing the chain into a single condition preserved the behaviour of the
misspelling rather than of the case it was written for: a tesla is not a client,
so !ent->enemy->client is true for a monster whose enemy is a tesla and it is
retargeted on every blocked frame.

TargetTesla skips the switch when it is the same tesla, but its AI_MEDIC
bail runs before that test, so a medic repeatedly calls cleanupHealTarget on
a tesla; and when the blocking area belongs to a different tesla the monster
switches to that one and overwrites oldenemy with a tesla, losing the player
it was chasing.

The remaster keeps all four cases and spells the classname correctly, so this
restores the exclusion rather than reintroducing the typo.

Testing

make is clean with no new warnings, before and after, on gcc 13 (Linux
aarch64). The two changes are independent and in separate commits if you would
rather take only one.

FindSubstituteItem chooses a replacement item for randomrespawn.  Its
DF_NO_SPHERES, DF_NO_NUKES and DF_NO_MINES filters test ent->classname,
the item being replaced, inside the loop that is choosing the item to
replace it with.  The condition does not depend on the loop, so the
effect is the inverse of the comment above the sphere filter: a disabled
sphere respawns as itself, while every other item can still be
substituted into one.  SpawnItem's own DF_NO_SPHERES guard then frees
that substitute, and DoRandomRespawn returns the freed edict to
DoRespawn, which relinks it as a solid trigger.

The third sphere classname is misspelled "item_spehre_defender" besides,
so of the three the Defender is the one DF_NO_SPHERES never recognised.

Both come from the original Ground Zero source.  The 2023 remaster tests
the candidate item in these three filters and spells the Defender
correctly, which is what this commit does as well.

The sphere filter only existed in the counting pass.  Once the filters
depend on the candidate, the counting pass and the picking pass have to
apply the same ones or they disagree on how many items are eligible and
the pick walks off the count, so both now share a single predicate.  It
identifies spheres by the pickup handler, the same test SpawnItem
already uses for DF_NO_SPHERES, rather than by three more classname
literals.
SV_movestep retargets a blocked monster onto the tesla whose bad area
blocks it.  The original had four cases: no valid enemy, retarget; the
enemy is already a tesla, do nothing; the enemy is a player, retarget
only when it cannot be seen; any other enemy, retarget.  The second was
guarded by strcmp(ent->enemy->classname, "telsa"), a misspelling, so it
never ran and its case fell through to the last one and retargeted after
all.

Collapsing the chain into a single condition kept the behaviour of the
misspelling rather than of the case it was written for: a tesla is not a
client, so !ent->enemy->client is true for a monster whose enemy is a
tesla and it is retargeted on every blocked frame.

TargetTesla skips the switch when the tesla is the one the monster is
already angry at, but its AI_MEDIC bail runs before that test, so a medic
repeatedly calls cleanupHealTarget on a tesla; and when the blocking area
belongs to a different tesla the monster switches to that one and
overwrites oldenemy with a tesla, losing the player it was chasing.

The 2023 remaster keeps all four cases and spells the classname
correctly, so restore the exclusion.
@Yamagi

Yamagi commented Sep 6, 2026

Copy link
Copy Markdown
Member

Thank you :)

@Yamagi
Yamagi merged commit da619df into yquake2:master Sep 6, 2026
9 checks passed
0lvin added a commit to yquake2/yquake2remaster that referenced this pull request Sep 6, 2026
Based on:
* yquake2/rogue#151

Co-authored-by: Niehztog <niehztog@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants