og-odamex/common/p_mobj.cpp

3452 lines
81 KiB
C++

// Emacs style mode select -*- C++ -*-
//-----------------------------------------------------------------------------
//
// $Id$
//
// Copyright (C) 1993-1996 by id Software, Inc.
// Copyright (C) 2006-2026 by The Odamex Team.
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// DESCRIPTION:
// Moving object handling. Spawn functions.
//
//-----------------------------------------------------------------------------
#include "odamex.h"
#include "m_alloc.h"
#include "i_system.h"
#include "z_zone.h"
#include "m_random.h"
#include "p_local.h"
#include "p_lnspec.h"
#include "c_effect.h"
#include "s_sound.h"
#include "v_video.h"
#include "m_vectors.h"
#include "g_game.h"
#include "p_mobj.h"
#include "p_ctf.h"
#include "gi.h"
#include "g_gametype.h"
#include "c_dispatch.h"
#include "p_horde.h"
#include "p_hordespawn.h"
#include "g_mapinfo.h"
#include "g_skill.h"
#include "m_wdlstats.h"
#include "p_mapformat.h"
#include "g_musinfo.h"
#include "r_sky.h"
#ifdef CLIENT_APP
#include "hu_speedometer.h"
#endif
#include <p_boomfspec.h>
void SV_UpdateMobj(const AActor* mo);
void SV_UpdateMobjState(const AActor* mo);
#define WATER_SINK_FACTOR 3
#define WATER_SINK_SMALL_FACTOR 4
#define WATER_SINK_SPEED (FRACUNIT/2)
#define WATER_JUMP_SPEED (FRACUNIT*7/2)
extern bool predicting;
extern fixed_t attackrange;
extern AActor *shootthing;
void P_SpawnPlayer(player_t &player, const mapthing2_t& mthing);
void P_ShowSpawns(const mapthing2_t& mthing);
void P_ExplodeMissile(AActor* mo);
void SV_SpawnMobj(AActor *mobj);
void SV_SendDestroyActor(const AActor *);
void SV_ExplodeMissile(const AActor *);
void SV_UpdateMonsterRespawnCount();
EXTERN_CVAR(sv_freelook)
EXTERN_CVAR(sv_itemsrespawn)
EXTERN_CVAR(sv_respawnsuper)
EXTERN_CVAR(sv_respawnbarrels)
EXTERN_CVAR(sv_itemrespawntime)
EXTERN_CVAR(co_zdoomphys)
EXTERN_CVAR(co_mbfphys)
EXTERN_CVAR(co_realactorheight)
EXTERN_CVAR(sv_teamspawns)
EXTERN_CVAR(sv_nomonsters)
EXTERN_CVAR(sv_monstersrespawn)
EXTERN_CVAR(sv_monstershealth)
EXTERN_CVAR(co_fixweaponimpacts)
EXTERN_CVAR(co_fineautoaim)
EXTERN_CVAR(sv_allowshowspawns)
EXTERN_CVAR(sv_teamsinplay)
EXTERN_CVAR(g_thingfilter)
NetIDHandler ServerNetID;
// denis - fast netid lookup
typedef std::map<uint32_t, AActor::AActorPtr> netid_map_t;
netid_map_t actor_by_netid;
IMPLEMENT_SERIAL(AActor, DThinker)
AActor::~AActor ()
{
// Please avoid calling the destructor directly (or through delete)!
// Use Destroy() instead.
// Zero all pointers generated by this->ptr()
self.update_all(NULL);
}
void MapThing::Serialize (FArchive &arc)
{
if (arc.IsStoring ())
{
arc << thingid << x << y << z << angle << type << flags << special
<< args[0] << args[1] << args[2] << args[3] << args[4];
}
else
{
arc >> thingid >> x >> y >> z >> angle >> type >> flags >> special
>> args[0] >> args[1] >> args[2] >> args[3] >> args[4];
}
}
AActor::AActor()
: x(0), y(0), z(0), prevx(0), prevy(0), prevz(0), snext(NULL), sprev(NULL), angle(0),
prevangle(0), sprite(SPR_UNKN), frame(0), pitch(0), prevpitch(0), effects(0),
subsector(NULL), floorz(0), ceilingz(0), dropoffz(0), floorsector(NULL), radius(0),
height(0), momx(0), momy(0), momz(0), validcount(0), type(MT_UNKNOWNTHING),
info(NULL), tics(0), state(NULL), damage(0), flags(0), flags2(0),
flags3(0), oflags(0), statusflags(0), special1(0), special2(0), health(0), movedir(0), movecount(0), visdir(0),
reactiontime(0), threshold(0), player(NULL), lastlook(0), special(0), inext(NULL),
iprev(NULL), translation(translationref_t()), translucency(0), waterlevel(0),
gear(0), onground(false), touching_sectorlist(NULL), deadtic(0), oldframe(0),
rndindex(0), friend_playerid(0), friend_teamid(TEAM_NONE), pursuecount(0), strafecount(0),
netid(0), tid(0), baseline(), baseline_set(false), bmapnode(this)
{
memset(args, 0, sizeof(args));
self.init(this);
}
AActor::AActor(const AActor& other)
: x(other.x), y(other.y), z(other.z), prevx(other.prevx), prevy(other.prevy),
prevz(other.prevz), snext(other.snext), sprev(other.sprev), angle(other.angle),
prevangle(other.prevangle), sprite(other.sprite), frame(other.frame),
pitch(other.pitch), prevpitch(other.prevpitch), effects(other.effects),
subsector(other.subsector), floorz(other.floorz), ceilingz(other.ceilingz),
dropoffz(other.dropoffz), floorsector(other.floorsector), radius(other.radius),
height(other.height), momx(other.momx), momy(other.momy), momz(other.momz),
validcount(other.validcount), type(other.type), info(other.info), tics(other.tics),
state(other.state), damage(other.damage),
flags(other.flags), flags2(other.flags2), flags3(other.flags3), oflags(other.oflags),
special1(other.special1), special2(other.special2),
health(other.health), movedir(other.movedir), movecount(other.movecount),
visdir(other.visdir), reactiontime(other.reactiontime), threshold(other.threshold),
player(other.player), lastlook(other.lastlook), special(other.special),
inext(other.inext), iprev(other.iprev), translation(other.translation),
translucency(other.translucency), waterlevel(other.waterlevel), gear(other.gear),
onground(other.onground), touching_sectorlist(other.touching_sectorlist),
deadtic(other.deadtic), oldframe(other.oldframe), rndindex(other.rndindex),
friend_playerid(other.friend_playerid),
friend_teamid(other.friend_teamid), pursuecount(other.pursuecount),
strafecount(other.strafecount),
netid(other.netid), tid(other.tid),
baseline_set(false), bmapnode(other.bmapnode)
{
memcpy(args, other.args, sizeof(args));
memcpy(&baseline, &other.baseline, sizeof(baseline));
self.init(this);
}
AActor &AActor::operator= (const AActor &other)
{
x = other.x;
y = other.y;
z = other.z;
prevx = other.prevx;
prevy = other.prevy;
prevz = other.prevz;
snext = other.snext;
sprev = other.sprev;
angle = other.angle;
prevangle = other.prevangle;
sprite = other.sprite;
frame = other.frame;
pitch = other.pitch;
prevpitch = other.prevpitch;
effects = other.effects;
subsector = other.subsector;
floorz = other.floorz;
ceilingz = other.ceilingz;
dropoffz = other.dropoffz;
floorsector = other.floorsector;
radius = other.radius;
height = other.height;
momx = other.momx;
momy = other.momy;
momz = other.momz;
validcount = other.validcount;
type = other.type;
info = other.info;
tics = other.tics;
state = other.state;
damage = other.damage;
flags = other.flags;
flags2 = other.flags2;
flags3 = other.flags3;
oflags = other.oflags;
statusflags = other.statusflags;
special1 = other.special1;
special2 = other.special2;
health = other.health;
movedir = other.movedir;
movecount = other.movecount;
visdir = other.visdir;
reactiontime = other.reactiontime;
threshold = other.threshold;
player = other.player;
lastlook = other.lastlook;
inext = other.inext;
iprev = other.iprev;
translation = other.translation;
translucency = other.translucency;
waterlevel = other.waterlevel;
gear = other.gear;
onground = other.onground;
touching_sectorlist = other.touching_sectorlist;
deadtic = other.deadtic;
oldframe = other.oldframe;
rndindex = other.rndindex;
friend_playerid = other.friend_playerid;
friend_teamid = other.friend_teamid;
pursuecount = other.pursuecount;
strafecount = other.strafecount;
netid = other.netid;
tid = other.tid;
special = other.special;
memcpy(args, other.args, sizeof(args));
bmapnode = other.bmapnode;
memcpy(&baseline, &other.baseline, sizeof(baseline));
baseline_set = other.baseline_set;
return *this;
}
//
//
// P_SpawnMobj
//
//
AActor::AActor(fixed_t ix, fixed_t iy, fixed_t iz, int32_t itype)
: x(ix), y(iy), z(0), prevx(0), prevy(0), prevz(0), snext(NULL), sprev(NULL), angle(0),
prevangle(0), sprite(SPR_UNKN), frame(0), pitch(0), prevpitch(0), effects(0),
subsector(NULL), floorz(0), ceilingz(0), dropoffz(0), floorsector(NULL), radius(0),
height(0), momx(0), momy(0), momz(0), validcount(0), type(itype),
info(NULL), tics(0), state(NULL), damage(0), flags(0), flags2(0), flags3(0), oflags(0),
statusflags(0), special1(0), special2(0), health(0), movedir(0), movecount(0), visdir(0),
reactiontime(0), threshold(0), player(NULL), lastlook(0), special(0), inext(NULL),
iprev(NULL), translation(translationref_t()), translucency(0), waterlevel(0),
gear(0), onground(false), touching_sectorlist(NULL), deadtic(0), oldframe(0),
rndindex(0), friend_playerid(0), friend_teamid(TEAM_NONE), pursuecount(0), strafecount(0),
netid(0), tid(0), baseline(), baseline_set(false), bmapnode(this)
{
// Fly!!! fix it in P_RespawnSpecial
const auto it = ::mobjinfo.find(itype);
if (it == mobjinfo.end())
{
I_Error("Tried to spawn actor type {}\n", itype);
}
self.init(this);
info = &it->second;
radius = info->radius;
height = P_ThingInfoHeight(info);
damage = info->damage;
flags = info->flags;
flags2 = info->flags2;
flags3 = info->flags3;
health = info->spawnhealth;
translucency = info->translucency;
rndindex = M_Random();
if (multiplayer && serverside)
netid = ::ServerNetID.obtainNetID();
if (!G_GetCurrentSkill().instant_reaction)
reactiontime = info->reactiontime;
if (clientside)
lastlook = P_Random() % MAXPLAYERS_VANILLA;
else
lastlook = P_Random() % MAXPLAYERS;
// do not set the state with P_SetMobjState,
// because action routines can not be called yet
state_t& st = states[info->spawnstate];
state = &st;
tics = st.tics;
sprite = st.sprite;
frame = st.frame;
touching_sectorlist = NULL; // NULL head of sector list // phares 3/13/98
// set subsector and/or block links
LinkToWorld ();
if(!subsector)
return;
floorz = P_FloorHeight(this);
ceilingz = P_CeilingHeight(this);
dropoffz = floorz;
floorsector = subsector->sector;
if (iz == ONFLOORZ)
{
z = floorz;
}
else if (iz == ONCEILINGZ)
{
z = ceilingz - height;
}
else if (flags2 & MF2_FLOATBOB)
{
z = floorz + iz; // artifact z passed in as height
}
else
{
z = iz;
}
spawnpoint.type = 0;
memset(args, 0, sizeof(args));
}
bool P_IsVoodooDoll(const AActor* mo)
{
return mo->player && mo->player->mo != mo;
}
//
// P_AnimationTick
//
void P_AnimationTick(AActor *mo)
{
if (mo && mo->tics != -1)
{
mo->tics--;
// you can cycle through multiple states in a tic
if (!mo->tics)
if (!P_SetMobjState (mo, mo->state->nextstate) )
return; // freed itself
}
}
//
// P_ClearAllNetIds
//
void P_ClearAllNetIds()
{
ServerNetID.resetNetIDs();
actor_by_netid.clear();
}
//
// P_FindThingById
// denis - fast netid lookup
//
AActor* P_FindThingById(uint32_t id)
{
netid_map_t::iterator i = actor_by_netid.find(id);
if(i == actor_by_netid.end())
return AActor::AActorPtr();
else
return i->second;
}
//
// P_SetThingId
//
void P_SetThingId(AActor *mo, uint32_t newnetid)
{
mo->netid = newnetid;
actor_by_netid[newnetid] = mo->ptr();
}
//
// P_ClearId
//
void P_ClearId(uint32_t id)
{
AActor *mo = P_FindThingById(id);
if(!mo)
return;
if(mo->player)
{
if(mo->player->mo == mo)
mo->player->mo = AActor::AActorPtr();
mo->player = NULL;
}
mo->Destroy();
}
//
// P_RemoveMobj
//
void AActor::Destroy ()
{
SV_SendDestroyActor(this);
actor_by_netid.erase(netid);
// Remove from health pool.
if (!::savegamerestore)
P_RemoveHealthPool(this);
// Add special to item respawn queue if it is destined to be respawned
// also add barrels
if (((flags & MF_SPECIAL) && !(flags & MF_DROPPED) && spawnpoint.type > 0) ||
info->type == MT_BARREL)
{
itemrespawnque.emplace(spawnpoint, level.time);
// lose one off the end?
if (demoplayback && itemrespawnque.size() >= 128)
itemrespawnque.pop();
}
// [RH] Unlink from tid chain
RemoveFromHash ();
// unlink from sector and block lists
UnlinkFromWorld ();
// Delete all nodes on the current sector_list phares 3/16/98
if (sector_list)
{
P_DelSeclist (sector_list);
sector_list = NULL;
}
// stop any playing sound
if (clientside)
S_RelinkSound (this, NULL);
Super::Destroy ();
}
void P_CheckTouchy(AActor* mo)
{
/* killough 11/98: touchy objects explode on impact */
if (mo->flags & MF_TOUCHY && mo->oflags & MFO_ARMED && mo->health > 0)
P_DamageMobj(mo, NULL, NULL, mo->health);
}
//
// P_CalculateMinMom
//
// Determines the value for mo->momz that will cause the player to grunt when
// landing.
//
// [SL] Factored out of P_MoveActor for reuse and changed to fixed-point math
// for consistency purposes.
//
fixed_t P_CalculateMinMom(const AActor *mo)
{
const float sectorGravityFloat = (mo && mo->subsector) ? mo->subsector->sector->gravity : 1.0f;
const fixed_t sectorgravity = FLOAT2FIXED(sectorGravityFloat);
const fixed_t levelgravity = co_zdoomphys ? FixedDiv(FLOAT2FIXED(level.gravity), 100 << FRACBITS) : GRAVITY * 8;
return -FixedMul(levelgravity, sectorgravity);
}
//
// P_MoveActor
//
// Tries to move an actor based on its momentum while performing
// clipping against other actors and walls.
//
// [SL] 2012-03-10 - Factored out from AActor::RunThink() to allow other
// functions to utilize the movement clipping code.
//
void P_MoveActor(AActor *mo)
{
if (!mo || !mo->subsector)
return;
AActor *onmo = NULL;
fixed_t minmom;
// [RH] If standing on a steep slope, fall down it
if (!(mo->flags & (MF_NOCLIP|MF_NOGRAVITY)) &&
!(mo->player && mo->player->spectator) && mo->momz <= 0 &&
mo->floorz == mo->z && mo->floorsector &&
mo->floorsector->floorplane.c < STEEPSLOPE &&
P_FloorHeight(mo->x, mo->y, mo->floorsector) <= mo->floorz)
{
const msecnode_t *node;
bool dopush = true;
if (mo->floorsector->floorplane.c > STEEPSLOPE*2/3)
{
for (node = mo->touching_sectorlist; node; node = node->m_tnext)
{
const sector_t *sec = node->m_sector;
if (sec->floorplane.c >= STEEPSLOPE)
{
if (P_FloorHeight(mo->x, mo->y, sec) >= mo->z - 24*FRACUNIT)
{
dopush = false;
break;
}
}
}
}
if (dopush)
{
mo->momx += mo->floorsector->floorplane.a;
mo->momy += mo->floorsector->floorplane.b;
}
}
// Handle X and Y momemtums
BlockingMobj = NULL;
P_XYMovement(mo);
if (mo->ObjectFlags & OF_Destroyed)
return; // actor was destroyed
if (mo->flags2 & MF2_FLOATBOB)
{ // Floating item bobbing motion (special1 is height)
mo->z = mo->floorz + mo->special1;
}
if ((mo->z != mo->floorz) || mo->momz || BlockingMobj)
{
// Handle Z momentum and gravity
if (P_AllowPassover() && (mo->flags2 & MF2_PASSMOBJ))
{
if (!(onmo = P_CheckOnmobj(mo)))
{
P_ZMovement(mo);
if (mo->player && mo->flags2 & MF2_ONMOBJ)
{
mo->flags2 &= ~MF2_ONMOBJ;
}
}
else
{
if (mo->player)
{
minmom = P_CalculateMinMom(mo);
if (mo->momz < minmom && !(mo->flags2 & MF2_FLY))
PlayerLandedOnThing(mo, onmo);
}
if (onmo->z + onmo->height - mo->z <= 24 * FRACUNIT)
{
if (mo->player)
{
mo->player->viewheight -= onmo->z + onmo->height - mo->z;
mo->player->deltaviewheight =
(VIEWHEIGHT - mo->player->viewheight) >> 3;
}
mo->z = onmo->z + onmo->height;
}
mo->flags2 |= MF2_ONMOBJ;
mo->momz = 0;
P_CheckTouchy(onmo);
}
}
else
{
P_ZMovement(mo);
}
if (mo->ObjectFlags & OF_Destroyed)
return; // actor was destroyed
}
else if (!(mo->momx | mo->momy) && !sentient(mo))
{ // non-sentient objects at rest
mo->oflags |= MFO_ARMED; // arm a mine which has come to rest
// killough 9/12/98: objects fall off ledges if they are hanging off
// slightly push off of ledge if hanging more than halfway off
// [RH] Be more restrictive to avoid pushing monsters/players down steps
if (!(mo->flags & MF_NOGRAVITY) && (mo->z > mo->dropoffz) && P_AllowDropOff())
{
P_ApplyTorque(mo); // Apply torque
}
else
{
mo->oflags &= ~MFO_FALLING;
mo->gear = 0; // Reset torque
}
}
if (mo->subsector)
{
//byte lastwaterlevel = waterlevel;
mo->waterlevel = 0;
if (mo->subsector->sector->waterzone)
mo->waterlevel = 3;
sector_t *hsec = mo->subsector->sector->heightsec;
if (hsec && hsec->waterzone && !mo->subsector->sector->waterzone)
{
if (mo->z < hsec->floorheight)
{
fixed_t floorheight = P_FloorHeight(mo->x, mo->y, hsec);
if (mo->z < floorheight)
{
mo->waterlevel = 1;
if (mo->z + mo->height/2 < floorheight)
{
mo->waterlevel = 2;
if (mo->z + mo->height <= floorheight)
mo->waterlevel = 3;
}
}
else if (mo->z + mo->height > P_CeilingHeight(mo->x, mo->y, hsec))
{
mo->waterlevel = 3;
}
}
else if (mo->z + mo->height > hsec->ceilingheight)
{
mo->waterlevel = 3;
}
}
if (!mo->player && map_format.actor_in_special_sector(mo))
return;
}
}
//
// P_TestActorMovement
//
// [SL] 2012-03-10 - Performs collision testing to see if an actor can move
// to (tryx, tryy, tryz). (destx, desty, destz) is the location the actor
// would be moved to with wall and actor collsion taken into account.
//
void P_TestActorMovement(AActor *mo, fixed_t tryx, fixed_t tryy, fixed_t tryz,
fixed_t &destx, fixed_t &desty, fixed_t &destz)
{
// backup the actor's position/state
ActorSnapshot backup(gametic, mo);
mo->momx = tryx - mo->x;
mo->momy = tryy - mo->y;
mo->momz = tryz - mo->z;
// Perform collision testing
P_MoveActor(mo);
// the position the actor would move to
destx = mo->x;
desty = mo->y;
destz = mo->z;
// restore the actor's position/state
backup.toActor(mo);
}
//
// P_MobjThinker
//
void AActor::RunThink ()
{
if(!subsector)
return;
// MUSINFO
if (type == MT_MUSICSOURCE && clientside)
{
if (musinfo.mapthing != this
&& displayplayer().mo->subsector
&& subsector->sector == displayplayer().mo->subsector->sector)
{
musinfo.lastmapthing = musinfo.mapthing;
musinfo.mapthing = this->ptr();
musinfo.tics = 30;
}
return;
}
prevx = x;
prevy = y;
prevz = z;
if (!player || P_IsVoodooDoll(this))
{
prevangle = angle;
prevpitch = pitch;
}
// server removal of corpses only
if (!clientside && serverside)
{
if (type == MT_PLAYER && health <= 0)
deadtic++;
}
// GhostlyDeath -- Was a spectator but now it's nothing!
if ((this->oflags & MFO_SPECTATOR ) && !player)
{
this->Destroy();
return;
}
// remove dead players but don't tell clients about it
if (type == MT_PLAYER && !player && deadtic >= REMOVECORPSESTIC)
{
this->Destroy();
return;
}
// [RH] Fade a stealth monster in and out of visibility
if (visdir > 0)
{
translucency += 2*FRACUNIT/TICRATE;
if (translucency > FRACUNIT)
{
translucency = FRACUNIT;
visdir = 0;
}
}
else if (visdir < 0)
{
translucency -= 3*FRACUNIT/TICRATE/2;
if (translucency < 0)
{
translucency = 0;
visdir = 0;
}
}
#ifdef CLIENT_APP
if (player && ::consoleplayer_id == player->id)
{
v3double_t start, end;
M_ActorPositionToVec3(&start, this);
P_MoveActor(this);
M_ActorPositionToVec3(&end, this);
HU_AddPlayerSpeed(start, end);
}
else
#endif
{
P_MoveActor(this);
}
if(predicting)
return;
if (flags2 & MF2_DORMANT)
return;
// cycle through states,
// calling action functions at transitions
if (tics != -1)
{
// run P_AnimationTick on everything except players who aren't voodoo dolls
if (!(player && this == player->mo))
P_AnimationTick(this);
}
else
{
const bool respawnmonsters = (G_GetCurrentSkill().respawn_counter || sv_monstersrespawn);
// check for nightmare respawn
if (!(flags & MF_COUNTKILL) || !respawnmonsters)
return;
// Ch0wW - Let the server handle it alone.
// (CHECKME: Does that interfere with vanilla demos?)
if ((multiplayer && clientside && !serverside))
return;
movecount++;
int respawntimer = 0;
if (G_GetCurrentSkill().respawn_counter < 0)
respawntimer = G_GetCurrentSkill().respawn_counter;
else
respawntimer = 12 * TICRATE;
if (movecount < respawntimer)
return;
if (level.time & 31)
return;
if (P_Random (this) > 4)
return;
P_NightmareRespawn (this);
}
}
void AActor::Serialize (FArchive &arc)
{
static constexpr uint32_t TLATE_NONE = 0xFFFFFFFF;
static constexpr uint32_t TLATE_BOSS = 0xFFFFFFFE;
Super::Serialize (arc);
if (arc.IsStoring ())
{
int playerid = player ? player->id : 0;
arc << netid
<< x
<< y
<< z
<< pitch
<< angle
// [SL] Removed AActor::roll
// delete this next time saved-game compatibilty changes
<< 0
<< sprite
<< frame
<< effects
<< floorz
<< ceilingz
<< dropoffz
<< radius
<< height
<< momx
<< momy
<< momz
<< type
<< tics
<< state
<< flags
<< flags2
<< flags3
<< oflags
<< statusflags
<< special1
<< special2
<< health
<< movedir
<< visdir
<< movecount
/*<< target ? target->netid : 0*/
/*<< lastenemy ? lastenemy->netid : 0*/
<< reactiontime
<< threshold
<< playerid
<< lastlook
<< tracer
<< tid
<< special
<< args[0]
<< args[1]
<< args[2]
<< args[3]
<< args[4]
/*<< goal ? goal->netid : 0*/
<< (unsigned)0
<< translucency
<< waterlevel
<< gear;
// NOTE(jsd): This is pretty awful right here:
// [AM] I am now part of the problem.
if (translation)
{
if (translation.getTable() == ::bosstable)
{
arc << TLATE_BOSS;
}
else
{
arc << (uint32_t)(translation.getTable() - ::translationtables);
}
}
else
{
arc << TLATE_NONE;
}
spawnpoint.Serialize (arc);
baseline.Serialize(arc);
}
else
{
unsigned dummy;
unsigned playerid;
int newnetid;
AActor* tmptracer;
arc >> newnetid
>> x
>> y
>> z
>> pitch
>> angle
// [SL] Removed AActor::roll
// delete this next time saved-game compatibilty changes
>> dummy
>> sprite
>> frame
>> effects
>> floorz
>> ceilingz
>> dropoffz
>> radius
>> height
>> momx
>> momy
>> momz
>> type
>> tics
>> state
>> flags
>> flags2
>> flags3
>> oflags
>> statusflags
>> special1
>> special2
>> health
>> movedir
>> visdir
>> movecount
/*>> target->netid*/
/*>> lastenemy->netid*/
>> reactiontime
>> threshold
>> playerid
>> lastlook
>> tmptracer
>> tid
>> special
>> args[0]
>> args[1]
>> args[2]
>> args[3]
>> args[4]
/*>> goal->netid*/
>> dummy
>> translucency
>> waterlevel
>> gear;
tracer.init(tmptracer);
P_SetThingId(this, newnetid);
uint32_t trans;
arc >> trans;
if (trans == TLATE_NONE)
{
translation = translationref_t();
}
else if (trans == TLATE_BOSS)
{
translation = translationref_t(::bosstable);
}
else
{
if ((trans / 256) <= MAXPLAYERS)
{
translation = translationref_t(::translationtables + trans, trans / 256);
}
else
{
translation = translationref_t(::translationtables + trans);
}
}
spawnpoint.Serialize (arc);
baseline.Serialize(arc);
if (mobjinfo.find(type) == mobjinfo.end())
{
I_Error("AActor::Serialize: Unknown object type ({}) in saved game", type);
}
if (sprnames.find(sprite) == sprnames.end())
{
I_Error("AActor::Serialize: Unknown sprite ({}) in saved game", sprite);
}
info = &mobjinfo[type];
touching_sectorlist = NULL;
LinkToWorld ();
floorsector = subsector ? subsector->sector : nullptr;
AddToHash ();
if(playerid && validplayer(idplayer(playerid)))
{
player = &idplayer(playerid);
player->mo = ptr();
player->camera = player->mo;
}
}
}
//
// P_ThingInfoHeight [From EE]
//
// haleyjd 07/06/05:
//
// Function to retrieve proper thing height information for a thing.
//
int P_ThingInfoHeight(mobjinfo_t *mi)
{
return (P_AllowPassover() && mi->cdheight ?
mi->cdheight : mi->height);
}
// Use a heuristic approach to detect infinite state cycles: Count the number
// of times the loop in P_SetMobjState() executes and exit with an error once
// an arbitrary very large limit is reached.
//
// [AM] Taken from Crispy Doom, with a smaller limit - 10,000 iterations
// still seems like a lot to me.
#define MOBJ_CYCLE_LIMIT 512
// P_SetMobjState
//
// Returns true if the mobj is still present.
bool P_SetMobjState(AActor *mobj, int32_t state, bool cl_update)
{
state_t* st;
int cycle_counter = 0;
do
{
if (states.find(state) == states.end())
{
I_Error("P_SetMobjState: State {} does not exist in state table.", state);
}
// strongly typed enum
if (state == S_NULL)
{
mobj->state = &states[S_NULL];
mobj->Destroy();
return false;
}
st = &states[state];
mobj->state = st;
mobj->tics = st->tics;
mobj->sprite = st->sprite;
mobj->frame = st->frame;
#if defined(SERVER_APP)
// [AM] Broadcast the mobj's state after changing it but before
// running the action associated with it. Also, only update
// the first state, since the client will cycle through the rest.
if (cl_update && !cycle_counter)
SV_UpdateMobjState(mobj);
#endif
// Modified handling.
// Call action functions when the state is set
if (st->action)
{
st->action(mobj);
}
state = st->nextstate;
// denis - prevent harmful state cycle
// [AM] A slightly different heruistic that doesn't involve global state.
if (cycle_counter++ > MOBJ_CYCLE_LIMIT)
{
I_Error("P_SetMobjState: Infinite state cycle detected for {} at state {}.",
mobj->info->name ? mobj->info->name : fmt::format("thing {}", mobj->info->type), state);
}
} while (!mobj->tics);
#if defined(SERVER_APP)
// [AM] Now broadcast the final state of the mobj after all actions
// have run.
if (cl_update)
SV_UpdateMobj(mobj);
#endif
return true;
}
//
// P_WindThrustActor
//
static void P_WindThrustActor(AActor* mo)
{
if ( mo
&& mo->subsector
&& (mo->flags2 & MF2_WINDTHRUST))
{
static constexpr int windTab[3] = {2048*5, 2048*10, 2048*25};
const int special = mo->subsector->sector->special;
switch (special)
{
case 40: case 41: case 42: // Wind_East
P_ThrustMobj (mo, 0, windTab[special - 40]);
break;
case 43: case 44: case 45: // Wind_North
P_ThrustMobj (mo, ANG90, windTab[special - 43]);
break;
case 46: case 47: case 48: // Wind_South
P_ThrustMobj (mo, ANG270, windTab[special - 46]);
break;
case 49: case 50: case 51: // Wind_West
P_ThrustMobj (mo, ANG180, windTab[special - 49]);
break;
}
}
}
static void P_WallBouncy(AActor* mo)
{
if (mo->flags & MF_BOUNCES ||
(!mo->player && BlockingLine &&
mo->z <= mo->floorz && P_GetFriction(mo, NULL) > ORIG_FRICTION))
{
if (BlockingLine)
{
fixed_t r = ((BlockingLine->dx >> FRACBITS) * mo->momx +
(BlockingLine->dy >> FRACBITS) * mo->momy) /
((BlockingLine->dx >> FRACBITS) * (BlockingLine->dx >> FRACBITS) +
(BlockingLine->dy >> FRACBITS) * (BlockingLine->dy >> FRACBITS));
fixed_t x = FixedMul(r, BlockingLine->dx);
fixed_t y = FixedMul(r, BlockingLine->dy);
// reflect momentum away from wall
mo->momx = x * 2 - mo->momx;
mo->momy = y * 2 - mo->momy;
// if under gravity, slow down in
// direction perpendicular to wall.
if (!(mo->flags & MF_NOGRAVITY))
{
mo->momx = (mo->momx + x) / 2;
mo->momy = (mo->momy + y) / 2;
}
}
else
{
mo->momx = mo->momy = 0;
}
}
else
{
mo->momx = mo->momy = 0;
}
}
//
// P_LostSoulReset
//
// Resets the Lost Soul to its spawn state after it collides with something.
//
static void P_LostSoulReset(AActor* mo)
{
if (!mo->momx && !mo->momy && (mo->flags & MF_SKULLFLY))
{
mo->flags &= ~MF_SKULLFLY;
mo->momx = mo->momy = mo->momz = 0;
P_SetMobjState(mo, mo->info->spawnstate);
}
}
//
// P_ExplodeMissileAgainstWall
//
// Checks if lines that the missile collided with are ordinary walls or
// are skywall-hacks/Line_Horizons. Returns false if the actor is destroyed.
//
static bool P_ExplodeMissileAgainstWall(AActor* mo)
{
if (mo->flags & MF_MISSILE)
{
short spe;
if (map_format.getZDoom())
spe = Line_Horizon;
else
spe = 337;
// [SL] 2012-01-25 - Don't explode missiles on horizon line
if (BlockingLine && BlockingLine->special == spe)
{
mo->Destroy();
return false;
}
// [SL] 2013-05-14 - Check for sky wall hacks
if (ceilingline)
{
const sector_t* sec1, *sec2;
if (!co_fixweaponimpacts || !ceilingline->backsector || !P_PointOnLineSide(mo->x, mo->y, ceilingline))
{
sec1 = ceilingline->frontsector;
sec2 = ceilingline->backsector;
}
else
{
sec1 = ceilingline->backsector;
sec2 = ceilingline->frontsector;
}
bool skyceiling1 = R_IsSkyFlat(sec1->ceilingpic);
bool skyceiling2 = sec2 && R_IsSkyFlat(sec2->ceilingpic);
if (skyceiling2)
{
if (!co_fixweaponimpacts || (skyceiling1 && mo->z > P_CeilingHeight(mo->x, mo->y, sec2)))
{
mo->Destroy();
return false;
}
}
}
// [SL] 2011-06-02 - Only server should control explosions
if (serverside)
P_ExplodeMissile(mo);
}
return true;
}
//
// P_ActorSlideAgainstWall
//
static void P_ActorSlideAgainstWall(AActor* mo)
{
if (BlockingLine != NULL &&
mo->player && mo->waterlevel && mo->waterlevel < 3 &&
(mo->player->cmd.forwardmove | mo->player->cmd.sidemove) &&
BlockingLine->sidenum[1] != R_NOSIDE)
{
mo->momz = WATER_JUMP_SPEED;
}
P_SlideMove(mo);
}
//
// P_ActorSlideAgainstActor
//
static void P_ActorSlideAgainstActor(AActor* mo, fixed_t ptryx, fixed_t ptryy)
{
// try sliding in the x direction
fixed_t tx = 0, ty = ptryy - mo->y;
bool walkplane = P_CheckSlopeWalk(mo, tx, ty);
if (P_TryMove(mo, mo->x + tx, mo->y + ty, true, walkplane))
{
mo->momx = 0;
}
else
{
// try sliding in the y direction
tx = ptryx - mo->x, ty = 0;
walkplane = P_CheckSlopeWalk(mo, tx, ty);
if (!P_TryMove(mo, mo->x + tx, mo->y + ty, true, walkplane))
mo->momx = 0;
mo->momy = 0;
}
}
//
// P_ApplyXYFriction
//
// Applies several types of friction to the actor's velocity in the xy-direction.
// First, airfriction is applied to players who are in the air. Then sector
// friction is applied.
//
static void P_ApplyXYFriction(AActor* mo)
{
// No friction for missiles ever
if (mo->flags & (MF_MISSILE | MF_SKULLFLY))
return;
// Apply air friction
if (mo->z > mo->floorz && !(mo->flags2 & (MF2_ONMOBJ | MF2_FLY)) && mo->waterlevel == 0)
{
if (co_zdoomphys && mo->player && level.airfriction != FRACUNIT)
{
mo->momx = FixedMul(mo->momx, level.airfriction);
mo->momy = FixedMul(mo->momy, level.airfriction);
}
return;
}
// killough 8/11/98: add bouncers
// killough 9/15/98: add objects falling off ledges
// killough 11/98: only include bouncers hanging off ledges
if (((mo->flags & MF_BOUNCES && mo->z > mo->dropoffz) || mo->flags & MF_CORPSE ||
mo->oflags & MFO_FALLING) &&
(mo->momx > FRACUNIT / 4 || mo->momx < -FRACUNIT / 4 || mo->momy > FRACUNIT / 4 ||
mo->momy < -FRACUNIT / 4) &&
mo->floorz != mo->subsector->sector->floorheight)
return; // do not stop sliding if halfway off a step with some momentum
// keep corpses sliding if halfway off a step with some momentum
if ((mo->flags & MF_CORPSE) && (abs(mo->momx) > FRACUNIT/4 || abs(mo->momy) > FRACUNIT/4))
{
if (mo->floorz != P_FloorHeight(mo))
return;
}
bool stationary_player = mo->player &&
(mo->player->cmd.forwardmove == 0 && mo->player->cmd.sidemove == 0);
// killough 11/98: Stop voodoo dolls that have come to rest,
// despite any moving corresponding player:
if (abs(mo->momx) < STOPSPEED && abs(mo->momy) < STOPSPEED &&
(!mo->player || stationary_player || P_IsVoodooDoll(mo)))
{
// if in a walking frame, stop moving
// killough 10/98: Don't affect main player when voodoo dolls stop:
if (mo->player && !P_IsVoodooDoll(mo) && static_cast<uint32_t>((mo->state->statenum) - S_PLAY_RUN1) < 4)
P_SetMobjState(mo, S_PLAY);
mo->momx = mo->momy = 0;
}
else
{
// phares 3/17/98
// Friction will have been adjusted by friction thinkers for icy
// or muddy floors. Otherwise it was never touched and
// remained set at ORIG_FRICTION
//
// killough 8/28/98: removed inefficient thinker algorithm,
// instead using touching_sectorlist in P_GetFriction() to
// determine friction (and thus only when it is needed).
//
// killough 10/98: changed to work with new bobbing method.
// Reducing player momentum is no longer needed to reduce
// bobbing, so ice works much better now.
fixed_t friction = P_GetFriction(mo, NULL);
mo->momx = FixedMul(mo->momx, friction);
mo->momy = FixedMul(mo->momy, friction);
}
}
//
// P_XYMovement
//
void P_XYMovement(AActor *mo)
{
if (!mo || !mo->subsector)
return;
P_WindThrustActor(mo);
P_LostSoulReset(mo);
if (!mo->momx && !mo->momy)
return;
fixed_t maxmove = (mo->waterlevel < 2) || (mo->flags & MF_MISSILE) ? MAXMOVE/2 : MAXMOVE/8;
fixed_t mom_clamp = maxmove * 2;
fixed_t xmove = mo->momx = clamp(mo->momx, -mom_clamp, mom_clamp);
fixed_t ymove = mo->momy = clamp(mo->momy, -mom_clamp, mom_clamp);
// [SL] is the destination on a slope and if so, should the actor
// continue to be on the floor?
bool walkplane = P_CheckSlopeWalk(mo, xmove, ymove);
do
{
fixed_t ptryx, ptryy;
// This is where the "wallrunning" effect happens. Vanilla only
// allows wallrunning North and East, while ZDoom physics allow
// North and South.
if (co_zdoomphys && (abs(xmove) > maxmove || abs(ymove) > maxmove))
{
xmove >>= 1;
ymove >>= 1;
ptryx = mo->x + xmove;
ptryy = mo->y + ymove;
}
else if (!co_zdoomphys && ((xmove > maxmove || ymove > maxmove) || (co_mbfphys && (xmove < -maxmove || ymove < -maxmove))))
{
ptryx = mo->x + xmove / 2;
ptryy = mo->y + ymove / 2;
xmove >>= 1;
ymove >>= 1;
}
else
{
ptryx = mo->x + xmove;
ptryy = mo->y + ymove;
xmove = ymove = 0;
}
if (!P_TryMove(mo, ptryx, ptryy, true, walkplane))
{
// blocked move
if (mo->flags2 & MF2_SLIDE)
{
// try to slide along it
if (BlockingMobj == NULL)
P_ActorSlideAgainstWall(mo);
else
P_ActorSlideAgainstActor(mo, ptryx, ptryy);
}
else if (mo->flags & MF_MISSILE)
{
if (!P_ExplodeMissileAgainstWall(mo))
return;
}
else if (!(mo->flags & (MF_MISSILE | MF_SKULLFLY)) && P_IsMBFCompatMode())
{
P_WallBouncy(mo);
}
else
{
mo->momx = mo->momy = 0;
}
}
// determine if the actor is still on the floor after sliding on a slope
fixed_t dummy_x, dummy_y;
walkplane = P_CheckSlopeWalk(mo, dummy_x, dummy_y);
} while (xmove || ymove);
// slow down
if (mo->player && !P_IsVoodooDoll(mo) && mo->player->cheats & CF_NOMOMENTUM)
{
// debug option for no sliding at all
mo->momx = mo->momy = 0;
return;
}
P_ApplyXYFriction(mo);
}
//
// P_CorrectLostSoulBounce
//
// Note (id):
// somebody left this after the setting momz to 0,
// kinda useless there.
//
// cph - This was the a bug in the linuxdoom-1.10 source which
// caused it not to sync Doom 2 v1.9 demos. Someone
// added the above comment and moved up the following code. So
// demos would desync in close lost soul fights.
// Note that this only applies to original Doom 1 or Doom2 demos - not
// Final Doom and Ultimate Doom. So we test demo_compatibility *and*
// gamemission. (Note we assume that Doom1 is always Ult Doom, which
// seems to hold for most published demos.)
//
// fraggle - cph got the logic here slightly wrong. There are three
// versions of Doom 1.9:
//
// * The version used in registered doom 1.9 + doom2 - no bounce
// * The version used in ultimate doom - has bounce
// * The version used in final doom - has bounce
//
// So we need to check that this is either retail or commercial
// (but not doom2)
//
// [SL] The source code shows both Doom & Doom 2 BFG Editions also have bounce.
//
static bool P_CorrectLostSoulBounce()
{
if (co_zdoomphys)
return true;
if (gamemode == retail || gamemode == retail_bfg || gamemode == commercial_bfg)
return true;
if ((gamemode == commercial || gamemode == commercial_bfg)
&& (gamemission == pack_tnt || gamemission == pack_plut))
return true;
return false;
}
//
// P_PlayerSmoothStepUp
//
// Smooths large changes in the viewheight due to stepping up stairs.
//
static void P_PlayerSmoothStepUp(AActor* mo)
{
if (mo->player && mo->z < mo->floorz)
{
mo->player->viewheight -= mo->floorz-mo->z;
mo->player->deltaviewheight = (VIEWHEIGHT - mo->player->viewheight) >> 3;
}
}
//
// P_CalculateActorGravityDoom
//
// Calculates the change in velocity in the z-direction for an actor due to
// vanilla Doom's gravity calculation. Note that the effect of gravity is
// twice as strong for the first tic that a player walks over a ledge (momz == 0).
//
static fixed_t P_CalculateActorGravityDoom(AActor* mo)
{
fixed_t velocity_change = FLOAT2FIXED(level.gravity * mo->subsector->sector->gravity) / 800;
if (mo->momz == 0)
velocity_change *= 2;
if (mo->flags2 & MF2_LOGRAV)
velocity_change >>= 3;
return velocity_change;
}
//
// P_CalculateActorGravityZDoom
//
// Calculates the change in velocity in the z-direction for an actor due to
// ZDoom 1.23b33's gravity calculation. ZDoom alters vanilla Doom behavior
// with regards vanilla's special case when momz == 0.
//
static fixed_t P_CalculateActorGravityZDoom(AActor* mo)
{
fixed_t velocity_change = (fixed_t)(level.gravity * mo->subsector->sector->gravity * 81.92f);
if (mo->flags2 & MF2_LOGRAV)
velocity_change >>= 3;
return velocity_change;
}
//
// P_ApplyGravity
//
static void P_ApplyGravity(AActor* mo, fixed_t momz_change)
{
if (mo->z > mo->floorz && !(mo->flags & MF_NOGRAVITY))
{
fixed_t startmomz = mo->momz;
bool stationary_player = mo->player && !(mo->player->cmd.forwardmove | mo->player->cmd.sidemove);
if (mo->waterlevel == 0 || mo->flags & MF_CORPSE || stationary_player)
mo->momz -= momz_change;
if (mo->waterlevel > 1)
{
fixed_t sinkspeed = mo->flags & MF_CORPSE ? -WATER_SINK_SPEED/3 : -WATER_SINK_SPEED;
if (mo->momz < sinkspeed)
mo->momz = MIN(startmomz, sinkspeed);
else
mo->momz = startmomz + ((mo->momz - startmomz) >> WATER_SINK_FACTOR);
}
}
}
//
// P_AdjustMonsterFloat
//
// Makes floating monsters adjust their z-position to be closer to the
// z-position of their target.
//
static void P_AdjustMonsterFloat(AActor* mo)
{
if (mo->flags & MF_FLOAT && mo->target)
{
// float down towards target if too close
if (!(mo->flags & MF_SKULLFLY) && !(mo->flags & MF_INFLOAT))
{
fixed_t dist = P_AproxDistance(mo->x - mo->target->x, mo->y - mo->target->y);
fixed_t delta = (mo->target->z + (mo->height >> 1)) - mo->z;
if (delta < 0 && dist < -(delta * 3))
mo->z -= FLOATSPEED;
else if (delta > 0 && dist < (delta * 3))
mo->z += FLOATSPEED;
}
}
}
//
// P_PlayerFlyBob
//
// Bobs flying players up and down.
//
static void P_PlayerFlyBob(AActor* mo)
{
if ((mo->flags2 & MF2_FLY) && mo->z > mo->floorz)
{
if (!mo->player->spectator)
mo->z += finesine[(FINEANGLES / 80 * level.time) & FINEMASK] / 8;
mo->momz = FixedMul(mo->momz, FRICTION_FLY);
}
}
//
// P_PlayerHitGround
//
static void P_PlayerHitGround(AActor* mo)
{
mo->player->jumpTics = 7; // delay any jumping for a short while
if (mo->momz < P_CalculateMinMom(mo) && !(mo->player->spectator) && !(mo->flags2 & MF2_FLY))
{
// Squat down. Decrease viewheight for a moment after hitting the
// ground (hard), and utter appropriate sound.
PlayerLandedOnThing(mo, NULL);
}
}
//
// P_ClipMovementToFloor
//
// Clips the z-position of the actor to the floor. Returns false if
// the actor has been destroyed, as is the case with missiles exploding.
//
static bool P_ClipMovementToFloor(AActor* mo)
{
if (mo->z <= mo->floorz)
{
// [AM] If there is a actor special for hitting the floor, activate it.
if (mo->subsector->sector->SecActTarget &&
P_FloorHeight(mo->x, mo->y, mo->subsector->sector) == mo->floorz)
A_TriggerAction(mo->subsector->sector->SecActTarget, mo, SECSPAC_HitFloor);
// Lost Soul hit the floor
if (mo->flags & MF_SKULLFLY && P_CorrectLostSoulBounce())
mo->momz = -mo->momz;
mo->z = mo->floorz;
if (mo->momz < 0)
{
if (mo->player)
P_PlayerHitGround(mo);
mo->momz = 0;
}
// cph 2001/05/26 - See lost soul bouncing comment above. We need this here
// for bug compatibility with original Doom2 v1.9 - if a soul is charging
// and hit by a raising floor this incorrectly reverses its Y momentum.
if (mo->flags & MF_SKULLFLY && !P_CorrectLostSoulBounce())
mo->momz = -mo->momz;
// Explode missiles
if ((mo->flags & MF_MISSILE) && !(mo->flags & MF_NOCLIP))
{
if (co_fixweaponimpacts && R_IsSkyFlat(mo->subsector->sector->floorpic))
mo->Destroy();
else if (serverside)
P_ExplodeMissile(mo);
return false;
}
}
return true;
}
//
// P_ClipMovementToCeiling
//
// Clips the z-position of the actor to the ceiling. Returns false if
// the actor has been destroyed, as is the case with missiles exploding.
//
static bool P_ClipMovementToCeiling(AActor* mo)
{
if (mo->z + mo->height > mo->ceilingz)
{
// [AM] If there is a actor special for hitting the floor, activate it.
if (mo->subsector->sector->SecActTarget &&
P_CeilingHeight(mo->x, mo->y, mo->subsector->sector) == mo->ceilingz)
A_TriggerAction(mo->subsector->sector->SecActTarget, mo, SECSPAC_HitCeiling);
if (mo->momz > 0)
mo->momz = 0;
mo->z = mo->ceilingz - mo->height;
// Lost Soul hit the ceiling
if (mo->flags & MF_SKULLFLY)
mo->momz = -mo->momz;
// Explode missiles
if ((mo->flags & MF_MISSILE) && !(mo->flags & MF_NOCLIP))
{
if (co_fixweaponimpacts && R_IsSkyFlat(mo->subsector->sector->ceilingpic))
mo->Destroy();
else if (serverside)
P_ExplodeMissile(mo);
return false;
}
}
return true;
}
//
// P_ActorFakeSectorTriggers
//
// [AM] Handle actor specials that deal with fake floors and ceilings.
//
static void P_ActorFakeSectorTriggers(AActor* mo, fixed_t oldz)
{
if (mo->subsector->sector->heightsec && mo->subsector->sector->SecActTarget)
{
const sector_t* hs = mo->subsector->sector->heightsec;
fixed_t waterz = P_FloorHeight(mo->x, mo->y, hs);
fixed_t viewheight;
if (mo->player)
viewheight = mo->player->viewheight;
else
viewheight = mo->height / 2;
fixed_t newz = mo->z + viewheight;
oldz += viewheight;
if (oldz <= waterz && newz > waterz) // view above fake floor
A_TriggerAction(mo->subsector->sector->SecActTarget, mo, SECSPAC_EyesSurface);
else if (oldz > waterz && newz <= waterz) // view below fake floor
// View went below fake floor
A_TriggerAction(mo->subsector->sector->SecActTarget, mo, SECSPAC_EyesDive);
if (!(hs->MoreFlags & SECF_FAKEFLOORONLY))
{
waterz = P_CeilingHeight(mo->x, mo->y, hs);
if (oldz <= waterz && newz > waterz) // view above fake ceiling
A_TriggerAction(mo->subsector->sector->SecActTarget, mo, SECSPAC_EyesAboveC);
else if (oldz > waterz && newz <= waterz) // view below fake ceiling
A_TriggerAction(mo->subsector->sector->SecActTarget, mo, SECSPAC_EyesBelowC);
}
}
}
static void P_ApplyBouncyPhysics(AActor *mo)
{
if (mo->flags & MF_BOUNCES && mo->momz)
{
mo->z += mo->momz;
if (mo->z <= mo->floorz) // bounce off floors
{
mo->z = mo->floorz;
if (mo->momz < 0)
{
mo->momz = -mo->momz;
if (!(mo->flags & MF_NOGRAVITY)) // bounce back with decay
{
mo->momz = mo->flags & MF_FLOAT
? // floaters fall slowly
mo->flags & MF_DROPOFF
? // DROPOFF indicates rate
FixedMul(mo->momz, (fixed_t)(FRACUNIT * .85))
: FixedMul(mo->momz, (fixed_t)(FRACUNIT * .70))
: FixedMul(mo->momz, (fixed_t)(FRACUNIT * .45));
// Bring it to rest below a certain speed
if (abs(mo->momz) <= mo->info->mass * (GRAVITY * 4 / 256))
mo->momz = 0;
}
return;
}
}
else if (mo->z >= mo->ceilingz - mo->height) // bounce off ceilings
{
mo->z = mo->ceilingz - mo->height;
if (mo->momz > 0)
{
if (!R_IsSkyFlat(mo->subsector->sector->ceilingpic))
mo->momz = -mo->momz; // always bounce off non-sky ceiling
else if (mo->flags & MF_MISSILE)
mo->Destroy(); // missiles don't bounce off skies
else if (mo->flags & MF_NOGRAVITY)
mo->momz = -mo->momz; // bounce unless under gravity
return;
}
}
else
{
if (!(mo->flags & MF_NOGRAVITY)) // free-fall under gravity
mo->momz -= mo->info->mass * (GRAVITY / 256);
return;
}
// came to a stop
mo->momz = 0;
if (mo->flags & MF_MISSILE)
{
if (ceilingline && ceilingline->backsector &&
R_IsSkyFlat(ceilingline->backsector->ceilingpic) &&
mo->z > ceilingline->backsector->ceilingheight)
mo->Destroy();
else
P_ExplodeMissile(mo);
}
return;
}
}
//
// P_ZMovement
//
// Handles the movement of an actor in the z-direction, including gravity
// and clipping movement against the floor and ceiling.
//
// [SL] 2014-01-11 - Refactored significantly for clarity.
//
void P_ZMovement(AActor *mo)
{
// This check also protects a bunch of static functions that are only used
// as part of Z-Movement.
if (not (mo && mo->subsector))
{
return;
}
fixed_t oldz = mo->z;
if (mo->flags & MF_BOUNCES && mo->momz)
{
P_ApplyBouncyPhysics(mo);
P_CheckTouchy(mo);
return;
}
if (mo->player && !P_IsVoodooDoll(mo))
P_PlayerSmoothStepUp(mo);
// ZDoom applies gravity earlier in the function than vanilla
if (co_zdoomphys)
P_ApplyGravity(mo, P_CalculateActorGravityZDoom(mo));
mo->z += mo->momz;
if (mo->momz < 0)
P_CheckTouchy(mo);
if (mo->flags & MF_FLOAT)
P_AdjustMonsterFloat(mo);
if (mo->player)
P_PlayerFlyBob(mo);
// apply friction in the z-direction in water
if (mo->waterlevel && !(mo->flags & MF_NOGRAVITY))
mo->momz = FixedMul(mo->momz, mo->subsector->sector->friction);
if (!P_ClipMovementToFloor(mo))
return;
// Apply vanilla gravity
if (!co_zdoomphys)
P_ApplyGravity(mo, P_CalculateActorGravityDoom(mo));
if (!P_ClipMovementToCeiling(mo))
return;
// [AM] Handle actor specials that deal with fake floors and ceilings.
P_ActorFakeSectorTriggers(mo, oldz);
}
//
// PlayerLandedOnThing
//
void PlayerLandedOnThing(AActor *mo, AActor *onmobj)
{
if ((clientside && predicting) || (mo->player && mo->player->spectator))
return;
mo->player->deltaviewheight = mo->momz>>3;
// The server sends the sound to us for other players
if (mo->player->id != consoleplayer_id && !serverside)
return;
if (co_zdoomphys)
{
// [SL] 2011-06-16 - ZDoom Oomphiness
if (mo->health > 0)
{
if (mo->momz < (fixed_t)(level.gravity * mo->subsector->sector->gravity * -983.04f))
UV_SoundAvoidPlayer(mo, CHAN_VOICE, "player/male/land1", ATTN_NORM);
UV_SoundAvoidPlayer(mo, CHAN_VOICE, "player/male/land1", ATTN_NORM);
}
}
else
{
// [SL] 2011-06-16 - Vanilla Doom Oomphiness
UV_SoundAvoidPlayer(mo, CHAN_VOICE, "player/male/land1", ATTN_NORM);
}
// mo->player->centering = true;
}
//
// P_NightmareRespawn
//
void P_NightmareRespawn (AActor *mobj)
{
fixed_t x;
fixed_t y;
fixed_t z;
subsector_t* ss;
mapthing2_t* mthing;
AActor *mo;
x = mobj->spawnpoint.x << FRACBITS;
y = mobj->spawnpoint.y << FRACBITS;
// something is occupying it's position?
if (!P_CheckPosition (mobj, x, y))
return; // no respawn
// spawn a teleport fog at old spot
// because of removal of the body?
mo = new AActor(
mobj->x,
mobj->y,
P_FloorHeight(mobj) + INT2FIXED(gameinfo.telefogHeight),
MT_TFOG
);
// initiate teleport sound
if (clientside)
S_Sound (mo, CHAN_VOICE, "misc/teleport", 1, ATTN_NORM);
// spawn a teleport fog at the new spot
ss = P_PointInSubsector (x,y);
// spawn a teleport fog at the new spot
mo = new AActor (x, y, P_FloorHeight(x, y, ss->sector) + INT2FIXED(gameinfo.telefogHeight), MT_TFOG);
if (clientside)
S_Sound (mo, CHAN_VOICE, "misc/teleport", 1, ATTN_NORM);
// spawn the new monster
mthing = &mobj->spawnpoint;
// spawn it
if (mobj->info->flags & MF_SPAWNCEILING)
z = ONCEILINGZ;
else if (mobj->info->flags2 & MF2_FLOATBOB)
z = mthing->z << FRACBITS;
else
z = ONFLOORZ;
// spawn it
// inherit attributes from deceased one
if(serverside)
{
mo = new AActor (x, y, z, mobj->type);
mo->spawnpoint = mobj->spawnpoint;
mo->angle = ANG45 * (mthing->angle/45);
if (mthing->flags & MTF_AMBUSH)
mo->flags |= MF_AMBUSH;
SV_SpawnMobj(mo);
// Count the respawned_monsters up.
level.respawned_monsters++;
SV_UpdateMonsterRespawnCount();
mo->reactiontime = 18;
}
// remove the old monster,
mobj->Destroy ();
}
AActor* AActor::TIDHash[TIDHashSize];
//
// [RH] Some new functions to work with Thing IDs. ------->
//
//
// P_ClearTidHashes
//
// Clears the tid hashtable.
//
void AActor::ClearTIDHashes ()
{
for (size_t i = 0; i < TIDHashSize; i++)
TIDHash[i] = NULL;
}
//
// P_AddMobjToHash
//
// Inserts an mobj into the correct chain based on its tid.
// If its tid is 0, this function does nothing.
//
void AActor::AddToHash ()
{
if (tid == 0)
{
inext = iprev = NULL;
return;
}
else
{
int hash = TIDHASH (tid);
inext = TIDHash[hash];
iprev = NULL;
TIDHash[hash] = this;
}
}
//
// P_RemoveMobjFromHash
//
// Removes an mobj from its hash chain.
//
void AActor::RemoveFromHash ()
{
if (tid == 0)
return;
else
{
if (iprev == NULL)
{
// First mobj in the chain (probably)
int hash = TIDHASH(tid);
if (TIDHash[hash] == this)
TIDHash[hash] = inext;
if (inext)
{
inext->iprev = NULL;
inext = NULL;
}
}
else
{
// Not the first mobj in the chain
iprev->inext = inext;
if (inext)
{
inext->iprev = iprev;
inext = NULL;
}
iprev = NULL;
}
}
}
//
// P_FindMobjByTid
//
// Returns the next mobj with the tid after the one given,
// or the first with that tid if no mobj is passed. Returns
// NULL if there are no more.
//
AActor *AActor::FindByTID (int tid) const
{
return FindByTID (this, tid);
}
AActor *AActor::FindByTID (const AActor *actor, int tid)
{
// Mobjs without tid are never stored.
if (tid == 0)
return NULL;
if (!actor)
actor = TIDHash[TIDHASH(tid)];
else
actor = actor->inext;
while (actor && actor->tid != tid)
actor = actor->inext;
return const_cast<AActor *>(actor);
}
//
// P_FindGoal
//
// Like FindByTID except it also matches on type.
//
AActor *AActor::FindGoal (int tid, int kind) const
{
return FindGoal (this, tid, kind);
}
AActor *AActor::FindGoal (const AActor *actor, int tid, int kind)
{
do
{
actor = FindByTID (actor, tid);
} while (actor && actor->type != kind);
return const_cast<AActor *>(actor);
}
// <------- [RH] End new functions
//
// GAME SPAWN FUNCTIONS
//
//
// P_SpawnPuff
//
void P_SpawnPuff (fixed_t x, fixed_t y, fixed_t z)
{
// [SL] Allow only servers and clients that are predicting their own shots
if (!serverside && (shootthing != consoleplayer().mo ||
!consoleplayer().userinfo.predict_weapons))
return;
AActor *puff;
z += (P_RandomDiff () << 10);
puff = new AActor(x, y, z, MT_PUFF);
puff->momz = FRACUNIT;
puff->tics -= P_Random(puff) & 3;
if (puff->tics < 1)
puff->tics = 1;
// don't make punches spark on the wall
if (attackrange == MELEERANGE)
P_SetMobjState(puff, S_PUFF3);
if (serverside)
{
// [SL] 2012-10-02 - Allow a client to predict their own bullet puffs
// so don't send the puffs to the client already predicting
if (shootthing && shootthing->player && shootthing->player->userinfo.predict_weapons)
puff->players_aware.set(shootthing->player->id);
SV_SpawnMobj(puff);
}
}
//
// P_SpawnTracerPuff
//
// Does not pay any attention to shootthing, because revenants do not
// set that pointer, thus any decision-making based on that pointer will
// be complete nonsense.
//
void P_SpawnTracerPuff(fixed_t x, fixed_t y, fixed_t z)
{
if (!serverside)
return;
AActor *puff;
z += (P_RandomDiff() << 10);
puff = new AActor(x, y, z, MT_PUFF);
puff->momz = FRACUNIT;
puff->tics -= P_Random(puff) & 3;
if (puff->tics < 1)
puff->tics = 1;
// don't make punches spark on the wall
if (attackrange == MELEERANGE)
P_SetMobjState(puff, S_PUFF3);
if (serverside)
SV_SpawnMobj(puff);
}
//
// P_SpawnBlood
//
void P_SpawnBlood (fixed_t x, fixed_t y, fixed_t z, int damage)
{
// denis - not clientside
if(!serverside)
return;
AActor *th;
z += P_RandomDiff () << 10;
th = new AActor (x, y, z, MT_BLOOD);
th->momz = FRACUNIT*2;
th->tics -= P_Random (th) & 3;
if (th->tics < 1)
th->tics = 1;
if (damage <= 12 && damage >= 9)
P_SetMobjState (th, S_BLOOD2);
else if (damage < 9)
P_SetMobjState (th, S_BLOOD3);
if (serverside)
SV_SpawnMobj(th);
}
//
// P_HitFloor
//
bool P_HitFloor (AActor *thing)
{
return true;
}
bool SV_AwarenessUpdate(player_t &pl, AActor* mo);
//
// P_CheckMissileSpawn
// Moves the missile forward a bit
// and possibly explodes it right there.
//
bool P_CheckMissileSpawn (AActor* th)
{
if (!th)
return false;
th->tics -= P_Random (th) & 3;
if (th->tics < 1)
th->tics = 1;
// move a little forward so an angle can
// be computed if it immediately explodes
th->x += th->momx>>1;
th->y += th->momy>>1;
th->z += th->momz>>1;
// killough 3/15/98: no dropoff (really = don't care for missiles)
// [AM] Set the baseline before we send it to clients.
P_SetMobjBaseline(*th);
// [SL] 2011-06-02 - If a missile explodes immediatley upon firing,
// make sure we spawn the missile first, send it to all clients immediately
// instead of queueing it, then explode it.
for (auto& player : players)
{
SV_AwarenessUpdate(player, th);
}
if (!P_TryMove (th, th->x, th->y, false))
{
P_ExplodeMissile (th);
return false;
}
return true;
}
// Returns 1 if 'source' needs to turn clockwise, or 0 if 'source' needs
// to turn counter clockwise. 'delta' is set to the amount 'source'
// needs to turn.
int P_FaceMobj(AActor* source, AActor* target, angle_t* delta)
{
angle_t diff;
angle_t angle1;
angle_t angle2;
angle1 = source->angle;
angle2 = R_PointToAngle2(source->x, source->y, target->x, target->y);
if (angle2 > angle1)
{
diff = angle2 - angle1;
if (diff > ANG180)
{
*delta = ANG360 - diff;
return (0);
}
else
{
*delta = diff;
return (1);
}
}
else
{
diff = angle1 - angle2;
if (diff > ANG180)
{
*delta = ANG360 - diff;
return (1);
}
else
{
*delta = diff;
return (0);
}
}
}
bool P_SeekerMissile(AActor* actor, AActor* seekTarget, angle_t thresh, angle_t turnMax, bool seekcenter)
{
int dir;
int dist;
angle_t delta;
angle_t angle;
AActor* target;
target = seekTarget;
if (target == NULL)
{
return (false);
}
if (!(target->flags & MF_SHOOTABLE))
{ // Target died
seekTarget = NULL;
return (false);
}
dir = P_FaceMobj(actor, target, &delta);
if (delta > thresh)
{
delta >>= 1;
if (delta > turnMax)
{
delta = turnMax;
}
}
if (dir)
{ // Turn clockwise
actor->angle += delta;
}
else
{ // Turn counter clockwise
actor->angle -= delta;
}
angle = actor->angle >> ANGLETOFINESHIFT;
actor->momx = FixedMul(actor->info->speed, finecosine[angle]);
actor->momy = FixedMul(actor->info->speed, finesine[angle]);
if (actor->z + actor->height < target->z || target->z + target->height < actor->z ||
seekcenter)
{ // Need to seek vertically
dist = P_AproxDistance(target->x - actor->x, target->y - actor->y);
dist = dist / actor->info->speed;
if (dist < 1)
{
dist = 1;
}
actor->momz = (target->z + (seekcenter ? target->height / 2 : 0) - actor->z) / dist;
}
return (true);
}
//
// P_SpawnMissile
//
AActor* P_SpawnMissile (AActor *source, AActor *dest, mobjtype_t type)
{
AActor *th;
angle_t an;
int dist;
fixed_t dest_x, dest_y, dest_z, dest_flags;
// denis: missile spawn code from chocolate doom
//
// fraggle: This prevents against *crashes* when dest == NULL.
// For example, when loading a game saved when a mancubus was
// in the middle of firing, mancubus->target == NULL. SpawnMissile
// then gets called with dest == NULL.
//
// However, this is not the *correct* behavior. At the moment,
// the missile is aimed at 0,0,0. In reality, monsters seem to aim
// somewhere else.
if (dest)
{
dest_x = dest->x;
dest_y = dest->y;
dest_z = dest->z;
dest_flags = dest->flags;
}
else
{
dest_x = 0;
dest_y = 0;
dest_z = 0;
dest_flags = 0;
}
th = new AActor (source->x, source->y, source->z + 4*8*FRACUNIT, type);
if (th->info->seesound)
S_Sound (th, CHAN_VOICE, th->info->seesound, 1, ATTN_NORM);
th->target = source->ptr(); // where it came from
an = P_PointToAngle (source->x, source->y, dest_x, dest_y);
// Horde boss? Make their projectiles look bossy
if (source->oflags & MFO_BOSSPOOL)
{
th->oflags |= MFO_FULLBRIGHT;
th->effects |= FX_YELLOWFOUNTAIN;
th->translation = translationref_t(&bosstable[0]);
}
else if (source->flags & MF_FRIEND)
{
th->translation = translationref_t(&friendtable[0]);
}
// fuzzy player
if (dest_flags & MF_SHADOW)
an += P_RandomDiff(th)<<20;
th->angle = an;
an >>= ANGLETOFINESHIFT;
th->momx = FixedMul(th->info->speed, finecosine[an]);
th->momy = FixedMul(th->info->speed, finesine[an]);
dist = P_AproxDistance (dest_x - source->x, dest_y - source->y);
dist = dist / th->info->speed;
if (dist < 1)
dist = 1;
th->momz = (dest_z - source->z) / dist;
P_CheckMissileSpawn (th);
return th;
}
//
// P_SpawnPlayerMissile
// Tries to aim at a nearby monster
//
AActor* P_SpawnPlayerMissile (AActor *source, mobjtype_t type)
{
AActor* th = AActor::AActorPtr();
if(!serverside)
return th;
fixed_t slope;
fixed_t pitchslope = finetangent[FINEANGLES/4 - (source->pitch>>ANGLETOFINESHIFT)];
// see which target is to be aimed at
angle_t an = source->angle;
// [AM] Refactored autoaim into a single function.
if (co_fineautoaim)
slope = P_AutoAimLineAttack(source, an, 1 << 26, 10, 16 * 64 * FRACUNIT);
else
slope = P_AutoAimLineAttack(source, an, 1 << 26, 1, 16 * 64 * FRACUNIT);
if (!linetarget)
an = source->angle;
// If a target was not found, or one was found, but outside the
// player's autoaim range, use the actor's pitch for the slope.
if (sv_freelook &&
(!linetarget || // target not found, or:
(source->player && // target found but outside of player's autoaim range
abs(slope - pitchslope) >= source->player->userinfo.aimdist)))
{
an = source->angle;
slope = pitchslope;
}
th = new AActor (source->x, source->y, source->z + 4*8*FRACUNIT, type);
if (th->info->seesound)
S_Sound (th, CHAN_VOICE, th->info->seesound, 1, ATTN_NORM);
th->target = source->ptr();
th->angle = an;
if (co_zdoomphys)
{
v3float_t velocity;
float speed = FIXED2FLOAT(th->info->speed);
velocity.x = FIXED2FLOAT (finecosine[an>>ANGLETOFINESHIFT]);
velocity.y = FIXED2FLOAT (finesine[an>>ANGLETOFINESHIFT]);
velocity.z = FIXED2FLOAT (slope);
M_NormalizeVec3f(&velocity, &velocity);
th->momx = FLOAT2FIXED (velocity.x * speed);
th->momy = FLOAT2FIXED (velocity.y * speed);
th->momz = FLOAT2FIXED (velocity.z * speed);
}
else
{
th->momx = FixedMul(th->info->speed, finecosine[an >> ANGLETOFINESHIFT]);
th->momy = FixedMul(th->info->speed, finesine[an >> ANGLETOFINESHIFT]);
th->momz = FixedMul(th->info->speed, slope);
}
P_CheckMissileSpawn (th);
return th;
}
//
// P_SpawnPlayerMissile
// Tries to aim at a nearby monster
//
void P_SpawnMBF21PlayerMissile(AActor* source, mobjtype_t type, fixed_t angle, fixed_t pitch, fixed_t xyofs, fixed_t zofs)
{
if (!serverside)
return;
fixed_t slope;
fixed_t pitchslope =
finetangent[FINEANGLES / 4 - (source->pitch >> ANGLETOFINESHIFT)];
// see which target is to be aimed at
angle_t an = source->angle;
// [AM] Refactored autoaim into a single function.
if (co_fineautoaim)
slope = P_AutoAimLineAttack(source, an, 1 << 26, 10, 16 * 64 * FRACUNIT);
else
slope = P_AutoAimLineAttack(source, an, 1 << 26, 1, 16 * 64 * FRACUNIT);
if (!linetarget)
an = source->angle;
// If a target was not found, or one was found, but outside the
// player's autoaim range, use the actor's pitch for the slope.
// [Blair] Also add MBF21 pitch to the projectile.
if (sv_freelook &&
(!linetarget || // target not found, or:
(source->player && // target found but outside of player's autoaim range
abs(slope - pitchslope) >= source->player->userinfo.aimdist)))
{
an = source->angle;
slope = pitchslope + DegToSlope(pitch);
}
AActor* th = new AActor(source->x, source->y, source->z + 4 * 8 * FRACUNIT, type);
if (th->info->seesound)
S_Sound(th, CHAN_VOICE, th->info->seesound, 1, ATTN_NORM);
th->target = source->ptr();
an += (angle_t)(((int64_t)angle << 16) / 360);
th->angle = an;
if (co_zdoomphys)
{
v3float_t velocity;
float speed = FIXED2FLOAT(th->info->speed);
velocity.x = FIXED2FLOAT(finecosine[an >> ANGLETOFINESHIFT]);
velocity.y = FIXED2FLOAT(finesine[an >> ANGLETOFINESHIFT]);
velocity.z = FIXED2FLOAT(slope);
M_NormalizeVec3f(&velocity, &velocity);
th->momx = FLOAT2FIXED(velocity.x * speed);
th->momy = FLOAT2FIXED(velocity.y * speed);
th->momz = FLOAT2FIXED(velocity.z * speed);
}
else
{
th->momx = FixedMul(th->info->speed, finecosine[an >> ANGLETOFINESHIFT]);
th->momy = FixedMul(th->info->speed, finesine[an >> ANGLETOFINESHIFT]);
th->momz = FixedMul(th->info->speed, slope);
}
an = (th->angle - ANG90) >> ANGLETOFINESHIFT;
// Adjust based on MBF21 params.
th->x += FixedMul(xyofs, finecosine[an]);
th->y += FixedMul(xyofs, finesine[an]);
th->z += zofs;
// [Blair] Set a tracer for player tracer weapons.
// This allows tracer projectiles fired from players to seek what
// was autoaim'd at.
// Piggybacks off linetarget to avoid autotargeting friendlies.
if (linetarget)
th->tracer = linetarget->ptr();
SV_UpdateMobjState(th);
P_CheckMissileSpawn(th);
}
//
// P_RespawnSpecials
//
void P_RespawnSpecials (void)
{
// clients do no control respawning of items
if(!serverside)
return;
// allow respawning if we specified it
if (!sv_itemsrespawn)
return;
// nothing left to respawn?
if (itemrespawnque.empty())
return;
const auto& [mthing, respawntime] = itemrespawnque.front();
// wait a certain number of seconds before respawning this special
if (level.time - respawntime < sv_itemrespawntime * TICRATE)
return;
const fixed_t x = mthing.x << FRACBITS;
const fixed_t y = mthing.y << FRACBITS;
// find which type to spawn
auto it = spawn_map.find(mthing.type);
if (it == spawn_map.end() ||
// Allow or not Partial Invisibility & Invulnerability from respawning
(!sv_respawnsuper && (mthing.type == 2022 || mthing.type == 2024)) ||
// pop barrels as well if needed
(!sv_respawnbarrels && mthing.type == 2035))
{
// pull it from the queue
itemrespawnque.pop();
return;
}
const fixed_t z = it->second->flags & MF_SPAWNCEILING ? ONCEILINGZ : ONFLOORZ;
// spawn a teleport fog at the new spot
AActor* mo = new AActor (x, y, z, MT_IFOG);
SV_SpawnMobj(mo);
if (clientside)
S_Sound (mo, CHAN_VOICE, "misc/spawn", 1, ATTN_IDLE);
// spawn it
mo = new AActor (x, y, z, it->second->type);
mo->spawnpoint = mthing;
mo->angle = ANG45 * (mthing.angle / 45);
if (z == ONFLOORZ)
mo->z += mthing.z << FRACBITS;
else if (z == ONCEILINGZ)
mo->z -= mthing.z << FRACBITS;
if (mo->flags2 & MF2_FLOATBOB)
{ // Seed random starting index for bobbing motion
mo->health = M_Random();
mo->special1 = mthing.z << FRACBITS;
}
mo->special = 0;
// pull it from the que
itemrespawnque.pop();
SV_SpawnMobj(mo);
M_LogWDLItemRespawnEvent(mo);
}
//
// P_ExplodeMissile
//
void P_ExplodeMissile (AActor* mo)
{
if (mo->target && mo->target->player)
{
// [Blair] We use means of death for WDL accuracy logs.
int mod;
switch (mo->type)
{
case MT_ROCKET:
mod = MOD_ROCKET;
break;
case MT_PLASMA:
mod = MOD_PLASMARIFLE;
break;
case MT_BFG:
mod = MOD_BFG_BOOM;
break;
// Blair: Unknown player projectiles get an unknown mod
default:
mod = MOD_UNKNOWN;
break;
}
M_LogWDLEvent(WDL_EVENT_PROJACCURACY, mo->target->player, NULL,
mo->target->player->mo->angle / 4, mod, 0, GetMaxShotsForMod(mod));
}
SV_ExplodeMissile(mo);
mo->momx = mo->momy = mo->momz = 0;
P_SetMobjState (mo, mobjinfo[mo->type].deathstate);
if (mobjinfo[mo->type].deathstate != S_NULL)
{
// [RH] If the object is already translucent, don't change it.
// Otherwise, make it 66% translucent.
//if (mo->translucency == FRACUNIT)
// mo->translucency = TRANSLUC66;
mo->translucency = FRACUNIT;
mo->tics -= P_Random(mo) & 3;
if (mo->tics < 1)
mo->tics = 1;
mo->flags &= ~MF_MISSILE;
if (mo->info->deathsound)
S_Sound (mo, CHAN_VOICE, mo->info->deathsound, 1, ATTN_NORM);
mo->effects = 0; // [RH]
}
}
//
// P_ThrustMobj
//
void P_ThrustMobj (AActor *mo, angle_t angle, fixed_t move)
{
angle >>= ANGLETOFINESHIFT;
mo->momx += FixedMul (move, finecosine[angle]);
mo->momy += FixedMul (move, finesine[angle]);
}
//
// P_GetMapThingPlayerNumber
//
// Returns the player number for a coop player start mapthing
//
size_t P_GetMapThingPlayerNumber(const mapthing2_t& mthing)
{
return mthing.type <= 4 ?
mthing.type - 1 :
(mthing.type - 4001 + 4) % MAXPLAYERSTARTS;
}
int P_IsPickupableThing(short type)
{
return (type == 82 // SSG
|| (type >= 2000 && type <= 2050) // weapons, ammo, health, armor, special items
|| type == 17 // cell pack
|| type == 83 // megasphere
|| type == 8 // backpack
);
}
//
// P_SpawnMapThing
// The fields of the mapthing should
// already be in host byte order.
//
// [RH] position is used to weed out unwanted start spots
//
void P_SpawnMapThing (mapthing2_t& mthing, int position)
{
int32_t type = -1;
if (mthing.type == 0 || mthing.type == -1)
return;
if (sv_allowshowspawns)
P_ShowSpawns(mthing);
const bool isTeleportDest = mthing.type == 14;
const bool isSecAct = (mthing.type >= 9982 && mthing.type <= 9983) ||
(mthing.type >= 9992 && mthing.type <= 9999);
const bool isSoundSource = (mthing.type >= 14001 && mthing.type <= 14065);
const bool isMusicChanger = (mthing.type >= 14100 && mthing.type <= 14165);
// only servers control spawning of items
// EXCEPT the client must spawn Type 14 (teleport exit).
// otherwise teleporters won't work well.
// Also spawn sector special things, fixes some other teleport issues.
if (!serverside && !(isTeleportDest || isSecAct || isSoundSource || isMusicChanger))
{
return;
}
//
// Clients also exclusively handle spawning of ambient sounds and music changers
//
if (!clientside && (isSoundSource || isMusicChanger))
{
return;
}
// count deathmatch start positions
if (mthing.type == 11 || (!sv_teamspawns && mthing.type >= 5080 && mthing.type <= 5082))
{
// [Nes] Maximum vanilla demo starts are fixed at 10.
if (DeathMatchStarts.size() >= 10 && demoplayback)
return;
M_LogWDLPlayerSpawn(mthing);
DeathMatchStarts.push_back(mthing);
return;
}
if (sv_teamspawns)
{
for (int iTeam = 0; iTeam < NUMTEAMS; iTeam++)
{
TeamInfo* teamInfo = GetTeamInfo((team_t)iTeam);
if (mthing.type == teamInfo->TeamSpawnThingNum)
{
teamInfo->Starts.push_back(mthing);
M_LogWDLPlayerSpawn(mthing);
return;
}
}
}
// [RH] Record polyobject-related things
if (HexenHack)
{
switch (mthing.type)
{
case PO_HEX_ANCHOR_TYPE:
mthing.type = PO_ANCHOR_TYPE;
break;
case PO_HEX_SPAWN_TYPE:
mthing.type = PO_SPAWN_TYPE;
break;
case PO_HEX_SPAWNCRUSH_TYPE:
mthing.type = PO_SPAWNCRUSH_TYPE;
break;
}
}
if (mthing.type == PO_ANCHOR_TYPE ||
mthing.type == PO_SPAWN_TYPE ||
mthing.type == PO_SPAWNCRUSH_TYPE)
{
polyspawns_t *polyspawn = new polyspawns_t;
polyspawn->next = polyspawns;
polyspawn->x = mthing.x << FRACBITS;
polyspawn->y = mthing.y << FRACBITS;
polyspawn->angle = mthing.angle;
polyspawn->type = mthing.type;
polyspawns = polyspawn;
if (mthing.type != PO_ANCHOR_TYPE)
po_NumPolyobjs++;
return;
}
// check for players specially
if ((mthing.type <= 4 && mthing.type > 0)
|| (mthing.type >= 4001 && mthing.type <= 4001 + MAXPLAYERSTARTS - 4))
{
// [RH] Only spawn spots that match position.
if (mthing.args[0] != position)
return;
if ((G_IsCoopGame() || G_UsesCoopSpawns()) && !G_IsHordeMode())
M_LogWDLPlayerSpawn(mthing);
size_t playernum = P_GetMapThingPlayerNumber(mthing);
// search for spots that already are for this player number
for (size_t i = 0; i < playerstarts.size(); i++)
{
const size_t otherplayernum = P_GetMapThingPlayerNumber(playerstarts[i]);
if (otherplayernum == playernum)
{
// consider playerstarts[i] to be a voodoo doll start
M_RemoveWDLPlayerSpawn(playerstarts[i]);
voodoostarts.push_back(playerstarts[i]);
playerstarts.erase(playerstarts.begin() + i);
break;
}
}
// save spots for respawning in network games
playerstarts.push_back(mthing);
player_t &p = idplayer(playernum+1);
if (clientside && G_IsCoopGame() && (validplayer(p) && p.ingame()))
{
P_SpawnPlayer(p, mthing);
return;
}
return;
}
// Filter mapthings based on the gamemode
if (!multiplayer && g_thingfilter != -1 && !G_GetCurrentSkill().spawn_multi)
{
if (!(mthing.flags & MTF_SINGLE))
return;
}
else if (sv_gametype == GM_DM || sv_gametype == GM_TEAMDM)
{
if (!(mthing.flags & MTF_DEATHMATCH))
return;
}
else if (G_IsCoopGame())
{
if (!(mthing.flags & MTF_COOPERATIVE))
return;
}
if (g_thingfilter == 3 && P_IsPickupableThing(mthing.type))
return;
// check for appropriate skill level
if (!(mthing.flags & G_GetCurrentSkill().spawn_filter))
return;
// [RH] sound sequence overrides
if (mthing.type >= 1400 && mthing.type < 1410)
{
subsector_t* sub = P_PointInSubsector(mthing.x << FRACBITS, mthing.y << FRACBITS);
sub->sector->seqType = mthing.type - 1400;
return;
}
else if (mthing.type == 1411)
{
int seqtype;
if (mthing.args[0] == 255)
seqtype = -1;
else
seqtype = mthing.args[0];
if (seqtype > 63)
{
PrintFmt(PRINT_WARNING, "Sound sequence {} out of range\n", seqtype);
}
else
{
subsector_t* sub = P_PointInSubsector(mthing.x << FRACBITS, mthing.y << FRACBITS);
sub->sector->seqType = seqtype;
}
return;
}
if (P_IsHordeThing(mthing.type))
{
type = MT_HORDESPAWN;
::level.detected_gametype = GM_HORDE;
}
if (mthing.type == 9081)
{
type = MT_SKYPICKER;
}
else if (mthing.type == 9080)
{
type = MT_SKYVIEWPOINT;
}
else if (mthing.type == 9082)
{
type = MT_SECTORSILENCER;
}
// [RH] Determine if it is an old ambient thing, and if so,
// map it to MT_AMBIENT with the proper parameter.
if (mthing.type >= 14001 && mthing.type <= 14064)
{
mthing.args[0] = mthing.type - 14000;
mthing.type = mobjinfo[MT_AMBIENT].doomednum;
type = MT_AMBIENT;
}
// [ML] Determine if it is a musicchanger thing, and if so,
// map it to MT_MUSICSOURCE with the proper parameter.
if (mthing.type >= 14100 && mthing.type <= 14164)
{
mthing.args[0] = mthing.type - 14100;
mthing.type = mobjinfo[MT_MUSICSOURCE].doomednum;
type = MT_MUSICSOURCE;
}
// [CMB] find the value in the mobjinfo table if we asked for a specific type; otherwise check the spawn table
mobjinfo_t* info = nullptr;
if (type == -1)
{
auto spawn_it = spawn_map.find(mthing.type);
if (spawn_it != spawn_map.end())
{
info = spawn_it->second;
// set this for further down
type = info->type;
}
// [RH] Check if it's a particle fountain
if (type == -1 && mthing.type >= 9027 && mthing.type <= 9033)
{
mthing.args[0] = mthing.type - 9026;
type = MT_FOUNTAIN;
info = &mobjinfo[type]; // mt_fountain guaranteed to exist
}
}
else
{
auto mobj_it = mobjinfo.find(type);
if (mobj_it != mobjinfo.end())
{
info = &mobj_it->second;
// set this for further down
type = info->type;
}
}
if (info == nullptr)
{
// [RH] Don't die if the map tries to spawn an unknown thing
PrintFmt(PRINT_WARNING, "P_SpawnMapThing: Unknown type {} at {}, {})\n",
mthing.type,
mthing.x, mthing.y);
info = &mobjinfo[MT_UNKNOWNTHING]; // [CMB] odamex specific MT_UNKNOWNTHING
type = MT_UNKNOWNTHING;
}
// [RH] If the thing's corresponding sprite has no frames, also map
// it to the unknown thing.
else if (sprites[states[info->spawnstate].sprite].numframes == 0)
{
PrintFmt(PRINT_WARNING, "P_SpawnMapThing: Type {} at {}, {} has no frames\n",
mthing.type, mthing.x, mthing.y);
info = &mobjinfo[MT_UNKNOWNTHING];
type = MT_UNKNOWNTHING;
}
// don't spawn keycards and players in deathmatch
if (!G_IsCoopGame() && info->flags & MF_NOTDMATCH)
return;
// don't spawn deathmatch weapons in offline single player mode
{
switch (type)
{
case MT_CHAINGUN:
case MT_SHOTGUN:
case MT_SUPERSHOTGUN:
case MT_MISC25: // BFG
case MT_MISC26: // chainsaw
case MT_MISC27: // rocket launcher
case MT_MISC28: // plasma gun
if (!multiplayer && g_thingfilter != -1 && !G_GetCurrentSkill().spawn_multi)
{
if ((mthing.flags & (MTF_DEATHMATCH | MTF_SINGLE)) == MTF_DEATHMATCH)
return;
}
else
{
if ((mthing.flags & (MTF_FILTER_COOPWPN)))
return;
}
break;
default:
break;
}
}
// [csDoom] don't spawn any monsters
if (sv_nomonsters || !serverside)
{
if (type == MT_SKULL || (info->flags & MF_COUNTKILL) )
{
return;
}
}
// [SL] 2011-05-31 - Moved so that clients get right level.total_items, etc
if (type == MT_SECRETTRIGGER)
level.total_secrets++;
if (info->flags & MF_COUNTKILL)
level.total_monsters++;
if (info->flags & MF_COUNTITEM)
level.total_items++;
// spawn it
const fixed_t x = mthing.x << FRACBITS;
const fixed_t y = mthing.y << FRACBITS;
const fixed_t z = (info->flags & MF_SPAWNCEILING) ? ONCEILINGZ : ONFLOORZ;
if (type == MT_WATERZONE)
{
sector_t *sec = P_PointInSubsector (x, y)->sector;
sec->waterzone = 1;
return;
}
AActor* mobj = new AActor(x, y, z, info->type);
if (type == MT_HORDESPAWN)
{
// Store the spawn type for later.
// [CMB] specific types must be checked; otherwise they won't spawn correctly
mobj->special1 = mthing.type;
if (mthing.type == 5301) // Supply cache (5301)
M_LogWDLItemSpawn(*mobj, WDL_PICKUP_CAREPACKAGE);
else if (mthing.type == 5307) // Horde Powerup (5307)
M_LogWDLItemSpawn(*mobj, WDL_PICKUP_POWERUPSPAWNER);
}
if (z == ONFLOORZ)
mobj->z += mthing.z << FRACBITS;
else if (z == ONCEILINGZ)
mobj->z -= mthing.z << FRACBITS;
mobj->spawnpoint = mthing;
if (mobj->flags2 & MF2_FLOATBOB)
{ // Seed random starting index for bobbing motion
mobj->health = M_Random();
mobj->special1 = mthing.z << FRACBITS;
}
// [RH] Set the thing's special
mobj->special = mthing.special;
memcpy (mobj->args, mthing.args, sizeof(mobj->args));
// [RH] If it's an ambient sound, activate it
if (type == MT_AMBIENT)
S_ActivateAmbient (mobj, mobj->args[0]);
// [RH] If a fountain and not dormant, start it
if (type == MT_FOUNTAIN && !(mthing.flags & MTF_DORMANT))
mobj->effects = mobj->args[0] << FX_FOUNTAINSHIFT;
// [SL] ZDoom Custom Bridge Things
if (type == MT_ZDOOMBRIDGE)
{
mobj->radius = mobj->args[0] << FRACBITS;
mobj->height = mobj->args[1] << FRACBITS;
}
// [AM] Adjust monster health based on server setting
if ((type == MT_SKULL || (info->flags & MF_COUNTKILL)) && sv_monstershealth != 1.0f)
mobj->health *= sv_monstershealth;
if (mobj->tics > 0)
mobj->tics = 1 + (P_Random(mobj) % mobj->tics);
if (type != MT_SPARK)
mobj->angle = ANG45 * (mthing.angle/45);
if (mthing.flags & MTF_AMBUSH)
mobj->flags |= MF_AMBUSH;
if (mthing.flags & MTF_FRIENDLY)
mobj->flags |= MF_FRIEND;
// [RH] Add ThingID to mobj and link it in with the others
mobj->tid = mthing.thingid;
mobj->AddToHash ();
SV_SpawnMobj(mobj);
if (mobj->type == MT_SKYVIEWPOINT)
{
mobj->angle = mthing.angle;
// If this actor has no TID, make it the default sky box
if (mobj->tid == 0)
{
int j;
for (j = 0; j < numsectors; j++)
{
if (sectors[j].Skybox == NULL)
{
sectors[j].Skybox = mobj->ptr();
}
}
}
}
if (mobj->type == MT_SKYPICKER)
{
sector_t* sector = mobj->subsector->sector;
if (mthing.args[0] == 0)
{
sector->Skybox = AActor::AActorPtr();
}
else
{
TActorIterator<AActor> iterator (mthing.args[0]);
AActor* box = iterator.Next();
if (box != NULL && box->type == MT_SKYVIEWPOINT)
{
sector->Skybox = box->ptr();
}
else
{
PrintFmt ("Can't find SkyViewpoint {} for sector {}\n", mthing.args[0],
sector - sectors);
}
}
mobj->Destroy ();
}
if ((mthing.type >= 9992 && mthing.type <= 9999) ||
(mthing.type >= 9982 && mthing.type <= 9983)) {
// Add ourselves to this sector's list of actions.
if (mobj->subsector->sector->SecActTarget != NULL) {
mobj->tracer = mobj->subsector->sector->SecActTarget->ptr();
}
mobj->subsector->sector->SecActTarget = mobj->ptr();
}
if (sv_gametype == GM_CTF)
{
for (int iTeam = 0; iTeam < sv_teamsinplay; iTeam++)
{
TeamInfo* teamInfo = GetTeamInfo((team_t)iTeam);
if (mthing.type == teamInfo->FlagThingNum)
{
SpawnFlag(mthing, teamInfo->Team);
M_LogWDLFlagLocation(mthing, teamInfo->Team);
break;
}
}
}
// [RH] Go dormant as needed
if (mthing.flags & MTF_DORMANT)
P_DeactivateMobj(mobj);
// [Blair] This looks like an item we'd want to log.
// Check it and log it if so.
WDLPowerups typetocheck = M_GetWDLItemByMobjType(static_cast<mobjtype_t>(mobj->type));
if (typetocheck != WDL_PICKUP_UNKNOWN)
{
M_LogWDLItemSpawn(*mobj, typetocheck);
}
}
/**
* @brief Spawn all avatars. Must have spawned other items first.
*
* @detail An avatar is an mobj that attempts to take the place of a voodoo
* doll in multiplayer. Instead of pretending to be any player in
* particular, it is a mere mobj, but with special handling in order
* to make any effects that would normally target a player target
* all ingame players instead.
*/
void P_SpawnAvatars()
{
if (clientside || !G_IsCoopGame())
{
// Voodoo dolls are handled in local games.
return;
}
for (const auto& thing : ::voodoostarts)
{
new AActor(thing.x << FRACBITS, thing.y << FRACBITS, ONFLOORZ, MT_AVATAR);
}
}
void SpawnFlag(const mapthing2_t& mthing, team_t flag)
{
if (GetTeamInfo(flag)->FlagData.flaglocated)
return;
CTF_RememberFlagPos(mthing);
CTF_SpawnFlag(flag);
}
//
// P_VisibleToPlayers
//
// Returns true if mo is currently in any player's field of view
//
bool P_VisibleToPlayers(const AActor *mo)
{
if (!mo)
return false;
for (const auto& player : players)
{
// players aren't considered visible to themselves
if (mo->player && mo->player->id == player.id)
continue;
if (!(player.mo) || player.spectator)
continue;
if (P_CheckSightEdges(player.mo, mo, 5.0))
return true;
}
return false;
}
/**
* @brief Bake the baseline into the actor before sending it to clients.
*/
void P_SetMobjBaseline(AActor& mo)
{
if (mo.baseline_set)
return;
mo.baseline.pos.x = mo.x;
mo.baseline.pos.y = mo.y;
mo.baseline.pos.z = mo.z;
mo.baseline.mom.x = mo.momx;
mo.baseline.mom.y = mo.momy;
mo.baseline.mom.z = mo.momz;
mo.baseline.angle = mo.angle;
mo.baseline.targetid = mo.target ? mo.target->netid : 0;
mo.baseline.tracerid = mo.tracer ? mo.tracer->netid : 0;
mo.baseline.movecount = mo.movecount;
mo.baseline.movedir = mo.movedir;
mo.baseline.rndindex = mo.rndindex;
mo.baseline_set = true;
}
/**
* @brief Generate flags that lists which fields are different
*/
uint32_t P_GetMobjBaselineFlags(const AActor& mo)
{
uint32_t flags = 0;
if (mo.baseline.pos.x != mo.x)
{
flags |= baseline_t::POSX;
}
if (mo.baseline.pos.y != mo.y)
{
flags |= baseline_t::POSY;
}
if (mo.baseline.pos.z != mo.z)
{
flags |= baseline_t::POSZ;
}
if (mo.baseline.angle != mo.angle)
{
flags |= baseline_t::ANGLE;
}
if (mo.baseline.movedir != mo.movedir)
{
flags |= baseline_t::MOVEDIR;
}
if (mo.baseline.movecount != mo.movecount)
{
flags |= baseline_t::MOVECOUNT;
}
if (mo.baseline.rndindex != mo.rndindex)
{
flags |= baseline_t::RNDINDEX;
}
if (mo.baseline.targetid != (mo.target ? mo.target->netid : 0))
{
flags |= baseline_t::TARGET;
}
if (mo.baseline.tracerid != (mo.tracer ? mo.tracer->netid : 0))
{
flags |= baseline_t::TRACER;
}
if (mo.baseline.mom.x != mo.momx)
{
flags |= baseline_t::MOMX;
}
if (mo.baseline.mom.y != mo.momy)
{
flags |= baseline_t::MOMY;
}
if (mo.baseline.mom.z != mo.momz)
{
flags |= baseline_t::MOMZ;
}
return flags;
}
BEGIN_COMMAND(cheat_mobjs)
{
if (argc < 2)
{
PrintFmt("Missing MT_* mobj type\n");
return;
}
const char* mobj_type = argv[1];
auto it = std::find_if(mobjinfo.begin(), mobjinfo.end(), [&](const auto& it){
return iequals(it.second.name, mobj_type);
});
if (it == mobjinfo.end())
{
PrintFmt("Unknown MT_* mobj type\n");
return;
}
const int32_t mobj_index = it->first;
PrintFmt("== {} ==", mobj_type);
AActor* mo;
TThinkerIterator<AActor> iterator;
while ((mo = iterator.Next()))
{
if (mo->type == mobj_index)
{
PrintFmt("ID: {}\n", mo->netid);
PrintFmt(" {:.1f}, {:.1f}, {:.1f}\n", FIXED2FLOAT(mo->x), FIXED2FLOAT(mo->y),
FIXED2FLOAT(mo->z));
}
}
}
END_COMMAND(cheat_mobjs)
VERSION_CONTROL (p_mobj_cpp, "$Id$")