Merge branch 'master' of https://github.com/ZDoom/gzdoom
This commit is contained in:
commit
74a27be3fd
114 changed files with 3366 additions and 1677 deletions
|
|
@ -100,6 +100,7 @@
|
|||
#include "a_dynlight.h"
|
||||
#include "fragglescript/t_fs.h"
|
||||
#include "shadowinlines.h"
|
||||
#include "d_net.h"
|
||||
|
||||
// MACROS ------------------------------------------------------------------
|
||||
|
||||
|
|
@ -178,6 +179,23 @@ IMPLEMENT_POINTERS_START(AActor)
|
|||
IMPLEMENT_POINTER(boneComponentData)
|
||||
IMPLEMENT_POINTERS_END
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// Make sure Actors can never have their networking disabled.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void AActor::EnableNetworking(const bool enable)
|
||||
{
|
||||
if (!enable)
|
||||
{
|
||||
ThrowAbortException(X_OTHER, "Cannot disable networking on Actors. Consider a Thinker instead.");
|
||||
return;
|
||||
}
|
||||
|
||||
Super::EnableNetworking(true);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// AActor :: Serialize
|
||||
|
|
@ -817,7 +835,7 @@ int P_GetRealMaxHealth(AActor *actor, int max)
|
|||
{
|
||||
max = actor->GetMaxHealth(true);
|
||||
// [MH] First step in predictable generic morph effects
|
||||
if (player->morphTics)
|
||||
if (actor->alternative != nullptr)
|
||||
{
|
||||
if (player->MorphStyle & MORPH_FULLHEALTH)
|
||||
{
|
||||
|
|
@ -839,7 +857,7 @@ int P_GetRealMaxHealth(AActor *actor, int max)
|
|||
else
|
||||
{
|
||||
// Bonus health should be added on top of the item's limit.
|
||||
if (player->morphTics == 0 || (player->MorphStyle & MORPH_ADDSTAMINA))
|
||||
if (actor->alternative == nullptr || (player->MorphStyle & MORPH_ADDSTAMINA))
|
||||
{
|
||||
max += actor->IntVar(NAME_BonusHealth);
|
||||
}
|
||||
|
|
@ -970,6 +988,44 @@ DEFINE_ACTION_FUNCTION(AActor, CheckLocalView)
|
|||
ACTION_RETURN_BOOL(self->CheckLocalView());
|
||||
}
|
||||
|
||||
void AActor::DisableLocalRendering(const unsigned int pNum, const bool disable)
|
||||
{
|
||||
if (pNum == consoleplayer)
|
||||
NoLocalRender = disable;
|
||||
}
|
||||
|
||||
static void DisableLocalRendering(AActor* const self, const unsigned int pNum, const int disable)
|
||||
{
|
||||
self->DisableLocalRendering(pNum, disable);
|
||||
}
|
||||
|
||||
DEFINE_ACTION_FUNCTION_NATIVE(AActor, DisableLocalRendering, DisableLocalRendering)
|
||||
{
|
||||
PARAM_SELF_PROLOGUE(AActor);
|
||||
PARAM_UINT(pNum);
|
||||
PARAM_INT(disable);
|
||||
|
||||
DisableLocalRendering(self, pNum, disable);
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool AActor::ShouldRenderLocally() const
|
||||
{
|
||||
return !NoLocalRender;
|
||||
}
|
||||
|
||||
static int ShouldRenderLocally(const AActor* const self)
|
||||
{
|
||||
return self->ShouldRenderLocally();
|
||||
}
|
||||
|
||||
DEFINE_ACTION_FUNCTION_NATIVE(AActor, ShouldRenderLocally, ShouldRenderLocally)
|
||||
{
|
||||
PARAM_SELF_PROLOGUE(AActor);
|
||||
|
||||
ACTION_RETURN_INT(ShouldRenderLocally(self));
|
||||
}
|
||||
|
||||
//============================================================================
|
||||
//
|
||||
// AActor :: IsInsideVisibleAngles
|
||||
|
|
@ -1049,6 +1105,9 @@ bool AActor::IsVisibleToPlayer() const
|
|||
// [BB] Safety check. This should never be NULL. Nevertheless, we return true to leave the default ZDoom behavior unaltered.
|
||||
if (p == nullptr || p->camera == nullptr )
|
||||
return true;
|
||||
|
||||
if (!ShouldRenderLocally())
|
||||
return false;
|
||||
|
||||
if (VisibleToTeam != 0 && teamplay &&
|
||||
(signed)(VisibleToTeam-1) != p->userinfo.GetTeam() )
|
||||
|
|
@ -3454,22 +3513,16 @@ void AActor::SetPitch(DAngle p, int fflags)
|
|||
{
|
||||
if (player != nullptr)
|
||||
{
|
||||
const bool mustLerp = !P_NoInterpolation(player, this);
|
||||
|
||||
if ((fflags & SPF_INTERPOLATE) || ((fflags & SPF_SCALEDNOLERP) && mustLerp))
|
||||
if (fflags & SPF_SCALEDNOLERP)
|
||||
{
|
||||
Angles.Pitch = p;
|
||||
player->cheats |= CF_INTERPVIEW;
|
||||
}
|
||||
else if ((fflags & SPF_SCALEDNOLERP) && !mustLerp)
|
||||
{
|
||||
player->angleTargets.Pitch = deltaangle(Angles.Pitch, p);
|
||||
player->angleAppliedAmounts.Pitch = nullAngle;
|
||||
player->angleOffsetTargets.Pitch = deltaangle(Angles.Pitch, p);
|
||||
player->cheats |= CF_SCALEDNOLERP;
|
||||
}
|
||||
else
|
||||
{
|
||||
Angles.Pitch = p;
|
||||
if (fflags & SPF_INTERPOLATE)
|
||||
player->cheats |= CF_INTERPVIEW;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
@ -3486,22 +3539,16 @@ void AActor::SetAngle(DAngle ang, int fflags)
|
|||
{
|
||||
if (player != nullptr)
|
||||
{
|
||||
const bool mustLerp = !P_NoInterpolation(player, this);
|
||||
|
||||
if ((fflags & SPF_INTERPOLATE) || ((fflags & SPF_SCALEDNOLERP) && mustLerp))
|
||||
if (fflags & SPF_SCALEDNOLERP)
|
||||
{
|
||||
Angles.Yaw = ang;
|
||||
player->cheats |= CF_INTERPVIEW;
|
||||
}
|
||||
else if ((fflags & SPF_SCALEDNOLERP) && !mustLerp)
|
||||
{
|
||||
player->angleTargets.Yaw = deltaangle(Angles.Yaw, ang);
|
||||
player->angleAppliedAmounts.Yaw = nullAngle;
|
||||
player->angleOffsetTargets.Yaw = deltaangle(Angles.Yaw, ang);
|
||||
player->cheats |= CF_SCALEDNOLERP;
|
||||
}
|
||||
else
|
||||
{
|
||||
Angles.Yaw = ang;
|
||||
if (fflags & SPF_INTERPOLATE)
|
||||
player->cheats |= CF_INTERPVIEW;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
@ -3518,22 +3565,16 @@ void AActor::SetRoll(DAngle r, int fflags)
|
|||
{
|
||||
if (player != nullptr)
|
||||
{
|
||||
const bool mustLerp = !P_NoInterpolation(player, this);
|
||||
|
||||
if ((fflags & SPF_INTERPOLATE) || ((fflags & SPF_SCALEDNOLERP) && mustLerp))
|
||||
if (fflags & SPF_SCALEDNOLERP)
|
||||
{
|
||||
Angles.Roll = r;
|
||||
player->cheats |= CF_INTERPVIEW;
|
||||
}
|
||||
else if ((fflags & SPF_SCALEDNOLERP) && !mustLerp)
|
||||
{
|
||||
player->angleTargets.Roll = deltaangle(Angles.Roll, r);
|
||||
player->angleAppliedAmounts.Roll = nullAngle;
|
||||
player->angleOffsetTargets.Roll = deltaangle(Angles.Roll, r);
|
||||
player->cheats |= CF_SCALEDNOLERP;
|
||||
}
|
||||
else
|
||||
{
|
||||
Angles.Roll = r;
|
||||
if (fflags & SPF_INTERPOLATE)
|
||||
player->cheats |= CF_INTERPVIEW;
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
@ -3726,6 +3767,22 @@ void AActor::Tick ()
|
|||
static const uint8_t HereticScrollDirs[4] = { 6, 9, 1, 4 };
|
||||
static const uint8_t HereticSpeedMuls[5] = { 5, 10, 25, 30, 35 };
|
||||
|
||||
// Check for Actor unmorphing, but only on the thing that is the morphed Actor.
|
||||
// Players do their own special checking for this.
|
||||
if (alternative != nullptr && !(flags & MF_UNMORPHED) && player == nullptr)
|
||||
{
|
||||
int res = false;
|
||||
IFVIRTUAL(AActor, CheckUnmorph)
|
||||
{
|
||||
VMValue params[] = { this };
|
||||
VMReturn ret[] = { &res };
|
||||
VMCall(func, params, 1, ret, 1);
|
||||
}
|
||||
|
||||
if (res)
|
||||
return;
|
||||
}
|
||||
|
||||
if (freezetics > 0)
|
||||
{
|
||||
freezetics--;
|
||||
|
|
@ -4752,6 +4809,7 @@ AActor *AActor::StaticSpawn(FLevelLocals *Level, PClassActor *type, const DVecto
|
|||
AActor *actor;
|
||||
|
||||
actor = static_cast<AActor *>(Level->CreateThinker(type));
|
||||
actor->EnableNetworking(true);
|
||||
|
||||
ConstructActor(actor, pos, SpawningMapThing);
|
||||
return actor;
|
||||
|
|
@ -5020,6 +5078,16 @@ void AActor::CallDeactivate(AActor *activator)
|
|||
|
||||
void AActor::OnDestroy ()
|
||||
{
|
||||
// If the Actor is leaving behind a premorph Actor, make sure it gets cleaned up as
|
||||
// well so it's not stuck in the map.
|
||||
if (alternative != nullptr && !(flags & MF_UNMORPHED))
|
||||
{
|
||||
alternative->ClearCounters();
|
||||
alternative->alternative = nullptr;
|
||||
alternative->Destroy();
|
||||
alternative = nullptr;
|
||||
}
|
||||
|
||||
// [ZZ] call destroy event hook.
|
||||
// note that this differs from ThingSpawned in that you can actually override OnDestroy to avoid calling the hook.
|
||||
// but you can't really do that without utterly breaking the game, so it's ok.
|
||||
|
|
@ -5141,59 +5209,182 @@ extern bool demonew;
|
|||
|
||||
//==========================================================================
|
||||
//
|
||||
// This once was the main method for pointer cleanup, but
|
||||
// nowadays its only use is swapping out PlayerPawns.
|
||||
// This requires pointer fixing throughout all objects and a few
|
||||
// global variables, but it only needs to look at pointers that
|
||||
// can point to a player.
|
||||
// This function is dangerous and only designed for swapping player pawns
|
||||
// over to their new ones upon changing levels or respawning. It SHOULD NOT be
|
||||
// used for anything else! Do not export this functionality as it's
|
||||
// meant strictly for internal usage. Only swap pointers if the thing being swapped
|
||||
// to is a type of the thing being swapped from.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
void StaticPointerSubstitution(AActor* old, AActor* notOld)
|
||||
void PlayerPointerSubstitution(AActor* oldPlayer, AActor* newPlayer)
|
||||
{
|
||||
DObject* probe;
|
||||
size_t changed = 0;
|
||||
int i;
|
||||
|
||||
if (old == nullptr) return;
|
||||
|
||||
// This is only allowed to replace players or swap out morphed monsters
|
||||
if (!old->IsKindOf(NAME_PlayerPawn) || (notOld != nullptr && !notOld->IsKindOf(NAME_PlayerPawn)))
|
||||
if (oldPlayer == nullptr || newPlayer == nullptr || oldPlayer == newPlayer
|
||||
|| !oldPlayer->IsKindOf(NAME_PlayerPawn) || !newPlayer->IsKindOf(NAME_PlayerPawn))
|
||||
{
|
||||
if (notOld == nullptr) return;
|
||||
if (!old->IsKindOf(NAME_MorphedMonster) && !notOld->IsKindOf(NAME_MorphedMonster)) return;
|
||||
}
|
||||
// Go through all objects.
|
||||
i = 0; DObject* last = 0;
|
||||
for (probe = GC::Root; probe != NULL; probe = probe->ObjNext)
|
||||
{
|
||||
i++;
|
||||
changed += probe->PointerSubstitution(old, notOld);
|
||||
last = probe;
|
||||
return;
|
||||
}
|
||||
|
||||
// Go through players.
|
||||
for (i = 0; i < MAXPLAYERS; i++)
|
||||
// Go through player infos.
|
||||
for (int i = 0; i < MAXPLAYERS; ++i)
|
||||
{
|
||||
if (playeringame[i])
|
||||
{
|
||||
AActor* replacement = notOld;
|
||||
auto& p = players[i];
|
||||
if (!oldPlayer->Level->PlayerInGame(i))
|
||||
continue;
|
||||
|
||||
if (p.mo == old) p.mo = replacement, changed++;
|
||||
if (p.poisoner.ForceGet() == old) p.poisoner = replacement, changed++;
|
||||
if (p.attacker.ForceGet() == old) p.attacker = replacement, changed++;
|
||||
if (p.camera.ForceGet() == old) p.camera = replacement, changed++;
|
||||
if (p.ConversationNPC.ForceGet() == old) p.ConversationNPC = replacement, changed++;
|
||||
if (p.ConversationPC.ForceGet() == old) p.ConversationPC = replacement, changed++;
|
||||
}
|
||||
auto p = oldPlayer->Level->Players[i];
|
||||
|
||||
if (p->mo == oldPlayer)
|
||||
p->mo = newPlayer;
|
||||
if (p->poisoner == oldPlayer)
|
||||
p->poisoner = newPlayer;
|
||||
if (p->attacker == oldPlayer)
|
||||
p->attacker = newPlayer;
|
||||
if (p->camera == oldPlayer)
|
||||
p->camera = newPlayer;
|
||||
if (p->ConversationNPC == oldPlayer)
|
||||
p->ConversationNPC = newPlayer;
|
||||
if (p->ConversationPC == oldPlayer)
|
||||
p->ConversationPC = newPlayer;
|
||||
}
|
||||
|
||||
// Go through sectors. Only the level this actor belongs to is relevant.
|
||||
for (auto& sec : old->Level->sectors)
|
||||
// Go through sectors.
|
||||
for (auto& sec : oldPlayer->Level->sectors)
|
||||
{
|
||||
if (sec.SoundTarget == old) sec.SoundTarget = notOld;
|
||||
if (sec.SoundTarget == oldPlayer)
|
||||
sec.SoundTarget = newPlayer;
|
||||
}
|
||||
|
||||
// Update all the remaining object pointers. This is dangerous but needed to ensure
|
||||
// everything functions correctly when respawning or changing levels.
|
||||
for (DObject* probe = GC::Root; probe != nullptr; probe = probe->ObjNext)
|
||||
probe->PointerSubstitution(oldPlayer, newPlayer);
|
||||
}
|
||||
|
||||
//==========================================================================
|
||||
//
|
||||
// This function is much safer than PlayerPointerSubstition as it only truly
|
||||
// swaps a few safe pointers. This has some extra barriers to it to allow
|
||||
// Actors to freely morph into other Actors which is its main usage.
|
||||
// Previously this used raw pointer substitutions but that's far too
|
||||
// volatile to use with modder-provided information. It also allows morphing
|
||||
// to be more extendable from ZScript.
|
||||
//
|
||||
//==========================================================================
|
||||
|
||||
int MorphPointerSubstitution(AActor* from, AActor* to)
|
||||
{
|
||||
// Special care is taken here to make sure things marked as a dummy Actor for a morphed thing aren't
|
||||
// allowed to be changed into other things. Anything being morphed into that's considered a player
|
||||
// is automatically out of the question to ensure modders aren't swapping clients around.
|
||||
if (from == nullptr || to == nullptr || from == to || to->player != nullptr
|
||||
|| (from->flags & MF_UNMORPHED) // Another thing's dummy Actor, unmorphing the wrong way, etc.
|
||||
|| (from->alternative == nullptr && to->alternative != nullptr) // Morphing into something that's already morphed.
|
||||
|| (from->alternative != nullptr && from->alternative != to)) // Only allow something morphed to unmorph.
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool toIsPlayer = to->IsKindOf(NAME_PlayerPawn);
|
||||
if (from->IsKindOf(NAME_PlayerPawn))
|
||||
{
|
||||
// Players are only allowed to turn into other valid player pawns. For
|
||||
// valid pawns, make sure an actual player is changing into an empty one.
|
||||
// Voodoo dolls aren't allowed to morph since that should be passed to
|
||||
// the main player directly.
|
||||
if (!toIsPlayer || from->player == nullptr || from->player->mo != from)
|
||||
return false;
|
||||
}
|
||||
else if (toIsPlayer || from->player != nullptr
|
||||
|| (from->IsKindOf(NAME_Inventory) && from->PointerVar<AActor>(NAME_Owner) != nullptr)
|
||||
|| (to->IsKindOf(NAME_Inventory) && to->PointerVar<AActor>(NAME_Owner) != nullptr))
|
||||
{
|
||||
// Only allow items to be swapped around if they aren't currently owned. Also prevent non-players from
|
||||
// turning into fake players.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Since the check is good, move the inventory items over. This should always be done when
|
||||
// morphing to emulate Heretic/Hexen's behavior since those stored the inventory in their
|
||||
// player structs.
|
||||
IFVM(Actor, ObtainInventory)
|
||||
{
|
||||
VMValue params[] = { to, from };
|
||||
VMCall(func, params, 2, nullptr, 0);
|
||||
}
|
||||
|
||||
// Only change some gameplay-related pointers that we know we can safely swap to whatever
|
||||
// new Actor class is present.
|
||||
AActor* mo = nullptr;
|
||||
auto it = from->Level->GetThinkerIterator<AActor>();
|
||||
while ((mo = it.Next()) != nullptr)
|
||||
{
|
||||
if (mo->target == from)
|
||||
mo->target = to;
|
||||
if (mo->tracer == from)
|
||||
mo->tracer = to;
|
||||
if (mo->master == from)
|
||||
mo->master = to;
|
||||
if (mo->goal == from)
|
||||
mo->goal = to;
|
||||
if (mo->lastenemy == from)
|
||||
mo->lastenemy = to;
|
||||
if (mo->LastHeard == from)
|
||||
mo->LastHeard = to;
|
||||
if (mo->LastLookActor == from)
|
||||
mo->LastLookActor = to;
|
||||
if (mo->Poisoner == from)
|
||||
mo->Poisoner = to;
|
||||
}
|
||||
|
||||
// Go through player infos.
|
||||
for (int i = 0; i < MAXPLAYERS; ++i)
|
||||
{
|
||||
if (!from->Level->PlayerInGame(i))
|
||||
continue;
|
||||
|
||||
auto p = from->Level->Players[i];
|
||||
|
||||
if (p->mo == from)
|
||||
p->mo = to;
|
||||
if (p->poisoner == from)
|
||||
p->poisoner = to;
|
||||
if (p->attacker == from)
|
||||
p->attacker = to;
|
||||
if (p->camera == from)
|
||||
p->camera = to;
|
||||
if (p->ConversationNPC == from)
|
||||
p->ConversationNPC = to;
|
||||
if (p->ConversationPC == from)
|
||||
p->ConversationPC = to;
|
||||
}
|
||||
|
||||
// Go through sectors.
|
||||
for (auto& sec : from->Level->sectors)
|
||||
{
|
||||
if (sec.SoundTarget == from)
|
||||
sec.SoundTarget = to;
|
||||
}
|
||||
|
||||
// Remaining maintenance related to morphing.
|
||||
if (from->player != nullptr)
|
||||
{
|
||||
to->player = from->player;
|
||||
from->player = nullptr;
|
||||
}
|
||||
|
||||
if (from->alternative != nullptr)
|
||||
{
|
||||
to->flags &= ~MF_UNMORPHED;
|
||||
to->alternative = from->alternative = nullptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
from->flags |= MF_UNMORPHED;
|
||||
from->alternative = to;
|
||||
to->alternative = from;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void FLevelLocals::PlayerSpawnPickClass (int playernum)
|
||||
|
|
@ -5466,7 +5657,7 @@ AActor *FLevelLocals::SpawnPlayer (FPlayerStart *mthing, int playernum, int flag
|
|||
if (sec.SoundTarget == oldactor) sec.SoundTarget = nullptr;
|
||||
}
|
||||
|
||||
StaticPointerSubstitution (oldactor, p->mo);
|
||||
PlayerPointerSubstitution (oldactor, p->mo);
|
||||
|
||||
localEventManager->PlayerRespawned(PlayerNum(p));
|
||||
Behaviors.StartTypedScripts (SCRIPT_Respawn, p->mo, true);
|
||||
|
|
@ -5701,15 +5892,26 @@ AActor *FLevelLocals::SpawnMapThing (FMapThing *mthing, int position)
|
|||
|
||||
const AActor *info = GetDefaultByType (i);
|
||||
|
||||
// don't spawn keycards and players in deathmatch
|
||||
if (deathmatch && info->flags & MF_NOTDMATCH)
|
||||
return NULL;
|
||||
// Don't spawn keycards and players in deathmatch.
|
||||
if (deathmatch && (info->flags & MF_NOTDMATCH))
|
||||
return nullptr;
|
||||
|
||||
// don't spawn extra things in coop if so desired
|
||||
if (multiplayer && !deathmatch && (dmflags2 & DF2_NO_COOP_THING_SPAWN))
|
||||
// Don't spawn extra things in co-op if desired.
|
||||
if (multiplayer && !deathmatch)
|
||||
{
|
||||
if ((mthing->flags & (MTF_DEATHMATCH|MTF_SINGLE)) == MTF_DEATHMATCH)
|
||||
return NULL;
|
||||
// Don't spawn DM-only things in co-op.
|
||||
if ((dmflags2 & DF2_NO_COOP_THING_SPAWN) && (mthing->flags & (MTF_DEATHMATCH|MTF_SINGLE)) == MTF_DEATHMATCH)
|
||||
return nullptr;
|
||||
// Having co-op only functionality is a bit odd, but you never know.
|
||||
if (!mthing->special && !mthing->thingid && (mthing->flags & (MTF_COOPERATIVE | MTF_SINGLE)) == MTF_COOPERATIVE)
|
||||
{
|
||||
// Don't spawn co-op only things in general.
|
||||
if (dmflags3 & DF3_NO_COOP_ONLY_THINGS)
|
||||
return nullptr;
|
||||
// Don't spawn co-op only items.
|
||||
if ((dmflags3 & DF3_NO_COOP_ONLY_ITEMS) && i->IsDescendantOf(NAME_Inventory))
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// [RH] don't spawn extra weapons in coop if so desired
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue