in doom_py/src/vizdoom/src/p_mobj.cpp [3175:3780]
void AActor::Tick ()
{
// [RH] Data for Heretic/Hexen scrolling sectors
static const BYTE HexenScrollDirs[8] = { 64, 0, 192, 128, 96, 32, 224, 160 };
static const BYTE HexenSpeedMuls[3] = { 5, 10, 25 };
static const SBYTE HexenScrollies[24][2] =
{
{ 0, 1 }, { 0, 2 }, { 0, 4 },
{ -1, 0 }, { -2, 0 }, { -4, 0 },
{ 0, -1 }, { 0, -2 }, { 0, -4 },
{ 1, 0 }, { 2, 0 }, { 4, 0 },
{ 1, 1 }, { 2, 2 }, { 4, 4 },
{ -1, 1 }, { -2, 2 }, { -4, 4 },
{ -1, -1 }, { -2, -2 }, { -4, -4 },
{ 1, -1 }, { 2, -2 }, { 4, -4 }
};
static const BYTE HereticScrollDirs[4] = { 6, 9, 1, 4 };
static const BYTE HereticSpeedMuls[5] = { 5, 10, 25, 30, 35 };
AActor *onmo;
int i;
//assert (state != NULL);
if (state == NULL)
{
Printf("Actor of type %s at (%f,%f) left without a state\n", GetClass()->TypeName.GetChars(),
X()/65536., Y()/65536.);
Destroy();
return;
}
// This is necessary to properly interpolate movement outside this function
// like from an ActorMover
PrevX = X();
PrevY = Y();
PrevZ = Z();
PrevAngle = angle;
if (flags5 & MF5_NOINTERACTION)
{
// only do the minimally necessary things here to save time:
// Check the time freezer
// apply velocity
// ensure that the actor is not linked into the blockmap
if (!(flags5 & MF5_NOTIMEFREEZE))
{
//Added by MC: Freeze mode.
if (bglobal.freeze || level.flags2 & LEVEL2_FROZEN)
{
// Boss cubes shouldn't be accelerated by timefreeze
if (flags6 & MF6_BOSSCUBE)
{
special2++;
}
return;
}
}
UnlinkFromWorld ();
flags |= MF_NOBLOCKMAP;
SetXYZ(Vec3Offset(velx, vely, velz));
SetMovement(velx, vely, velz);
LinkToWorld ();
}
else
{
AInventory * item = Inventory;
// Handle powerup effects here so that the order is controlled
// by the order in the inventory, not the order in the thinker table
while (item != NULL && item->Owner == this)
{
item->DoEffect();
item = item->Inventory;
}
if (flags & MF_UNMORPHED)
{
return;
}
if (!(flags5 & MF5_NOTIMEFREEZE))
{
// Boss cubes shouldn't be accelerated by timefreeze
if (flags6 & MF6_BOSSCUBE)
{
special2++;
}
//Added by MC: Freeze mode.
if (bglobal.freeze && !(player && player->Bot == NULL))
{
return;
}
// Apply freeze mode.
if ((level.flags2 & LEVEL2_FROZEN) && (player == NULL || player->timefreezer == 0))
{
return;
}
}
if (effects & FX_ROCKET)
{
if (++smokecounter == 4)
{
// add some smoke behind the rocket
smokecounter = 0;
AActor *th = Spawn("RocketSmokeTrail", Vec3Offset(-velx, -vely, -velz), ALLOW_REPLACE);
if (th)
{
th->tics -= pr_rockettrail()&3;
if (th->tics < 1) th->tics = 1;
if (!(cl_rockettrails & 2)) th->renderflags |= RF_INVISIBLE;
}
}
}
else if (effects & FX_GRENADE)
{
if (++smokecounter == 8)
{
smokecounter = 0;
angle_t moveangle = R_PointToAngle2(0,0,velx,vely);
fixed_t xo = -FixedMul(finecosine[(moveangle) >> ANGLETOFINESHIFT], radius * 2) + (pr_rockettrail() << 10);
fixed_t yo = -FixedMul(finesine[(moveangle) >> ANGLETOFINESHIFT], radius * 2) + (pr_rockettrail() << 10);
AActor * th = Spawn("GrenadeSmokeTrail", Vec3Offset(xo, yo, - (height>>3) * (velz>>16) + (2*height)/3), ALLOW_REPLACE);
if (th)
{
th->tics -= pr_rockettrail()&3;
if (th->tics < 1) th->tics = 1;
if (!(cl_rockettrails & 2)) th->renderflags |= RF_INVISIBLE;
}
}
}
fixed_t oldz = Z();
// [RH] Give the pain elemental vertical friction
// This used to be in APainElemental::Tick but in order to use
// A_PainAttack with other monsters it has to be here
if (flags4 & MF4_VFRICTION)
{
if (health >0)
{
if (abs (velz) < FRACUNIT/4)
{
velz = 0;
flags4 &= ~MF4_VFRICTION;
}
else
{
velz = FixedMul (velz, 0xe800);
}
}
}
// [RH] Pulse in and out of visibility
if (effects & FX_VISIBILITYPULSE)
{
if (visdir > 0)
{
alpha += 0x800;
if (alpha >= OPAQUE)
{
alpha = OPAQUE;
visdir = -1;
}
}
else
{
alpha -= 0x800;
if (alpha <= TRANSLUC25)
{
alpha = TRANSLUC25;
visdir = 1;
}
}
}
else if (flags & MF_STEALTH)
{
// [RH] Fade a stealth monster in and out of visibility
RenderStyle.Flags &= ~STYLEF_Alpha1;
if (visdir > 0)
{
alpha += 2*FRACUNIT/TICRATE;
if (alpha > OPAQUE)
{
alpha = OPAQUE;
visdir = 0;
}
}
else if (visdir < 0)
{
alpha -= 3*FRACUNIT/TICRATE/2;
if (alpha < 0)
{
alpha = 0;
visdir = 0;
}
}
}
if (bglobal.botnum && !demoplayback &&
((flags & (MF_SPECIAL|MF_MISSILE)) || (flags3 & MF3_ISMONSTER)))
{
BotSupportCycles.Clock();
bglobal.m_Thinking = true;
for (i = 0; i < MAXPLAYERS; i++)
{
if (!playeringame[i] || players[i].Bot == NULL)
continue;
if (flags3 & MF3_ISMONSTER)
{
if (health > 0
&& !players[i].Bot->enemy
&& player ? !IsTeammate (players[i].mo) : true
&& AproxDistance (players[i].mo) < MAX_MONSTER_TARGET_DIST
&& P_CheckSight (players[i].mo, this, SF_SEEPASTBLOCKEVERYTHING))
{ //Probably a monster, so go kill it.
players[i].Bot->enemy = this;
}
}
else if (flags & MF_SPECIAL)
{ //Item pickup time
//clock (BotWTG);
players[i].Bot->WhatToGet (this);
//unclock (BotWTG);
BotWTG++;
}
else if (flags & MF_MISSILE)
{
if (!players[i].Bot->missile && (flags3 & MF3_WARNBOT))
{ //warn for incoming missiles.
if (target != players[i].mo && players[i].Bot->Check_LOS (this, ANGLE_90))
players[i].Bot->missile = this;
}
}
}
bglobal.m_Thinking = false;
BotSupportCycles.Unclock();
}
//End of MC
// [RH] Consider carrying sectors here
fixed_t cummx = 0, cummy = 0;
if ((level.Scrolls != NULL || player != NULL) && !(flags & MF_NOCLIP) && !(flags & MF_NOSECTOR))
{
fixed_t height, waterheight; // killough 4/4/98: add waterheight
const msecnode_t *node;
int countx, county;
// killough 3/7/98: Carry things on floor
// killough 3/20/98: use new sector list which reflects true members
// killough 3/27/98: fix carrier bug
// killough 4/4/98: Underwater, carry things even w/o gravity
// Move objects only if on floor or underwater,
// non-floating, and clipped.
countx = county = 0;
for (node = touching_sectorlist; node; node = node->m_tnext)
{
const sector_t *sec = node->m_sector;
fixed_t scrollx, scrolly;
if (level.Scrolls != NULL)
{
const FSectorScrollValues *scroll = &level.Scrolls[sec - sectors];
scrollx = scroll->ScrollX;
scrolly = scroll->ScrollY;
}
else
{
scrollx = scrolly = 0;
}
if (player != NULL)
{
int scrolltype = sec->special;
if (scrolltype >= Scroll_North_Slow &&
scrolltype <= Scroll_SouthWest_Fast)
{ // Hexen scroll special
scrolltype -= Scroll_North_Slow;
if (i_compatflags&COMPATF_RAVENSCROLL)
{
angle_t fineangle = HexenScrollDirs[scrolltype / 3] * 32;
fixed_t carryspeed = DivScale32 (HexenSpeedMuls[scrolltype % 3], 32*CARRYFACTOR);
scrollx += FixedMul (carryspeed, finecosine[fineangle]);
scrolly += FixedMul (carryspeed, finesine[fineangle]);
}
else
{
// Use speeds that actually match the scrolling textures!
scrollx -= HexenScrollies[scrolltype][0] << (FRACBITS-1);
scrolly += HexenScrollies[scrolltype][1] << (FRACBITS-1);
}
}
else if (scrolltype >= Carry_East5 &&
scrolltype <= Carry_West35)
{ // Heretic scroll special
scrolltype -= Carry_East5;
BYTE dir = HereticScrollDirs[scrolltype / 5];
fixed_t carryspeed = DivScale32 (HereticSpeedMuls[scrolltype % 5], 32*CARRYFACTOR);
if (scrolltype<=Carry_East35 && !(i_compatflags&COMPATF_RAVENSCROLL))
{
// Use speeds that actually match the scrolling textures!
carryspeed = (1 << ((scrolltype%5) + FRACBITS-1));
}
scrollx += carryspeed * ((dir & 3) - 1);
scrolly += carryspeed * (((dir & 12) >> 2) - 1);
}
else if (scrolltype == dScroll_EastLavaDamage)
{ // Special Heretic scroll special
if (i_compatflags&COMPATF_RAVENSCROLL)
{
scrollx += DivScale32 (28, 32*CARRYFACTOR);
}
else
{
// Use a speed that actually matches the scrolling texture!
scrollx += DivScale32 (12, 32*CARRYFACTOR);
}
}
else if (scrolltype == Scroll_StrifeCurrent)
{ // Strife scroll special
int anglespeed = tagManager.GetFirstSectorTag(sec) - 100;
fixed_t carryspeed = DivScale32 (anglespeed % 10, 16*CARRYFACTOR);
angle_t fineangle = (anglespeed / 10) << (32-3);
fineangle >>= ANGLETOFINESHIFT;
scrollx += FixedMul (carryspeed, finecosine[fineangle]);
scrolly += FixedMul (carryspeed, finesine[fineangle]);
}
}
if ((scrollx | scrolly) == 0)
{
continue;
}
sector_t *heightsec = sec->GetHeightSec();
if (flags & MF_NOGRAVITY && heightsec == NULL)
{
continue;
}
height = sec->floorplane.ZatPoint (this);
if (Z() > height)
{
if (heightsec == NULL)
{
continue;
}
waterheight = heightsec->floorplane.ZatPoint (this);
if (waterheight > height && Z() >= waterheight)
{
continue;
}
}
cummx += scrollx;
cummy += scrolly;
if (scrollx) countx++;
if (scrolly) county++;
}
// Some levels designed with Boom in mind actually want things to accelerate
// at neighboring scrolling sector boundaries. But it is only important for
// non-player objects.
if (player != NULL || !(i_compatflags & COMPATF_BOOMSCROLL))
{
if (countx > 1)
{
cummx /= countx;
}
if (county > 1)
{
cummy /= county;
}
}
}
// [RH] If standing on a steep slope, fall down it
if ((flags & MF_SOLID) && !(flags & (MF_NOCLIP|MF_NOGRAVITY)) &&
!(flags & MF_NOBLOCKMAP) &&
velz <= 0 &&
floorz == Z())
{
secplane_t floorplane;
// Check 3D floors as well
floorplane = P_FindFloorPlane(floorsector, X(), Y(), floorz);
if (floorplane.c < STEEPSLOPE &&
floorplane.ZatPoint (this) <= floorz)
{
const msecnode_t *node;
bool dopush = true;
if (floorplane.c > STEEPSLOPE*2/3)
{
for (node = touching_sectorlist; node; node = node->m_tnext)
{
const sector_t *sec = node->m_sector;
if (sec->floorplane.c >= STEEPSLOPE)
{
if (floorplane.ZatPoint (this) >= Z() - MaxStepHeight)
{
dopush = false;
break;
}
}
}
}
if (dopush)
{
velx += floorplane.a;
vely += floorplane.b;
}
}
}
// [RH] Missiles moving perfectly vertical need some X/Y movement, or they
// won't hurt anything. Don't do this if damage is 0! That way, you can
// still have missiles that go straight up and down through actors without
// damaging anything.
if ((flags & MF_MISSILE) && (velx|vely) == 0 && Damage != 0)
{
velx = 1;
}
// Handle X and Y velocities
BlockingMobj = NULL;
fixed_t oldfloorz = P_XYMovement (this, cummx, cummy);
if (ObjectFlags & OF_EuthanizeMe)
{ // actor was destroyed
return;
}
if ((velx | vely) == 0) // Actors at rest
{
if (flags2 & MF2_BLASTED)
{ // Reset to not blasted when velocities are gone
flags2 &= ~MF2_BLASTED;
}
if ((flags6 & MF6_TOUCHY) && !IsSentient())
{ // Arm a mine which has come to rest
flags6 |= MF6_ARMED;
}
}
if (velz || BlockingMobj || Z() != floorz)
{ // Handle Z velocity and gravity
if (((flags2 & MF2_PASSMOBJ) || (flags & MF_SPECIAL)) && !(i_compatflags & COMPATF_NO_PASSMOBJ))
{
if (!(onmo = P_CheckOnmobj (this)))
{
P_ZMovement (this, oldfloorz);
flags2 &= ~MF2_ONMOBJ;
}
else
{
if (player)
{
if (velz < (fixed_t)(level.gravity * Sector->gravity * -655.36f)
&& !(flags&MF_NOGRAVITY))
{
PlayerLandedOnThing (this, onmo);
}
}
if (onmo->Top() - Z() <= MaxStepHeight)
{
if (player && player->mo == this)
{
player->viewheight -= onmo->Top() - Z();
fixed_t deltaview = player->GetDeltaViewHeight();
if (deltaview > player->deltaviewheight)
{
player->deltaviewheight = deltaview;
}
}
SetZ(onmo->Top());
}
// Check for MF6_BUMPSPECIAL
// By default, only players can activate things by bumping into them
// We trigger specials as long as we are on top of it and not just when
// we land on it. This could be considered as gravity making us continually
// bump into it, but it also avoids having to worry about walking on to
// something without dropping and not triggering anything.
if ((onmo->flags6 & MF6_BUMPSPECIAL) && ((player != NULL)
|| ((onmo->activationtype & THINGSPEC_MonsterTrigger) && (flags3 & MF3_ISMONSTER))
|| ((onmo->activationtype & THINGSPEC_MissileTrigger) && (flags & MF_MISSILE))
) && (level.maptime > onmo->lastbump)) // Leave the bumper enough time to go away
{
if (player == NULL || !(player->cheats & CF_PREDICTING))
{
if (P_ActivateThingSpecial(onmo, this))
onmo->lastbump = level.maptime + TICRATE;
}
}
if (velz != 0 && (BounceFlags & BOUNCE_Actors))
{
P_BounceActor(this, onmo, true);
}
else
{
flags2 |= MF2_ONMOBJ;
velz = 0;
Crash();
}
}
}
else
{
P_ZMovement (this, oldfloorz);
}
if (ObjectFlags & OF_EuthanizeMe)
return; // actor was destroyed
}
else if (Z() <= floorz)
{
Crash();
}
UpdateWaterLevel (oldz);
// [RH] Don't advance if predicting a player
if (player && (player->cheats & CF_PREDICTING))
{
return;
}
// Check for poison damage, but only once per PoisonPeriod tics (or once per second if none).
if (PoisonDurationReceived && (level.time % (PoisonPeriodReceived ? PoisonPeriodReceived : TICRATE) == 0))
{
P_DamageMobj(this, NULL, Poisoner, PoisonDamageReceived, PoisonDamageTypeReceived ? PoisonDamageTypeReceived : (FName)NAME_Poison, 0);
--PoisonDurationReceived;
// Must clear damage when duration is done, otherwise it
// could be added to with ADDITIVEPOISONDAMAGE.
if (!PoisonDurationReceived) PoisonDamageReceived = 0;
}
}
assert (state != NULL);
if (state == NULL)
{
Destroy();
return;
}
if ((flags7 & MF7_HANDLENODELAY) && !(flags2 & MF2_DORMANT))
{
flags7 &= ~MF7_HANDLENODELAY;
if (state->GetNoDelay())
{
// For immediately spawned objects with the NoDelay flag set for their
// Spawn state, explicitly call the current state's function.
if (state->CallAction(this, this) && (ObjectFlags & OF_EuthanizeMe))
return; // freed itself
}
}
// cycle through states, calling action functions at transitions
if (tics != -1)
{
// [RH] Use tics <= 0 instead of == 0 so that spawnstates
// of 0 tics work as expected.
if (--tics <= 0)
{
if (!SetState(state->GetNextState()))
return; // freed itself
}
}
else
{
int respawn_monsters = G_SkillProperty(SKILLP_Respawn);
// check for nightmare respawn
if (!(flags5 & MF5_ALWAYSRESPAWN))
{
if (!respawn_monsters || !(flags3 & MF3_ISMONSTER) || (flags2 & MF2_DORMANT) || (flags5 & MF5_NEVERRESPAWN))
return;
int limit = G_SkillProperty (SKILLP_RespawnLimit);
if (limit > 0 && skillrespawncount >= limit)
return;
}
movecount++;
if (movecount < respawn_monsters)
return;
if (level.time & 31)
return;
if (pr_nightmarerespawn() > 4)
return;
P_NightmareRespawn (this);
}
}