mirror of
https://github.com/BodbDearg/PsyDoom.git
synced 2024-11-23 05:19:52 +00:00
Map objects: implement a weak reference/pointer system for the 'target' and 'tracer' pointer fields.
(1) Do this to fix occasional undefined behavior with dangling pointers after certain enemies (like Lost Souls) have been removed from the game. I first noticed this bug when testing out the save file code; the system was trying to serialize a map object reference which had been invalidated. Fix the problem by using a construct similar to 'std::weak_ptr' but for map objects. (2 ) As part of this fix also, add extra null checks and verification for the 'target' and 'tracer' fields throughout the code, since they can now be nulled at any time. (3) Related: re-record NTSC DOOM MAP23 demo since it had undefined behavior that can't be reproduced, with dangling 'mobj_t' pointer fields. (4) Unrelated: fix a slight bug where the game was doing a line of sight check from the player to other map objects. (5) Unrelated: fix a crash playing demos via the '-playdemo' command where the map has a fire sky. Need to ensure the texture metrics for the sky texture are updated.
This commit is contained in:
parent
f2537621ad
commit
5cfcda4d4a
Binary file not shown.
@ -1,14 +1,14 @@
|
||||
{
|
||||
"player": {
|
||||
"x": -5131122,
|
||||
"y": 83782998,
|
||||
"x": -6150102,
|
||||
"y": 85349862,
|
||||
"z": 2621440,
|
||||
"angle": 1107820544,
|
||||
"momx": 53340,
|
||||
"momy": 879900,
|
||||
"momz": 0,
|
||||
"health": 171,
|
||||
"armorpoints": 75,
|
||||
"angle": 1078984704,
|
||||
"momx": -15330,
|
||||
"momy": 1095990,
|
||||
"momz": -262144,
|
||||
"health": 69,
|
||||
"armorpoints": 69,
|
||||
"armortype": 2,
|
||||
"powers": [
|
||||
0,
|
||||
@ -40,13 +40,13 @@
|
||||
0
|
||||
],
|
||||
"ammo": [
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
48
|
||||
0,
|
||||
36
|
||||
],
|
||||
"killcount": 28,
|
||||
"itemcount": 2,
|
||||
"itemcount": 1,
|
||||
"secretcount": 0
|
||||
}
|
||||
}
|
@ -117,6 +117,8 @@ set(SOURCE_FILES
|
||||
"Doom/Game/p_tick.h"
|
||||
"Doom/Game/p_user.cpp"
|
||||
"Doom/Game/p_user.h"
|
||||
"Doom/Game/p_weak.cpp"
|
||||
"Doom/Game/p_weak.h"
|
||||
"Doom/Game/sprinfo.cpp"
|
||||
"Doom/Game/sprinfo.h"
|
||||
"Doom/psx_main.cpp"
|
||||
|
@ -151,6 +151,7 @@ static void P_XYMovement(mobj_t& mobj) noexcept {
|
||||
//------------------------------------------------------------------------------------------------------------------------------------------
|
||||
static void P_FloatChange(mobj_t& mobj) noexcept {
|
||||
// Get the approximate distance to the target
|
||||
ASSERT(mobj.target);
|
||||
mobj_t& target = *mobj.target;
|
||||
const fixed_t approxDist = P_AproxDistance(target.x - mobj.x, target.y - mobj.y);
|
||||
|
||||
@ -648,17 +649,31 @@ static bool PB_CheckThing(mobj_t& mobj) noexcept {
|
||||
return true;
|
||||
|
||||
// Is the missile hitting the same species that it came from?
|
||||
const mobjtype_t sourceObjType = baseThing.target->type;
|
||||
// PsyDoom: add a safety check to ensure the firer still exists; this is needed now that we have weak 'mobj_t' pointers.
|
||||
mobj_t* const pFirer = baseThing.target;
|
||||
|
||||
if (sourceObjType == mobj.type) {
|
||||
// Colliding with the same species type: don't explode the missile if it's hitting the shooter of the missile
|
||||
if (&mobj == baseThing.target)
|
||||
return true;
|
||||
#if PSYDOOM_FIX_UB
|
||||
const bool bFirerExists = (pFirer != nullptr);
|
||||
#else
|
||||
// The original code did not check if the firer existed ('target' field) because that pointer should always be set for a missile.
|
||||
// The firer could technically be destroyed and freed however (undefined behavior) as the original code did not have weak pointers.
|
||||
constexpr bool bFirerExists = true;
|
||||
ASSERT(pFirer);
|
||||
#endif
|
||||
|
||||
// If it's hitting anything other than the player, explode the missile but do no damage (set no 'hit' thing).
|
||||
// Players can still damage each other with missiles however, hence the exception.
|
||||
if (sourceObjType != MT_PLAYER)
|
||||
return false;
|
||||
if (bFirerExists) {
|
||||
const mobjtype_t sourceObjType = pFirer->type;
|
||||
|
||||
if (sourceObjType == mobj.type) {
|
||||
// Colliding with the same species type: don't explode the missile if it's hitting the shooter of the missile
|
||||
if (&mobj == pFirer)
|
||||
return true;
|
||||
|
||||
// If it's hitting anything other than the player, explode the missile but do no damage (set no 'hit' thing).
|
||||
// Players can still damage each other with missiles however, hence the exception.
|
||||
if (sourceObjType != MT_PLAYER)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// So long as the thing is shootable then the missile can hit it
|
||||
|
@ -129,12 +129,13 @@ bool P_CheckMeleeRange(mobj_t& attacker) noexcept {
|
||||
return false;
|
||||
|
||||
// If there is no target then obviously nothing is in melee range
|
||||
if (!attacker.target)
|
||||
mobj_t* const pTarget = attacker.target;
|
||||
|
||||
if (!pTarget)
|
||||
return false;
|
||||
|
||||
// Return whether the target is within melee range
|
||||
mobj_t& target = *attacker.target;
|
||||
const fixed_t approxDist = P_AproxDistance(target.x - attacker.x, target.y - attacker.y);
|
||||
const fixed_t approxDist = P_AproxDistance(pTarget->x - attacker.x, pTarget->y - attacker.y);
|
||||
return (approxDist < MELEERANGE);
|
||||
}
|
||||
|
||||
@ -143,6 +144,8 @@ bool P_CheckMeleeRange(mobj_t& attacker) noexcept {
|
||||
// Takes into account things like range and current sight status and also adds an element of randomness.
|
||||
//------------------------------------------------------------------------------------------------------------------------------------------
|
||||
bool P_CheckMissileRange(mobj_t& attacker) noexcept {
|
||||
ASSERT(attacker.target);
|
||||
|
||||
// If the attacker can't see it's target then it can't do a missile attack
|
||||
if ((attacker.flags & MF_SEETARGET) == 0)
|
||||
return false;
|
||||
@ -158,7 +161,8 @@ bool P_CheckMissileRange(mobj_t& attacker) noexcept {
|
||||
return false;
|
||||
|
||||
// Get the distance to the target and do a tweak adjust for the sake of the random logic below
|
||||
fixed_t distFrac = P_AproxDistance(attacker.x - attacker.target->x, attacker.y - attacker.target->y);
|
||||
mobj_t& target = *attacker.target;
|
||||
fixed_t distFrac = P_AproxDistance(attacker.x - target.x, attacker.y - target.y);
|
||||
distFrac -= 64 * FRACUNIT;
|
||||
|
||||
// If the attacker has no melee attack then do a missile attack more frequently
|
||||
@ -250,7 +254,9 @@ bool P_TryWalk(mobj_t& actor) noexcept {
|
||||
//------------------------------------------------------------------------------------------------------------------------------------------
|
||||
void P_NewChaseDir(mobj_t& actor) noexcept {
|
||||
// Actor is expected to have a target if called
|
||||
if (!actor.target) {
|
||||
const mobj_t* const pTarget = actor.target;
|
||||
|
||||
if (!pTarget) {
|
||||
I_Error("P_NewChaseDir: called with no target");
|
||||
return;
|
||||
}
|
||||
@ -263,11 +269,11 @@ void P_NewChaseDir(mobj_t& actor) noexcept {
|
||||
// PsyDoom: if the external camera is active then make monsters walk away from their targets, to give the player time to react after the camera ends.
|
||||
#if PSYDOOM_MODS
|
||||
const int32_t tgtDistFlip = (gExtCameraTicsLeft > 0) ? -1 : 1;
|
||||
const fixed_t tgtDistX = (actor.target->x - actor.x) * tgtDistFlip;
|
||||
const fixed_t tgtDistY = (actor.target->y - actor.y) * tgtDistFlip;
|
||||
const fixed_t tgtDistX = (pTarget->x - actor.x) * tgtDistFlip;
|
||||
const fixed_t tgtDistY = (pTarget->y - actor.y) * tgtDistFlip;
|
||||
#else
|
||||
const fixed_t tgtDistX = actor.target->x - actor.x;
|
||||
const fixed_t tgtDistY = actor.target->y - actor.y;
|
||||
const fixed_t tgtDistX = pTarget->x - actor.x;
|
||||
const fixed_t tgtDistY = pTarget->y - actor.y;
|
||||
#endif
|
||||
|
||||
dirtype_t hdirToTgt;
|
||||
@ -581,16 +587,17 @@ void A_Chase(mobj_t& actor) noexcept {
|
||||
//------------------------------------------------------------------------------------------------------------------------------------------
|
||||
void A_FaceTarget(mobj_t& actor) noexcept {
|
||||
// Can't face target if there is none
|
||||
if (!actor.target)
|
||||
mobj_t* const pTarget = actor.target;
|
||||
|
||||
if (!pTarget)
|
||||
return;
|
||||
|
||||
// Monster is no longer in ambush mode and turn to face the target
|
||||
mobj_t& target = *actor.target;
|
||||
actor.flags &= ~MF_AMBUSH;
|
||||
actor.angle = R_PointToAngle2(actor.x, actor.y, target.x, target.y);
|
||||
actor.angle = R_PointToAngle2(actor.x, actor.y, pTarget->x, pTarget->y);
|
||||
|
||||
// If the target has partial invisbility then vary the angle randomly a bit (by almost 45 degrees)
|
||||
if (target.flags & MF_ALL_BLEND_FLAGS) {
|
||||
if (pTarget->flags & MF_ALL_BLEND_FLAGS) {
|
||||
actor.angle += P_SubRandom() * (ANG45 / 256);
|
||||
}
|
||||
}
|
||||
@ -718,18 +725,22 @@ void A_SpidRefire(mobj_t& actor) noexcept {
|
||||
// Does the attack for an Arachnotron
|
||||
//------------------------------------------------------------------------------------------------------------------------------------------
|
||||
void A_BspiAttack(mobj_t& actor) noexcept {
|
||||
if (!actor.target)
|
||||
mobj_t* const pTarget = actor.target;
|
||||
|
||||
if (!pTarget)
|
||||
return;
|
||||
|
||||
A_FaceTarget(actor);
|
||||
P_SpawnMissile(actor, *actor.target, MT_ARACHPLAZ);
|
||||
P_SpawnMissile(actor, *pTarget, MT_ARACHPLAZ);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------------------------------------
|
||||
// Does the attack for an Imp, which can either be a melee attack or sending a fireball towards the target
|
||||
//------------------------------------------------------------------------------------------------------------------------------------------
|
||||
void A_TroopAttack(mobj_t& actor) noexcept {
|
||||
if (!actor.target)
|
||||
mobj_t* const pTarget = actor.target;
|
||||
|
||||
if (!pTarget)
|
||||
return;
|
||||
|
||||
A_FaceTarget(actor);
|
||||
@ -738,9 +749,9 @@ void A_TroopAttack(mobj_t& actor) noexcept {
|
||||
if (P_CheckMeleeRange(actor)) {
|
||||
S_StartSound(&actor, sfx_claw);
|
||||
const int32_t damage = ((P_Random() & 7) + 1) * 3; // 3-24 damage
|
||||
P_DamageMobj(*actor.target, &actor, &actor, damage);
|
||||
P_DamageMobj(*pTarget, &actor, &actor, damage);
|
||||
} else {
|
||||
P_SpawnMissile(actor, *actor.target, MT_TROOPSHOT);
|
||||
P_SpawnMissile(actor, *pTarget, MT_TROOPSHOT);
|
||||
}
|
||||
}
|
||||
|
||||
@ -760,7 +771,9 @@ void A_SargAttack(mobj_t& actor) noexcept {
|
||||
// Does the attack for a Cacodemon, which can either be a melee attack or sending a fireball towards the target
|
||||
//------------------------------------------------------------------------------------------------------------------------------------------
|
||||
void A_HeadAttack(mobj_t& actor) noexcept {
|
||||
if (!actor.target)
|
||||
mobj_t* const pTarget = actor.target;
|
||||
|
||||
if (!pTarget)
|
||||
return;
|
||||
|
||||
A_FaceTarget(actor);
|
||||
@ -768,9 +781,9 @@ void A_HeadAttack(mobj_t& actor) noexcept {
|
||||
// Do a melee attack if possible, otherwise spawn a fireball
|
||||
if (P_CheckMeleeRange(actor)) {
|
||||
const int32_t damage = ((P_Random() & 7) + 1) * 8; // 8-64 damage
|
||||
P_DamageMobj(*actor.target, &actor, &actor, damage);
|
||||
P_DamageMobj(*pTarget, &actor, &actor, damage);
|
||||
} else {
|
||||
P_SpawnMissile(actor, *actor.target, MT_HEADSHOT);
|
||||
P_SpawnMissile(actor, *pTarget, MT_HEADSHOT);
|
||||
}
|
||||
}
|
||||
|
||||
@ -778,28 +791,30 @@ void A_HeadAttack(mobj_t& actor) noexcept {
|
||||
// Does the attack for a Cyberdemon
|
||||
//------------------------------------------------------------------------------------------------------------------------------------------
|
||||
void A_CyberAttack(mobj_t& actor) noexcept {
|
||||
if (!actor.target)
|
||||
mobj_t* const pTarget = actor.target;
|
||||
|
||||
if (!pTarget)
|
||||
return;
|
||||
|
||||
A_FaceTarget(actor);
|
||||
P_SpawnMissile(actor, *actor.target, MT_ROCKET);
|
||||
P_SpawnMissile(actor, *pTarget, MT_ROCKET);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------------------------------------
|
||||
// Does the attack for a Baron or Hell Knight, which can either be a melee attack or sending a fireball towards the target
|
||||
//------------------------------------------------------------------------------------------------------------------------------------------
|
||||
void A_BruisAttack(mobj_t& actor) noexcept {
|
||||
if (!actor.target)
|
||||
return;
|
||||
mobj_t* const pTarget = actor.target;
|
||||
|
||||
mobj_t& target = *actor.target;
|
||||
if (!pTarget)
|
||||
return;
|
||||
|
||||
if (P_CheckMeleeRange(actor)) {
|
||||
S_StartSound(&actor, sfx_claw);
|
||||
const int32_t damage = ((P_Random() & 7) + 1) * 11; // 11-88 damage
|
||||
P_DamageMobj(target, &actor, &actor, damage);
|
||||
P_DamageMobj(*pTarget, &actor, &actor, damage);
|
||||
} else {
|
||||
P_SpawnMissile(actor, target, MT_BRUISERSHOT);
|
||||
P_SpawnMissile(actor, *pTarget, MT_BRUISERSHOT);
|
||||
}
|
||||
}
|
||||
|
||||
@ -807,20 +822,22 @@ void A_BruisAttack(mobj_t& actor) noexcept {
|
||||
// Make a Revenant fire it's missile towards a player
|
||||
//------------------------------------------------------------------------------------------------------------------------------------------
|
||||
void A_SkelMissile(mobj_t& actor) noexcept {
|
||||
if (!actor.target)
|
||||
mobj_t* const pTarget = actor.target;
|
||||
|
||||
if (!pTarget)
|
||||
return;
|
||||
|
||||
A_FaceTarget(actor);
|
||||
|
||||
// Spawn the missile: also hack adjust the Revenant height slightly (temporarily) so the missile spawns higher
|
||||
actor.z += 16 * FRACUNIT;
|
||||
mobj_t& missile = *P_SpawnMissile(actor, *actor.target, MT_TRACER);
|
||||
mobj_t& missile = *P_SpawnMissile(actor, *pTarget, MT_TRACER);
|
||||
actor.z -= 16 * FRACUNIT;
|
||||
|
||||
// Move the missile a little and set it's target
|
||||
missile.x += missile.momx;
|
||||
missile.y += missile.momy;
|
||||
missile.tracer = actor.target;
|
||||
missile.tracer = pTarget;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@ -903,7 +920,9 @@ void A_SkelWhoosh(mobj_t& actor) noexcept {
|
||||
// Does the melee attack of the Revenant
|
||||
//------------------------------------------------------------------------------------------------------------------------------------------
|
||||
void A_SkelFist(mobj_t& actor) noexcept {
|
||||
if (!actor.target)
|
||||
mobj_t* const pTarget = actor.target;
|
||||
|
||||
if (!pTarget)
|
||||
return;
|
||||
|
||||
A_FaceTarget(actor);
|
||||
@ -911,7 +930,7 @@ void A_SkelFist(mobj_t& actor) noexcept {
|
||||
if (P_CheckMeleeRange(actor)) {
|
||||
S_StartSound(&actor, sfx_skepch);
|
||||
const int32_t damage = (P_Random() % 10 + 1) * 6; // 6-60 damage
|
||||
P_DamageMobj(*actor.target, &actor, &actor, damage);
|
||||
P_DamageMobj(*pTarget, &actor, &actor, damage);
|
||||
}
|
||||
}
|
||||
|
||||
@ -928,21 +947,22 @@ void A_FatRaise(mobj_t& actor) noexcept {
|
||||
//------------------------------------------------------------------------------------------------------------------------------------------
|
||||
void A_FatAttack1(mobj_t& actor) noexcept {
|
||||
// PsyDoom: avoid undefined behavior if for some reason there is no target
|
||||
mobj_t* const pTarget = actor.target;
|
||||
|
||||
#if PSYDOOM_MODS && PSYDOOM_FIX_UB
|
||||
if (!actor.target)
|
||||
if (!pTarget)
|
||||
return;
|
||||
#else
|
||||
ASSERT(actor.target);
|
||||
ASSERT(pTarget);
|
||||
#endif
|
||||
|
||||
A_FaceTarget(actor);
|
||||
mobj_t& target = *actor.target;
|
||||
|
||||
// Spawn the projectiles for this round and adjust the mancubus's aim
|
||||
actor.angle += FATSPREAD;
|
||||
P_SpawnMissile(actor, target, MT_FATSHOT);
|
||||
P_SpawnMissile(actor, *pTarget, MT_FATSHOT);
|
||||
|
||||
mobj_t& missile = *P_SpawnMissile(actor, target, MT_FATSHOT);
|
||||
mobj_t& missile = *P_SpawnMissile(actor, *pTarget, MT_FATSHOT);
|
||||
missile.angle += FATSPREAD;
|
||||
|
||||
const uint32_t missileFineAngle = missile.angle >> ANGLETOFINESHIFT;
|
||||
@ -955,21 +975,22 @@ void A_FatAttack1(mobj_t& actor) noexcept {
|
||||
//------------------------------------------------------------------------------------------------------------------------------------------
|
||||
void A_FatAttack2(mobj_t& actor) noexcept {
|
||||
// PsyDoom: avoid undefined behavior if for some reason there is no target
|
||||
mobj_t* const pTarget = actor.target;
|
||||
|
||||
#if PSYDOOM_MODS && PSYDOOM_FIX_UB
|
||||
if (!actor.target)
|
||||
if (!pTarget)
|
||||
return;
|
||||
#else
|
||||
ASSERT(actor.target);
|
||||
ASSERT(pTarget);
|
||||
#endif
|
||||
|
||||
A_FaceTarget(actor);
|
||||
mobj_t& target = *actor.target;
|
||||
|
||||
// Spawn the projectiles for this round and adjust the mancubus's aim
|
||||
actor.angle -= FATSPREAD;
|
||||
P_SpawnMissile(actor, target, MT_FATSHOT);
|
||||
P_SpawnMissile(actor, *pTarget, MT_FATSHOT);
|
||||
|
||||
mobj_t& missile = *P_SpawnMissile(actor, target, MT_FATSHOT);
|
||||
mobj_t& missile = *P_SpawnMissile(actor, *pTarget, MT_FATSHOT);
|
||||
missile.angle -= FATSPREAD * 2;
|
||||
|
||||
const uint32_t missileFineAngle = missile.angle >> ANGLETOFINESHIFT;
|
||||
@ -982,19 +1003,20 @@ void A_FatAttack2(mobj_t& actor) noexcept {
|
||||
//------------------------------------------------------------------------------------------------------------------------------------------
|
||||
void A_FatAttack3(mobj_t& actor) noexcept {
|
||||
// PsyDoom: avoid undefined behavior if for some reason there is no target
|
||||
mobj_t* const pTarget = actor.target;
|
||||
|
||||
#if PSYDOOM_MODS && PSYDOOM_FIX_UB
|
||||
if (!actor.target)
|
||||
if (!pTarget)
|
||||
return;
|
||||
#else
|
||||
ASSERT(actor.target);
|
||||
ASSERT(pTarget);
|
||||
#endif
|
||||
|
||||
A_FaceTarget(actor);
|
||||
mobj_t& target = *actor.target;
|
||||
|
||||
// Spawn the projectiles for this round and adjust the mancubus's aim
|
||||
{
|
||||
mobj_t& missile = *P_SpawnMissile(actor, target, MT_FATSHOT);
|
||||
mobj_t& missile = *P_SpawnMissile(actor, *pTarget, MT_FATSHOT);
|
||||
missile.angle -= FATSPREAD / 2;
|
||||
|
||||
const uint32_t missileFineAngle = missile.angle >> ANGLETOFINESHIFT;
|
||||
@ -1003,7 +1025,7 @@ void A_FatAttack3(mobj_t& actor) noexcept {
|
||||
}
|
||||
|
||||
{
|
||||
mobj_t& missile = *P_SpawnMissile(actor, target, MT_FATSHOT);
|
||||
mobj_t& missile = *P_SpawnMissile(actor, *pTarget, MT_FATSHOT);
|
||||
missile.angle += FATSPREAD / 2;
|
||||
|
||||
const uint32_t missileFineAngle = missile.angle >> ANGLETOFINESHIFT;
|
||||
@ -1016,7 +1038,9 @@ void A_FatAttack3(mobj_t& actor) noexcept {
|
||||
// Does the attack for a Lost Soul, sets it flying towards it's target
|
||||
//------------------------------------------------------------------------------------------------------------------------------------------
|
||||
void A_SkullAttack(mobj_t& actor) noexcept {
|
||||
if (!actor.target)
|
||||
mobj_t* const pTarget = actor.target;
|
||||
|
||||
if (!pTarget)
|
||||
return;
|
||||
|
||||
// Skull is now flying, play the attack sound and face the target
|
||||
@ -1031,11 +1055,9 @@ void A_SkullAttack(mobj_t& actor) noexcept {
|
||||
actor.momy = FixedMul(SKULLSPEED, gFineSine[actorFineAngle]);
|
||||
|
||||
// Figure out the z velocity based on the travel time and z delta to the target
|
||||
mobj_t& target = *actor.target;
|
||||
|
||||
const fixed_t distToTgt = P_AproxDistance(target.x - actor.x, target.y - actor.y);
|
||||
const fixed_t distToTgt = P_AproxDistance(pTarget->x - actor.x, pTarget->y - actor.y);
|
||||
const int32_t travelTime = std::max(distToTgt / SKULLSPEED, 1);
|
||||
const fixed_t zDelta = target.z + d_rshift<1>(target.height) - actor.z;
|
||||
const fixed_t zDelta = pTarget->z + d_rshift<1>(pTarget->height) - actor.z;
|
||||
|
||||
actor.momz = zDelta / travelTime;
|
||||
}
|
||||
@ -1492,21 +1514,19 @@ void A_VileStart(mobj_t& actor) noexcept {
|
||||
// Spawns Arch-vile's fire and makes it face the target
|
||||
//------------------------------------------------------------------------------------------------------------------------------------------
|
||||
void A_VileTarget(mobj_t& actor) noexcept {
|
||||
// Don't do anything if there is no target, otherwise face it
|
||||
if (!actor.target)
|
||||
// Don't do anything if there is no target, otherwise face it and spawn the fire
|
||||
mobj_t* const pTarget = actor.target;
|
||||
|
||||
if (!pTarget)
|
||||
return;
|
||||
|
||||
A_FaceTarget(actor);
|
||||
|
||||
// Spawn the fire
|
||||
ASSERT(actor.target);
|
||||
mobj_t& target = *actor.target;
|
||||
mobj_t& fire = *P_SpawnMobj(target.x, target.x, target.z, MT_FIRE);
|
||||
mobj_t& fire = *P_SpawnMobj(pTarget->x, pTarget->x, pTarget->z, MT_FIRE);
|
||||
|
||||
// Make the Arch-vile remember the fire, and the fire remember the Arch-vile and it's target
|
||||
actor.tracer = &fire;
|
||||
fire.target = &actor;
|
||||
fire.tracer = ⌖
|
||||
fire.tracer = pTarget;
|
||||
|
||||
// Move the fire to follow the Arch-vile's target
|
||||
A_Fire(fire);
|
||||
@ -1517,22 +1537,21 @@ void A_VileTarget(mobj_t& actor) noexcept {
|
||||
//------------------------------------------------------------------------------------------------------------------------------------------
|
||||
void A_VileAttack(mobj_t& actor) noexcept {
|
||||
// Don't do anything if there is no target, otherwise face it
|
||||
if (!actor.target)
|
||||
mobj_t* const pTarget = actor.target;
|
||||
|
||||
if (!pTarget)
|
||||
return;
|
||||
|
||||
A_FaceTarget(actor);
|
||||
|
||||
// If there is no line of sight now to the target then don't do the attack
|
||||
ASSERT(actor.target);
|
||||
mobj_t& target = *actor.target;
|
||||
|
||||
if (!P_CheckSight(actor, target))
|
||||
if (!P_CheckSight(actor, *pTarget))
|
||||
return;
|
||||
|
||||
// Play the attack sound, damage the target and make the target hop upwards
|
||||
S_StartSound(&actor, sfx_barexp);
|
||||
P_DamageMobj(target, &actor, &actor, 20);
|
||||
target.momz = (1000 * FRACUNIT) / target.info->mass;
|
||||
P_DamageMobj(*pTarget, &actor, &actor, 20);
|
||||
pTarget->momz = (1000 * FRACUNIT) / pTarget->info->mass;
|
||||
|
||||
// Don't do splash damage unless there is fire
|
||||
mobj_t* const pFire = actor.tracer;
|
||||
@ -1542,8 +1561,8 @@ void A_VileAttack(mobj_t& actor) noexcept {
|
||||
|
||||
// Make sure the fire is in front of the target
|
||||
const uint32_t angleIdx = actor.angle >> ANGLETOFINESHIFT;
|
||||
pFire->x = target.x - FixedMul(24 * FRACUNIT, gFineCosine[angleIdx]);
|
||||
pFire->y = target.y - FixedMul(24 * FRACUNIT, gFineSine[angleIdx]);
|
||||
pFire->x = pTarget->x - FixedMul(24 * FRACUNIT, gFineCosine[angleIdx]);
|
||||
pFire->y = pTarget->y - FixedMul(24 * FRACUNIT, gFineSine[angleIdx]);
|
||||
|
||||
// Do the splash damage at the fire location
|
||||
P_RadiusAttack(*pFire, &actor, 70);
|
||||
@ -1559,11 +1578,11 @@ void A_Fire(mobj_t& actor) noexcept {
|
||||
if (!pVileTgt)
|
||||
return;
|
||||
|
||||
// Don't move the fire if there is no line of sight between it and it's target
|
||||
ASSERT(actor.target);
|
||||
mobj_t& vileMobj = *actor.target;
|
||||
// Don't move the fire if there is no line of sight between the arch vile and it's target
|
||||
mobj_t* const pVileMobj = actor.target;
|
||||
const bool bVileHasLineOfSight = (pVileMobj && P_CheckSight(*pVileMobj, *pVileTgt));
|
||||
|
||||
if (!P_CheckSight(vileMobj, *pVileTgt))
|
||||
if (!bVileHasLineOfSight)
|
||||
return;
|
||||
|
||||
// Place the fire in front of the Arch-vile's target
|
||||
@ -1722,52 +1741,54 @@ void A_SpawnFly(mobj_t& actor) noexcept {
|
||||
if (--actor.reactiontime > 0)
|
||||
return;
|
||||
|
||||
// Do the spawn fire effect (re-uses Arch-vile fire)
|
||||
ASSERT(actor.target);
|
||||
mobj_t& target = *actor.target;
|
||||
// Note: the target should always exist, but add a safety check here just in case it doesn't...
|
||||
mobj_t* const pTarget = actor.target;
|
||||
|
||||
{
|
||||
mobj_t& fireFx = *P_SpawnMobj(target.x, target.y, target.z, MT_SPAWNFIRE);
|
||||
if (pTarget) {
|
||||
// Do the spawn fire effect (re-uses Arch-vile fire).
|
||||
mobj_t& fireFx = *P_SpawnMobj(pTarget->x, pTarget->y, pTarget->z, MT_SPAWNFIRE);
|
||||
S_StartSound(&fireFx, sfx_telept);
|
||||
|
||||
// Randomly decide which enemy to spawn.
|
||||
// Lower level enemies are generally weighted higher in terms of probability.
|
||||
const int32_t randNum = P_Random();
|
||||
mobjtype_t spawnType;
|
||||
|
||||
if (randNum < 50) {
|
||||
spawnType = MT_TROOP;
|
||||
} else if (randNum < 120) {
|
||||
spawnType = MT_SERGEANT;
|
||||
} else if (randNum < 130) {
|
||||
spawnType = MT_PAIN;
|
||||
} else if (randNum < 160) {
|
||||
spawnType = MT_HEAD;
|
||||
} else if (randNum < 162) {
|
||||
spawnType = MT_VILE;
|
||||
} else if (randNum < 172) {
|
||||
spawnType = MT_UNDEAD;
|
||||
} else if (randNum < 192) {
|
||||
spawnType = MT_BABY;
|
||||
} else if (randNum < 222) {
|
||||
spawnType = MT_FATSO;
|
||||
} else if (randNum < 246) {
|
||||
spawnType = MT_KNIGHT;
|
||||
} else {
|
||||
spawnType = MT_BRUISER;
|
||||
}
|
||||
|
||||
// Spawn the enemy and alert it immediately
|
||||
mobj_t& spawned = *P_SpawnMobj(pTarget->x, pTarget->y, pTarget->z, spawnType);
|
||||
|
||||
if (P_LookForPlayers(spawned, true)) {
|
||||
P_SetMobjState(spawned, spawned.info->seestate);
|
||||
}
|
||||
|
||||
// Telefrag anything where the enemy spawned and remove the cube.
|
||||
// Note: do not allow self-telefragging!
|
||||
P_Telefrag(spawned, spawned.x, spawned.y, false);
|
||||
}
|
||||
|
||||
// Randomly decide which enemy to spawn.
|
||||
// Lower level enemies are generally weighted higher in terms of probability.
|
||||
const int32_t randNum = P_Random();
|
||||
mobjtype_t spawnType;
|
||||
|
||||
if (randNum < 50) {
|
||||
spawnType = MT_TROOP;
|
||||
} else if (randNum < 120) {
|
||||
spawnType = MT_SERGEANT;
|
||||
} else if (randNum < 130) {
|
||||
spawnType = MT_PAIN;
|
||||
} else if (randNum < 160) {
|
||||
spawnType = MT_HEAD;
|
||||
} else if (randNum < 162) {
|
||||
spawnType = MT_VILE;
|
||||
} else if (randNum < 172) {
|
||||
spawnType = MT_UNDEAD;
|
||||
} else if (randNum < 192) {
|
||||
spawnType = MT_BABY;
|
||||
} else if (randNum < 222) {
|
||||
spawnType = MT_FATSO;
|
||||
} else if (randNum < 246) {
|
||||
spawnType = MT_KNIGHT;
|
||||
} else {
|
||||
spawnType = MT_BRUISER;
|
||||
}
|
||||
|
||||
// Spawn the enemy and alert it immediately
|
||||
mobj_t& spawned = *P_SpawnMobj(target.x, target.y, target.z, spawnType);
|
||||
|
||||
if (P_LookForPlayers(spawned, true)) {
|
||||
P_SetMobjState(spawned, spawned.info->seestate);
|
||||
}
|
||||
|
||||
// Telefrag anything where the enemy spawned and remove the cube.
|
||||
// Note: do not allow self-telefragging!
|
||||
P_Telefrag(spawned, spawned.x, spawned.y, false);
|
||||
// Remove the spawner cube
|
||||
P_RemoveMobj(actor);
|
||||
}
|
||||
#endif // #if PSYDOOM_MODS
|
||||
|
@ -63,6 +63,12 @@ void P_RemoveMobj(mobj_t& mobj) noexcept {
|
||||
// Remove from the global linked list of things and deallocate
|
||||
mobj.next->prev = mobj.prev;
|
||||
mobj.prev->next = mobj.next;
|
||||
|
||||
#if PSYDOOM_MODS
|
||||
P_WeakReferencedDestroyed(mobj); // PsyDoom: weak references to this object are now nulled
|
||||
mobj.~mobj_t(); // PsyDoom: destroy C++ weak pointers
|
||||
#endif
|
||||
|
||||
Z_Free2(*gpMainMemZone, &mobj);
|
||||
}
|
||||
|
||||
@ -202,6 +208,10 @@ mobj_t* P_SpawnMobj(const fixed_t x, const fixed_t y, const fixed_t z, const mob
|
||||
mobj_t& mobj = *(mobj_t*) Z_Malloc(*gpMainMemZone, sizeof(mobj_t), PU_LEVEL, nullptr);
|
||||
D_memset(&mobj, std::byte(0), sizeof(mobj_t));
|
||||
|
||||
#if PSYDOOM_MODS
|
||||
new (&mobj) mobj_t(); // PsyDoom: call C++ constructors for weak pointers
|
||||
#endif
|
||||
|
||||
// Fill in basic fields
|
||||
mobjinfo_t& info = gMobjInfo[type];
|
||||
mobj.type = type;
|
||||
|
@ -535,17 +535,29 @@ static bool PIT_CheckThing(mobj_t& mobj) noexcept {
|
||||
|
||||
// If we are colliding with the same species which fired the missile in most cases explode/collide the missile, but don't damage what was hit.
|
||||
// The firing thing is in the 'target' field for missiles.
|
||||
mobj_t& firingThing = *tryMoveThing.target;
|
||||
// PsyDoom: add a safety check to ensure the firer still exists; this is needed now that we have weak 'mobj_t' pointers.
|
||||
mobj_t* const pFiringThing = tryMoveThing.target;
|
||||
|
||||
if (mobj.type == firingThing.type) {
|
||||
// Missiles don't collide with the things which fired them
|
||||
if (&mobj == &firingThing)
|
||||
return true;
|
||||
#if PSYDOOM_FIX_UB
|
||||
const bool bFirerExists = (pFiringThing != nullptr);
|
||||
#else
|
||||
// The original code did not check if the firer existed ('target' field) because that pointer should always be set for a missile.
|
||||
// The firer could technically be destroyed and freed however (undefined behavior) as the original code did not have weak pointers.
|
||||
constexpr bool bFirerExists = true;
|
||||
ASSERT(pFiringThing);
|
||||
#endif
|
||||
|
||||
// Explode, but do no damage by just returning 'false' and not saving what was hit.
|
||||
// The exception to this is if the thing type is a player; players can splash damage other players with rockets...
|
||||
if (mobj.type != MT_PLAYER)
|
||||
return false;
|
||||
if (bFirerExists) {
|
||||
if (mobj.type == pFiringThing->type) {
|
||||
// Missiles don't collide with the things which fired them
|
||||
if (&mobj == pFiringThing)
|
||||
return true;
|
||||
|
||||
// Explode, but do no damage by just returning 'false' and not saving what was hit.
|
||||
// The exception to this is if the thing type is a player; players can splash damage other players with rockets...
|
||||
if (mobj.type != MT_PLAYER)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// If the thing hit is shootable then save it for damage purposes and return 'false' for a collision
|
||||
|
@ -797,9 +797,17 @@ void A_Light2(player_t& player, [[maybe_unused]] pspdef_t& sprite) noexcept {
|
||||
// Up to 40 explosions are created.
|
||||
//------------------------------------------------------------------------------------------------------------------------------------------
|
||||
void A_BFGSpray(mobj_t& mobj) noexcept {
|
||||
// PsyDoom: add extra assert here - it's expected the map object has a target (the firer of the BFG, i.e the player thing)
|
||||
ASSERT(mobj.target);
|
||||
mobj_t& target = *mobj.target;
|
||||
// PsyDoom: added an extra check to make sure the firer still exists (this reference is now a weak pointer)
|
||||
mobj_t* const pBfgFirer = mobj.target;
|
||||
|
||||
#if PSYDOOM_FIX_UB
|
||||
if (!pBfgFirer)
|
||||
return;
|
||||
#else
|
||||
// The original code did not check if the firer existed ('target' field) because that pointer should always be set for a BFG projectile.
|
||||
// The firer could technically be destroyed and freed however (undefined behavior) as the original code did not have weak pointers.
|
||||
ASSERT(pBfgFirer);
|
||||
#endif
|
||||
|
||||
// PsyDoom: spray/tracer range can be further extended to '2048' units
|
||||
#if PSYDOOM_MODS
|
||||
@ -812,7 +820,7 @@ void A_BFGSpray(mobj_t& mobj) noexcept {
|
||||
constexpr int32_t NUM_EXPLOSIONS = 40;
|
||||
|
||||
for (int32_t explosionIdx = 0; explosionIdx < NUM_EXPLOSIONS; ++explosionIdx) {
|
||||
P_AimLineAttack(target, mobj.angle - ANG45 + (ANG90 / NUM_EXPLOSIONS) * explosionIdx, attackRange);
|
||||
P_AimLineAttack(*pBfgFirer, mobj.angle - ANG45 + (ANG90 / NUM_EXPLOSIONS) * explosionIdx, attackRange);
|
||||
mobj_t* const pLineTarget = gpLineTarget;
|
||||
|
||||
if (!pLineTarget)
|
||||
@ -828,7 +836,7 @@ void A_BFGSpray(mobj_t& mobj) noexcept {
|
||||
damageAmt += (P_Random() & 7) + 1;
|
||||
}
|
||||
|
||||
P_DamageMobj(*pLineTarget, &target, &target, damageAmt);
|
||||
P_DamageMobj(*pLineTarget, pBfgFirer, pBfgFirer, damageAmt);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -25,6 +25,7 @@
|
||||
#include "p_spec.h"
|
||||
#include "p_switch.h"
|
||||
#include "p_tick.h"
|
||||
#include "p_weak.h"
|
||||
#include "PsyDoom/DevMapAutoReloader.h"
|
||||
#include "PsyDoom/Game.h"
|
||||
#include "PsyDoom/MapHash.h"
|
||||
@ -1237,6 +1238,15 @@ static void P_Init() noexcept {
|
||||
gPaletteClutId_CurMapSky = gPaletteClutIds[FIRESKYPAL];
|
||||
gUpdateFireSkyFunc = P_UpdateFireSky;
|
||||
|
||||
// PsyDoom: updates to work with the new WAD management code - ensure texture metrics are up-to-date!
|
||||
#if PSYDOOM_MODS
|
||||
{
|
||||
const WadLump& skyTexWadLump = W_GetLump(skyTex.lumpNum);
|
||||
const std::byte* const pLumpData = (const std::byte*) skyTexWadLump.pCachedData;
|
||||
R_UpdateTexMetricsFromData(skyTex, pLumpData, skyTexWadLump.uncompressedSize);
|
||||
}
|
||||
#endif
|
||||
|
||||
// This gets the fire going, so it doesn't take a while to creep up when the map is started.
|
||||
// Do a number of fire update rounds before the player even enters the map:
|
||||
for (int32_t i = 0; i < 64; ++i) {
|
||||
@ -1418,6 +1428,11 @@ void P_SetupLevel(const int32_t mapNum, [[maybe_unused]] const skill_t skill) no
|
||||
Z_CheckHeap(*gpMainMemZone);
|
||||
M_ClearRandom();
|
||||
|
||||
// PsyDoom: initialize the map object weak referencing system
|
||||
#if PSYDOOM_MODS
|
||||
P_InitWeakRefs();
|
||||
#endif
|
||||
|
||||
// PsyDoom limit removing: init the sets of wall and flat textures to be loaded
|
||||
#if PSYDOOM_LIMIT_REMOVING
|
||||
gCacheTextureSet.init(gNumTexLumps);
|
||||
|
@ -32,10 +32,12 @@ void P_CheckSights() noexcept {
|
||||
for (mobj_t* pmobj = gMobjHead.next; pmobj != &gMobjHead; pmobj = pmobj->next) {
|
||||
// Must be killable (enemy) to do sight checking.
|
||||
//
|
||||
// PsyDoom: extend the sight check to types that include a 'see state' in order to allow the reimplemented 'Icon Of Sin' boss to spot the player.
|
||||
// PsyDoom: extend the sight check to types that include a 'see state' (except the player) in order to allow the reimplemented 'Icon Of Sin' boss to spot the player.
|
||||
// This doesn't cause any demo de-sync against original game demos so I've made this update non-optional.
|
||||
#if PSYDOOM_MODS
|
||||
const bool bCheckSight = ((pmobj->flags & MF_COUNTKILL) || pmobj->info->seestate);
|
||||
const bool bHasSeeState = (pmobj->info->seestate != S_NULL);
|
||||
const bool bIsNotPlayer = (pmobj->type != MT_PLAYER);
|
||||
const bool bCheckSight = ((pmobj->flags & MF_COUNTKILL) || (bHasSeeState && bIsNotPlayer));
|
||||
#else
|
||||
const bool bCheckSight = (pmobj->flags & MF_COUNTKILL);
|
||||
#endif
|
||||
@ -47,7 +49,9 @@ void P_CheckSights() noexcept {
|
||||
if (pmobj->tics == 1) {
|
||||
// See if we can see the target - if any.
|
||||
// Add or remove the visibility flag based on this:
|
||||
if (pmobj->target && P_CheckSight(*pmobj, *pmobj->target)) {
|
||||
mobj_t* const pMobjTarget = pmobj->target;
|
||||
|
||||
if (pMobjTarget && P_CheckSight(*pmobj, *pMobjTarget)) {
|
||||
pmobj->flags |= MF_SEETARGET;
|
||||
} else {
|
||||
pmobj->flags &= (~MF_SEETARGET); // No longer can see target
|
||||
|
@ -846,6 +846,16 @@ void P_Stop([[maybe_unused]] const gameaction_t exitAction) noexcept {
|
||||
ScriptingEngine::shutdown();
|
||||
DevMapAutoReloader::shutdown();
|
||||
#endif
|
||||
|
||||
// PsyDoom: shut down the map object weak referencing system.
|
||||
// Also cleanup all map objects to remove all usage of weak reference counts before we shutdown the system.
|
||||
#if PSYDOOM_MODS
|
||||
while (gMobjHead.next != &gMobjHead) {
|
||||
P_RemoveMobj(*gMobjHead.next);
|
||||
}
|
||||
|
||||
P_ShutdownWeakRefs();
|
||||
#endif
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
188
game/Doom/Game/p_weak.cpp
Normal file
188
game/Doom/Game/p_weak.cpp
Normal file
@ -0,0 +1,188 @@
|
||||
#include "p_weak.h"
|
||||
|
||||
#if PSYDOOM_MODS
|
||||
|
||||
#include "Doom/doomdef.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
// Weak count internal data.
|
||||
// Stores the number of weak references to an object, whether it is alive and a pointer to the object itself.
|
||||
struct WeakCount {
|
||||
uint32_t count; // How many weak references there are to the object
|
||||
bool bMobjExists; // Whether the object is still valid/existing
|
||||
mobj_t* pMobj; // The object (weakly) pointed to
|
||||
};
|
||||
|
||||
static std::vector<WeakCount> gWeakCounts; // Weak references to various map objects, some of these slots may be unused
|
||||
static std::vector<uint32_t> gFreeWeakCountIdxs; // Which weak count slots are currently free (by index)
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------------------------------------
|
||||
// Tells if the specified weak count is still in use
|
||||
//------------------------------------------------------------------------------------------------------------------------------------------
|
||||
static bool isWeakCountInUse(const WeakCount& weakCount) noexcept {
|
||||
return (weakCount.bMobjExists || (weakCount.count > 0));
|
||||
}
|
||||
|
||||
#if ASSERTS_ENABLED
|
||||
//------------------------------------------------------------------------------------------------------------------------------------------
|
||||
// Debug helper: checks to see if any weak counts are still in use
|
||||
//------------------------------------------------------------------------------------------------------------------------------------------
|
||||
static bool areWeakCountsInUse() noexcept {
|
||||
for (const WeakCount& weakCount : gWeakCounts) {
|
||||
if (isWeakCountInUse(weakCount))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
#endif // #if ASSERTS_ENABLED
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------------------------------------
|
||||
// Allocates a weak count for the specified map object which is assumed to not already have one and returns it by index.
|
||||
// The count is not initialized.
|
||||
//------------------------------------------------------------------------------------------------------------------------------------------
|
||||
static uint32_t allocWeakCount(mobj_t& mobj) noexcept {
|
||||
// Object must not already have a count allocated
|
||||
ASSERT(mobj.weakCountIdx == MobjWeakPtr::NULL_WEAK_COUNT_IDX);
|
||||
|
||||
// Is there a free count we can use?
|
||||
if (!gFreeWeakCountIdxs.empty()) {
|
||||
const uint32_t idx = gFreeWeakCountIdxs.back();
|
||||
gFreeWeakCountIdxs.pop_back();
|
||||
return idx;
|
||||
}
|
||||
|
||||
// If there's no free count then alloc a new one
|
||||
const uint32_t idx = (uint32_t) gWeakCounts.size();
|
||||
gWeakCounts.emplace_back();
|
||||
return idx;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------------------------------------
|
||||
// Initializes the weak reference system: should be called on level setup, prior to creating map objects
|
||||
//------------------------------------------------------------------------------------------------------------------------------------------
|
||||
void P_InitWeakRefs() noexcept {
|
||||
ASSERT(gWeakCounts.empty());
|
||||
ASSERT(gFreeWeakCountIdxs.empty());
|
||||
gWeakCounts.reserve(2048);
|
||||
gWeakCounts.resize(1); // The first index is unused (the null weak count index)
|
||||
gFreeWeakCountIdxs.reserve(1024);
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------------------------------------
|
||||
// Shuts down the weak reference system: should be called on level teardown
|
||||
//------------------------------------------------------------------------------------------------------------------------------------------
|
||||
void P_ShutdownWeakRefs() noexcept {
|
||||
ASSERT_LOG(!areWeakCountsInUse(), "There should be no weak references to objects on shutting down this system!");
|
||||
gWeakCounts.clear();
|
||||
gFreeWeakCountIdxs.clear();
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------------------------------------
|
||||
// Adds a weak reference to the specified map object and returns the index of the weak count used to keep track of it.
|
||||
// If the object is a null pointer returns 'MobjWeakPtr::NULL_WEAK_COUNT_IDX'.
|
||||
//------------------------------------------------------------------------------------------------------------------------------------------
|
||||
uint32_t P_AddWeakRef(mobj_t* const pMobj) noexcept {
|
||||
// Adding a reference to a null object?
|
||||
if (!pMobj)
|
||||
return MobjWeakPtr::NULL_WEAK_COUNT_IDX;
|
||||
|
||||
// Does the count already exist? If so then just increment that:
|
||||
const uint32_t curCountIdx = pMobj->weakCountIdx;
|
||||
|
||||
if (curCountIdx != MobjWeakPtr::NULL_WEAK_COUNT_IDX) {
|
||||
ASSERT(curCountIdx < gWeakCounts.size());
|
||||
WeakCount& weakCount = gWeakCounts[curCountIdx];
|
||||
weakCount.count++;
|
||||
return curCountIdx;
|
||||
}
|
||||
|
||||
// If there's no count then make a new one and populate it with a single weak reference
|
||||
const uint32_t newCountIdx = allocWeakCount(*pMobj);
|
||||
|
||||
WeakCount& weakCount = gWeakCounts[newCountIdx];
|
||||
weakCount = {};
|
||||
weakCount.count = 1;
|
||||
weakCount.bMobjExists = true;
|
||||
weakCount.pMobj = pMobj;
|
||||
|
||||
// Assign the map object it's count and return which count is being used
|
||||
pMobj->weakCountIdx = newCountIdx;
|
||||
return newCountIdx;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------------------------------------
|
||||
// Adds an extra weak reference to the specified weak count (unless it's the null count index)
|
||||
//------------------------------------------------------------------------------------------------------------------------------------------
|
||||
void P_AddWeakRef(const uint32_t weakCountIdx) noexcept {
|
||||
if (weakCountIdx == MobjWeakPtr::NULL_WEAK_COUNT_IDX)
|
||||
return;
|
||||
|
||||
ASSERT(weakCountIdx < gWeakCounts.size());
|
||||
WeakCount& weakCount = gWeakCounts[weakCountIdx];
|
||||
weakCount.count++;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------------------------------------
|
||||
// Removes a weak reference to the specified weak count (unless it's the null count index)
|
||||
//------------------------------------------------------------------------------------------------------------------------------------------
|
||||
void P_RemoveWeakRef(const uint32_t weakCountIdx) noexcept {
|
||||
if (weakCountIdx == MobjWeakPtr::NULL_WEAK_COUNT_IDX)
|
||||
return;
|
||||
|
||||
ASSERT(weakCountIdx < gWeakCounts.size());
|
||||
WeakCount& weakCount = gWeakCounts[weakCountIdx];
|
||||
ASSERT(weakCount.count > 0);
|
||||
weakCount.count--;
|
||||
|
||||
// If the weak count is no longer in use then free it
|
||||
if (!isWeakCountInUse(weakCount)) {
|
||||
gFreeWeakCountIdxs.push_back(weakCountIdx);
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------------------------------------
|
||||
// Return the pointer to a weakly referenced map object
|
||||
//------------------------------------------------------------------------------------------------------------------------------------------
|
||||
mobj_t* P_WeakDeref(const uint32_t weakCountIdx) noexcept {
|
||||
if (weakCountIdx == MobjWeakPtr::NULL_WEAK_COUNT_IDX)
|
||||
return nullptr;
|
||||
|
||||
ASSERT(weakCountIdx < gWeakCounts.size());
|
||||
WeakCount& weakCount = gWeakCounts[weakCountIdx];
|
||||
|
||||
if (weakCount.bMobjExists) {
|
||||
return weakCount.pMobj;
|
||||
} else {
|
||||
#if PSYDOOM_FIX_UB
|
||||
return nullptr;
|
||||
#else
|
||||
ASSERT_FAIL("Undefined behavior! Referencing a map object that has been destroyed!");
|
||||
return weakCount.pMobj;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------------------------------------
|
||||
// Informs the weak reference system that the specified map object is being destroyed.
|
||||
// Causes all weak references to the object to be nulled.
|
||||
//------------------------------------------------------------------------------------------------------------------------------------------
|
||||
void P_WeakReferencedDestroyed(mobj_t& mobj) noexcept {
|
||||
// If there's no weak count we can just stop here
|
||||
const uint32_t weakCountIdx = mobj.weakCountIdx;
|
||||
|
||||
if (weakCountIdx == MobjWeakPtr::NULL_WEAK_COUNT_IDX)
|
||||
return;
|
||||
|
||||
// Otherwise mark the object destroyed and free up the weak count if it's no longer used
|
||||
ASSERT(weakCountIdx < gWeakCounts.size());
|
||||
WeakCount& weakCount = gWeakCounts[weakCountIdx];
|
||||
ASSERT_LOG(weakCount.bMobjExists, "The map object weakly referenced should only be destroyed once!");
|
||||
weakCount.bMobjExists = false;
|
||||
|
||||
if (weakCount.count <= 0) {
|
||||
gFreeWeakCountIdxs.push_back(weakCountIdx);
|
||||
}
|
||||
}
|
||||
#endif // #if PSYDOOM_MODS
|
99
game/Doom/Game/p_weak.h
Normal file
99
game/Doom/Game/p_weak.h
Normal file
@ -0,0 +1,99 @@
|
||||
#pragma once
|
||||
|
||||
#include "Asserts.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
struct mobj_t;
|
||||
|
||||
#if PSYDOOM_MODS
|
||||
|
||||
void P_InitWeakRefs() noexcept;
|
||||
void P_ShutdownWeakRefs() noexcept;
|
||||
|
||||
// These functions are for internal use by 'MobjWeakPtr' only
|
||||
uint32_t P_AddWeakRef(mobj_t* const pMobj) noexcept;
|
||||
void P_AddWeakRef(const uint32_t weakCountIdx) noexcept;
|
||||
void P_RemoveWeakRef(const uint32_t weakCountIdx) noexcept;
|
||||
mobj_t* P_WeakDeref(const uint32_t weakCountIdx) noexcept;
|
||||
void P_WeakReferencedDestroyed(mobj_t& mobj) noexcept;
|
||||
|
||||
//------------------------------------------------------------------------------------------------------------------------------------------
|
||||
// A 'std::weak_ptr' style weak reference to a map object.
|
||||
// Added as an addition to PsyDoom to prevent undefined behavior, since sometimes map objects can still reference others that are destroyed.
|
||||
//
|
||||
// Note: if fixing undefined behavior is not enabled then this just functions like a raw pointer and CAN return an invalid 'mobj_t'.
|
||||
// An assert will be triggered in debug mode however when this happens.
|
||||
//------------------------------------------------------------------------------------------------------------------------------------------
|
||||
class MobjWeakPtr {
|
||||
public:
|
||||
// The index used to represent a null weak pointer.
|
||||
// Use zero so that 'MobjWeakPtr' fields are automatically null if zero-initialized via memset.
|
||||
static constexpr uint32_t NULL_WEAK_COUNT_IDX = 0;
|
||||
|
||||
inline MobjWeakPtr() noexcept : weakCountIdx(NULL_WEAK_COUNT_IDX) {}
|
||||
inline MobjWeakPtr(nullptr_t) noexcept : weakCountIdx(NULL_WEAK_COUNT_IDX) {}
|
||||
inline MobjWeakPtr(mobj_t* const pMobj) noexcept : weakCountIdx(P_AddWeakRef(pMobj)) {}
|
||||
|
||||
inline MobjWeakPtr(const MobjWeakPtr& other) noexcept : weakCountIdx(other.weakCountIdx) {
|
||||
P_AddWeakRef(weakCountIdx);
|
||||
}
|
||||
|
||||
inline MobjWeakPtr(MobjWeakPtr&& other) noexcept : weakCountIdx(other.weakCountIdx) {
|
||||
other.weakCountIdx = NULL_WEAK_COUNT_IDX;
|
||||
}
|
||||
|
||||
inline ~MobjWeakPtr() noexcept {
|
||||
P_RemoveWeakRef(weakCountIdx);
|
||||
}
|
||||
|
||||
inline MobjWeakPtr& operator = (nullptr_t) noexcept {
|
||||
P_RemoveWeakRef(weakCountIdx);
|
||||
weakCountIdx = NULL_WEAK_COUNT_IDX;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline MobjWeakPtr& operator = (mobj_t* const pMobj) noexcept {
|
||||
const int32_t oldWeakCountIdx = weakCountIdx;
|
||||
weakCountIdx = P_AddWeakRef(pMobj);
|
||||
P_RemoveWeakRef(oldWeakCountIdx);
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline MobjWeakPtr& operator = (const MobjWeakPtr& other) noexcept {
|
||||
P_AddWeakRef(other.weakCountIdx);
|
||||
P_RemoveWeakRef(weakCountIdx);
|
||||
weakCountIdx = other.weakCountIdx;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline MobjWeakPtr& operator = (MobjWeakPtr&& other) noexcept {
|
||||
if (this != &other) {
|
||||
const int32_t oldWeakCountIdx = weakCountIdx;
|
||||
weakCountIdx = other.weakCountIdx;
|
||||
other.weakCountIdx = NULL_WEAK_COUNT_IDX;
|
||||
P_RemoveWeakRef(oldWeakCountIdx);
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline mobj_t& operator * () const noexcept {
|
||||
mobj_t* const pMobj = P_WeakDeref(weakCountIdx);
|
||||
ASSERT(pMobj);
|
||||
return *pMobj;
|
||||
}
|
||||
|
||||
inline mobj_t* operator -> () const noexcept { return P_WeakDeref(weakCountIdx); }
|
||||
inline mobj_t* get() const noexcept { return P_WeakDeref(weakCountIdx); }
|
||||
inline operator bool () const noexcept { return (P_WeakDeref(weakCountIdx) != nullptr); }
|
||||
inline operator mobj_t* () const noexcept { return P_WeakDeref(weakCountIdx); }
|
||||
|
||||
private:
|
||||
// Index of the weak count used to track the map object in question.
|
||||
// Will be '0' for a null/blank weak pointer.
|
||||
uint32_t weakCountIdx;
|
||||
};
|
||||
|
||||
#endif // #if PSYDOOM_MODS
|
@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "Doom/Game/p_weak.h"
|
||||
#include "PsyDoom/BitShift.h"
|
||||
|
||||
struct mobj_t;
|
||||
@ -235,7 +236,13 @@ struct mobj_t {
|
||||
int32_t health; // When this reaches '0' the object is dead
|
||||
dirtype_t movedir; // For enemy AI, what direction the enemy is moving in
|
||||
int32_t movecount; // When this reaches 0 a new dir is selected
|
||||
// PsyDoom: need to use weak refs!
|
||||
// These objects can sometimes get destroyed without references to them being cleared.
|
||||
#if PSYDOOM_MODS
|
||||
MobjWeakPtr target; // The current map object being chased or attacked (if any), or for missiles the source object
|
||||
#else
|
||||
mobj_t* target; // The current map object being chased or attacked (if any), or for missiles the source object
|
||||
#endif
|
||||
int32_t reactiontime; // Time left until an attack is allowed
|
||||
int32_t threshold; // Time left chasing the current target
|
||||
player_t* player; // Associated player, if any
|
||||
@ -244,7 +251,14 @@ struct mobj_t {
|
||||
int16_t spawny; // Used for respawns: original spawn position (integer) y
|
||||
uint16_t spawntype; // Used for respawns: item 'DoomEd' type/number
|
||||
int16_t spawnangle; // Used for respawns: item angle
|
||||
// PsyDoom: need to use weak refs!
|
||||
// These objects can sometimes get destroyed without references to them being cleared.
|
||||
#if PSYDOOM_MODS
|
||||
MobjWeakPtr tracer; // Used by homing missiles
|
||||
uint32_t weakCountIdx; // PsyDoom: index of the weak reference counter allocated for this map object ('0' if there are no weak references to it)
|
||||
#else
|
||||
mobj_t* tracer; // Used by homing missiles
|
||||
#endif
|
||||
};
|
||||
|
||||
// A degenerate map object with most of it's fields chopped out to save on memory.
|
||||
|
@ -132,6 +132,10 @@ static void allocMobjsToLoad() noexcept {
|
||||
mobj_t& mobj = *(mobj_t*) Z_Malloc(*gpMainMemZone, sizeof(mobj_t), PU_LEVEL, nullptr);
|
||||
std::memset(&mobj, 0, sizeof(mobj_t));
|
||||
|
||||
#if PSYDOOM_MODS
|
||||
new (&mobj) mobj_t(); // PsyDoom: construct C++ weak pointers
|
||||
#endif
|
||||
|
||||
// Keep track of it for later loading logic
|
||||
gMobjToIdx[&mobj] = i;
|
||||
gMobjList.push_back(&mobj);
|
||||
|
@ -244,11 +244,11 @@ static int32_t getMobjIndex(mobj_t* const pMobj) noexcept {
|
||||
if (pMobj) {
|
||||
const auto iter = SaveAndLoad::gMobjToIdx.find(pMobj);
|
||||
|
||||
// Make sure the map object pointer is valid. There are bugs in the DOOM code where sometimes the 'target' and 'tracer'
|
||||
// fields end up pointing to deleted objects. I've fixed this issue but keep the sanity checks here just in case.
|
||||
// If the pointer is to an invalid object then just pretend it was a null pointer for the purposes of serialization...
|
||||
if (iter != SaveAndLoad::gMobjToIdx.end()) {
|
||||
return iter->second;
|
||||
} else {
|
||||
// This should never happen, if it does then it might indicate some bad problems elsewhere...
|
||||
I_Error("SaveData: getMobjIndex: can't serialize invalid map object with no index!");
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user