mirror of
https://github.com/ZDoom/Raze.git
synced 2025-01-11 19:40:46 +00:00
5448f6ec2c
* DObjects may not be memset to 0. * There was still code trying to retrieve the player index with pointer artithmetic. With an array of pointers this does not work.
1638 lines
43 KiB
C++
1638 lines
43 KiB
C++
//-------------------------------------------------------------------------
|
|
/*
|
|
Copyright (C) 1997, 2005 - 3D Realms Entertainment
|
|
|
|
This file is part of Shadow Warrior version 1.2
|
|
|
|
Shadow Warrior 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.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
Original Source: 1997 - Frank Maddin and Jim Norwood
|
|
Prepared for public release: 03/28/2005 - Charlie Wiederhold, 3D Realms
|
|
*/
|
|
//-------------------------------------------------------------------------
|
|
#include "ns.h"
|
|
|
|
#include "build.h"
|
|
|
|
#include "names2.h"
|
|
#include "game.h"
|
|
#include "tags.h"
|
|
#include "sector.h"
|
|
#include "sprite.h"
|
|
|
|
// temp - should be moved
|
|
#include "ai.h"
|
|
|
|
#include "network.h"
|
|
|
|
BEGIN_SW_NS
|
|
|
|
bool DropAhead(DSWActor* actor, double min_height);
|
|
|
|
VMFunction* ChooseAction(DECISION decision[]);
|
|
|
|
|
|
#define CHOOSE2(value) (RANDOM_P2(1024) < (value))
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
bool ActorMoveHitReact(DSWActor* actor)
|
|
{
|
|
// Should only return true if there is a reaction to what was hit that
|
|
// would cause the calling function to abort
|
|
|
|
auto coll = actor->user.coll;
|
|
if (coll.type == kHitSprite)
|
|
{
|
|
auto hitActor = coll.actor();
|
|
if (hitActor->hasU() && hitActor->user.PlayerP)
|
|
{
|
|
// if you ran into a player - call close range functions
|
|
DoActorPickClosePlayer(actor);
|
|
auto action = ChooseAction(actor->user.Personality->TouchTarget);
|
|
actor->callFunction(action);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
bool ActorFlaming(DSWActor* actor)
|
|
{
|
|
auto flame = actor->user.flameActor;
|
|
if (flame != nullptr)
|
|
{
|
|
if (ActorSizeZ(flame) > ActorSizeZ(actor) * 0.75)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
void DoActorSetSpeed(DSWActor* actor, uint8_t speed)
|
|
{
|
|
if (actor->spr.cstat & (CSTAT_SPRITE_RESTORE))
|
|
return;
|
|
|
|
ASSERT(actor->user.__legacyState.Attrib);
|
|
|
|
actor->user.speed = speed;
|
|
|
|
int vel = actor->user.__legacyState.Attrib->Speed[speed];
|
|
if (ActorFlaming(actor))
|
|
vel = (vel * 3) >> 1;
|
|
|
|
actor->vel.X = vel * maptoworld;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
/*
|
|
!AIC - Does a table lookup based on a random value from 0 to 1023.
|
|
These tables are defined at the top of all actor files such as ninja.c,
|
|
goro.c etc.
|
|
*/
|
|
//---------------------------------------------------------------------------
|
|
|
|
VMFunction* ChooseAction(DECISION decision[])
|
|
{
|
|
// !JIM! Here is an opportunity for some AI, instead of randomness!
|
|
int random_value = RANDOM_P2(1024<<5)>>5;
|
|
|
|
for (int i = 0; true; i++)
|
|
{
|
|
ASSERT(i < 10);
|
|
|
|
if (random_value <= decision[i].range)
|
|
{
|
|
return *decision[i].action;
|
|
}
|
|
}
|
|
}
|
|
|
|
int ChooseNoise(DECISIONB decision[])
|
|
{
|
|
// !JIM! Here is an opportunity for some AI, instead of randomness!
|
|
int random_value = RANDOM_P2(1024 << 5) >> 5;
|
|
|
|
for (int i = 0; true; i++)
|
|
{
|
|
ASSERT(i < 10);
|
|
|
|
if (random_value <= decision[i].range)
|
|
{
|
|
return decision[i].noise;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
/*
|
|
!AIC - Sometimes just want the offset of the action
|
|
*/
|
|
//---------------------------------------------------------------------------
|
|
|
|
int ChooseActionNumber(int16_t decision[])
|
|
{
|
|
int random_value = RANDOM_P2(1024<<5)>>5;
|
|
|
|
for (int i = 0; true; i++)
|
|
{
|
|
if (random_value <= decision[i])
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
int DoActorNoise(DSWActor* actor, int noise)
|
|
{
|
|
if (noise == attr_alert)
|
|
{
|
|
if (!actor->hasU() || actor->user.DidAlert) // This only allowed once
|
|
return 0;
|
|
}
|
|
PlaySpriteSound(actor, noise, v3df_follow);
|
|
return 0;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
bool CanSeePlayer(DSWActor* actor)
|
|
{
|
|
return actor->user.targetActor && FAFcansee(ActorVectOfTop(actor), actor->sector(), ActorUpperVect(actor->user.targetActor), actor->user.targetActor->sector());
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
int CanHitPlayer(DSWActor* actor)
|
|
{
|
|
HitInfo hit{};
|
|
DVector3 vect;
|
|
// if actor can still see the player
|
|
|
|
DSWActor* targ = actor->user.targetActor;
|
|
DVector3 apos = actor->spr.pos.plusZ(-ActorSizeZ(actor) * 0.5);
|
|
DVector3 tpos = targ->spr.pos.plusZ(-ActorSizeZ(targ) * 0.5);
|
|
auto vec = (tpos - apos).Unit() * 1024;
|
|
|
|
FAFhitscan(apos, actor->sector(), vec, hit, CLIPMASK_MISSILE);
|
|
|
|
if (hit.hitSector == nullptr)
|
|
return false;
|
|
|
|
if (hit.actor() == actor->user.targetActor)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
/*
|
|
!AIC - Pick a nearby player to be the actors target
|
|
*/
|
|
//---------------------------------------------------------------------------
|
|
|
|
int DoActorPickClosePlayer(DSWActor* actor)
|
|
{
|
|
int pnum;
|
|
DSWPlayer* pp;
|
|
// if actor can still see the player
|
|
bool found = false;
|
|
int i;
|
|
|
|
double near_dist = MAX_ACTIVE_RANGE;
|
|
double dist;
|
|
|
|
if (actor->user.ID == ZOMBIE_RUN_R0 && gNet.MultiGameType == MULTI_GAME_COOPERATIVE)
|
|
goto TARGETACTOR;
|
|
|
|
// Set initial target to Player 0
|
|
actor->user.targetActor = getPlayer(0)->GetActor();
|
|
|
|
if (actor->user.Flags2 & (SPR2_DONT_TARGET_OWNER))
|
|
{
|
|
TRAVERSE_CONNECT(pnum)
|
|
{
|
|
pp = getPlayer(pnum);
|
|
|
|
if (GetOwner(actor) == pp->GetActor())
|
|
continue;
|
|
|
|
actor->user.targetActor = pp->GetActor();
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
// Set initial target to the closest player
|
|
TRAVERSE_CONNECT(pnum)
|
|
{
|
|
pp = getPlayer(pnum);
|
|
|
|
// Zombies don't target their masters!
|
|
if (actor->user.Flags2 & (SPR2_DONT_TARGET_OWNER))
|
|
{
|
|
if (GetOwner(actor) == pp->GetActor())
|
|
continue;
|
|
|
|
if (!PlayerTakeDamage(pp, actor))
|
|
continue;
|
|
|
|
// if co-op don't hurt teammate
|
|
// if (gNet.MultiGameType == MULTI_GAME_COOPERATIVE && !gNet.HurtTeammate && actor->user.spal == pp->actor->spr.spal)
|
|
// continue;
|
|
}
|
|
|
|
dist = (actor->spr.pos - pp->GetActor()->getPosWithOffsetZ()).Length();
|
|
|
|
if (dist < near_dist)
|
|
{
|
|
near_dist = dist;
|
|
actor->user.targetActor = pp->GetActor();
|
|
}
|
|
}
|
|
|
|
// see if you can find someone close that you can SEE
|
|
near_dist = MAX_ACTIVE_RANGE;
|
|
found = false;
|
|
TRAVERSE_CONNECT(pnum)
|
|
{
|
|
pp = getPlayer(pnum);
|
|
|
|
// Zombies don't target their masters!
|
|
if (actor->user.Flags2 & (SPR2_DONT_TARGET_OWNER))
|
|
{
|
|
if (GetOwner(actor) == pp->GetActor())
|
|
continue;
|
|
|
|
if (!PlayerTakeDamage(pp, actor))
|
|
continue;
|
|
}
|
|
|
|
dist = (actor->spr.pos - pp->GetActor()->getPosWithOffsetZ()).Length();
|
|
|
|
DSWActor* plActor = pp->GetActor();
|
|
|
|
if (dist < near_dist && FAFcansee(ActorVectOfTop(actor), actor->sector(), ActorUpperVect(plActor), plActor->sector()))
|
|
{
|
|
near_dist = dist;
|
|
actor->user.targetActor = pp->GetActor();
|
|
found = true;
|
|
}
|
|
}
|
|
|
|
|
|
TARGETACTOR:
|
|
// this is only for Zombies right now
|
|
// zombie target other actors
|
|
if (!found && (actor->user.Flags2 & SPR2_DONT_TARGET_OWNER))
|
|
{
|
|
near_dist = MAX_ACTIVE_RANGE;
|
|
SWStatIterator it(STAT_ENEMY);
|
|
while (auto itActor = it.Next())
|
|
{
|
|
if (itActor == actor || !itActor->hasU())
|
|
continue;
|
|
|
|
if ((itActor->user.Flags & (SPR_SUICIDE | SPR_DEAD)))
|
|
continue;
|
|
|
|
dist = (actor->spr.pos - itActor->spr.pos).Length();
|
|
|
|
if (dist < near_dist && FAFcansee(ActorVectOfTop(actor), actor->sector(), ActorUpperVect(itActor), itActor->sector()))
|
|
{
|
|
near_dist = dist;
|
|
actor->user.targetActor = itActor;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
DSWActor* GetPlayerSpriteNum(DSWActor* actor)
|
|
{
|
|
int pnum;
|
|
DSWPlayer* pp;
|
|
|
|
TRAVERSE_CONNECT(pnum)
|
|
{
|
|
pp = getPlayer(pnum);
|
|
|
|
if (pp->GetActor() == actor->user.targetActor)
|
|
{
|
|
return pp->GetActor();
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
int DoActorOperate(DSWActor* actor)
|
|
{
|
|
HitInfo near{};
|
|
double z[2];
|
|
unsigned int i;
|
|
|
|
if (actor->user.ID == HORNET_RUN_R0 || actor->user.ID == EEL_RUN_R0 || actor->user.ID == BUNNY_RUN_R0)
|
|
return false;
|
|
|
|
if (actor->checkStateGroup(NAME_Sit) || actor->checkStateGroup(NAME_Stand))
|
|
return false;
|
|
|
|
if ((actor->user.WaitTics -= ACTORMOVETICS) > 0)
|
|
return false;
|
|
|
|
z[0] = -ActorSizeZ(actor) + 5;
|
|
z[1] = -(ActorSizeZ(actor) * 0.5);
|
|
|
|
for (i = 0; i < SIZ(z); i++)
|
|
{
|
|
neartag(actor->spr.pos.plusZ(z[i]), actor->sector(), actor->spr.Angles.Yaw, near, 64., NT_Lotag | NT_Hitag | NT_NoSpriteCheck);
|
|
}
|
|
|
|
if (near.hitSector != nullptr)
|
|
{
|
|
if (OperateSector(near.hitSector, false))
|
|
{
|
|
actor->user.WaitTics = 2 * 120;
|
|
|
|
actor->setStateGroup(NAME_Sit);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
DECISION GenericFlaming[] =
|
|
{
|
|
{30, &AF(InitActorAttack)},
|
|
{512, &AF(InitActorRunToward)},
|
|
{1024, &AF(InitActorRunAway)},
|
|
};
|
|
|
|
/*
|
|
!AIC KEY - This routine decides what the actor will do next. It is not called
|
|
every time through the loop. This would be too slow. It is only called when
|
|
the actor needs to know what to do next such as running into something or being
|
|
targeted. It makes decisions based on the distance and viewablity of its target
|
|
(actor->user.targetActor). When it figures out the situatation with its target it calls
|
|
ChooseAction which does a random table lookup to decide what action to initialize.
|
|
Once this action is initialized it will be called until it can't figure out what to
|
|
do anymore and then this routine is called again.
|
|
*/
|
|
|
|
VMFunction* DoActorActionDecide(DSWActor* actor)
|
|
{
|
|
VMFunction* action;
|
|
bool ICanSee=false;
|
|
|
|
// REMINDER: This function is not even called if SpriteControl doesn't let
|
|
// it get called
|
|
|
|
ASSERT(actor->user.Personality);
|
|
|
|
actor->user.Dist = 0;
|
|
action = AF(InitActorDecide);
|
|
|
|
// target is gone.
|
|
if (actor->user.targetActor == nullptr)
|
|
{
|
|
return action;
|
|
}
|
|
|
|
if (actor->user.Flags & (SPR_JUMPING | SPR_FALLING))
|
|
{
|
|
//CON_Message("Jumping or falling");
|
|
return action;
|
|
}
|
|
|
|
// everybody on fire acts like this
|
|
if (ActorFlaming(actor))
|
|
{
|
|
action = ChooseAction(&GenericFlaming[0]);
|
|
//CON_Message("On Fire");
|
|
return action;
|
|
}
|
|
|
|
ICanSee = CanSeePlayer(actor); // Only need to call once
|
|
// But need the result multiple times
|
|
|
|
// !AIC KEY - If aware of player - var is changed in SpriteControl
|
|
if (actor->user.Flags & (SPR_ACTIVE))
|
|
{
|
|
|
|
// Try to operate stuff
|
|
DoActorOperate(actor);
|
|
|
|
// if far enough away and cannot see the player
|
|
double dist = (actor->spr.pos.XY() - actor->user.targetActor->spr.pos.XY()).Length();
|
|
|
|
if (dist > 1875 && !ICanSee)
|
|
{
|
|
// Enemy goes inactive - he is still allowed to roam about for about
|
|
// 5 seconds trying to find another player before his active_range is
|
|
// bumped down
|
|
actor->user.Flags &= ~(SPR_ACTIVE);
|
|
|
|
// You've lost the player - now decide what to do
|
|
action = ChooseAction(actor->user.Personality->LostTarget);
|
|
//CON_Message("LostTarget");
|
|
return action;
|
|
}
|
|
|
|
|
|
auto pActor = GetPlayerSpriteNum(actor);
|
|
// check for short range attack possibility
|
|
if ((dist < CloseRangeDist(actor, actor->user.targetActor) && ICanSee) ||
|
|
(pActor && pActor->hasU() && pActor->user.WeaponNum == WPN_FIST && actor->user.ID != RIPPER2_RUN_R0 && actor->user.ID != RIPPER_RUN_R0))
|
|
{
|
|
if ((actor->user.ID == COOLG_RUN_R0 && (actor->spr.cstat & CSTAT_SPRITE_TRANSLUCENT)) || (actor->spr.cstat & CSTAT_SPRITE_INVISIBLE))
|
|
action = ChooseAction(actor->user.Personality->Evasive);
|
|
else
|
|
action = ChooseAction(actor->user.Personality->CloseRange);
|
|
//CON_Message("CloseRange");
|
|
return action;
|
|
}
|
|
|
|
// if player is facing me and I'm being attacked
|
|
if (Facing(actor, actor->user.targetActor) && (actor->user.Flags & SPR_ATTACKED) && ICanSee)
|
|
{
|
|
// if I'm a target - at least one missile comming at me
|
|
if (actor->user.Flags & (SPR_TARGETED))
|
|
{
|
|
// not going to evade, reset the target bit
|
|
actor->user.Flags &= ~(SPR_TARGETED); // as far as actor
|
|
// knows, its not a
|
|
// target any more
|
|
if (actor->hasState(NAME_Duck) && RANDOM_P2(1024<<8)>>8 < 100)
|
|
action = AF(InitActorDuck);
|
|
else
|
|
{
|
|
if ((actor->user.ID == COOLG_RUN_R0 && (actor->spr.cstat & CSTAT_SPRITE_TRANSLUCENT)) || (actor->spr.cstat & CSTAT_SPRITE_INVISIBLE))
|
|
action = ChooseAction(actor->user.Personality->Evasive);
|
|
else
|
|
action = ChooseAction(actor->user.Personality->Battle);
|
|
}
|
|
//CON_Message("Battle 1");
|
|
return action;
|
|
}
|
|
// if NOT a target - don't bother with evasive action and start
|
|
// fighting
|
|
else
|
|
{
|
|
if ((actor->user.ID == COOLG_RUN_R0 && (actor->spr.cstat & CSTAT_SPRITE_TRANSLUCENT)) || (actor->spr.cstat & CSTAT_SPRITE_INVISIBLE))
|
|
action = ChooseAction(actor->user.Personality->Evasive);
|
|
else
|
|
action = ChooseAction(actor->user.Personality->Battle);
|
|
//CON_Message("Battle 2");
|
|
return action;
|
|
}
|
|
|
|
}
|
|
// if player is NOT facing me he is running or unaware of actor
|
|
else if (ICanSee)
|
|
{
|
|
if ((actor->user.ID == COOLG_RUN_R0 && (actor->spr.cstat & CSTAT_SPRITE_TRANSLUCENT)) || (actor->spr.cstat & CSTAT_SPRITE_INVISIBLE))
|
|
action = ChooseAction(actor->user.Personality->Evasive);
|
|
else
|
|
action = ChooseAction(actor->user.Personality->Offense);
|
|
//CON_Message("Offense");
|
|
return action;
|
|
}
|
|
else
|
|
{
|
|
// You've lost the player - now decide what to do
|
|
action = ChooseAction(actor->user.Personality->LostTarget);
|
|
//CON_Message("Close but cant see, LostTarget");
|
|
return action;
|
|
}
|
|
}
|
|
// Not active - not aware of player and cannot see him
|
|
else
|
|
{
|
|
// try and find another player
|
|
// pick a closeby player as the (new) target
|
|
if (actor->spr.hitag != TAG_SWARMSPOT)
|
|
DoActorPickClosePlayer(actor);
|
|
|
|
// if close by
|
|
double const dist = (actor->spr.pos.XY() - actor->user.targetActor->spr.pos.XY()).Length();
|
|
if (dist < 937.5 || ICanSee)
|
|
{
|
|
if ((Facing(actor, actor->user.targetActor) && dist < 625) || ICanSee)
|
|
{
|
|
DoActorOperate(actor);
|
|
|
|
// Don't let player completely sneek up behind you
|
|
action = ChooseAction(actor->user.Personality->Surprised);
|
|
//CON_Message("Surprised");
|
|
if (!actor->user.DidAlert && ICanSee)
|
|
{
|
|
DoActorNoise(actor, attr_alert);
|
|
actor->user.DidAlert = true;
|
|
}
|
|
return action;
|
|
|
|
}
|
|
else
|
|
{
|
|
// Player has not seen actor, to be fair let him know actor
|
|
// are there
|
|
;
|
|
DoActorNoise(actor, ChooseNoise(actor->user.Personality->Broadcast));
|
|
return action;
|
|
}
|
|
}
|
|
}
|
|
|
|
//CON_Message("Couldn't resolve decide, &AF(InitActorDecide)");
|
|
return action;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
int InitActorDecide(DSWActor* actor)
|
|
{
|
|
actor->setActionDecide();
|
|
return DoActorDecide(actor);
|
|
}
|
|
|
|
int InitActorSetDecide(DSWActor* actor)
|
|
{
|
|
actor->setActionDecide();
|
|
return 0;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
int DoActorDecide(DSWActor* actor)
|
|
{
|
|
VMFunction* actor_action;
|
|
|
|
// See what to do next
|
|
|
|
actor_action = DoActorActionDecide(actor);
|
|
|
|
// Fix for the GenericFlaming bug for actors that don't have attack states
|
|
if (actor_action == AF(InitActorAttack) && actor->user.WeaponNum == 0)
|
|
return 0; // Just let the actor do as it was doing before in this case
|
|
|
|
// Target is gone.
|
|
if (actor->user.targetActor == nullptr)
|
|
return 0;
|
|
|
|
// zombie is attacking a player
|
|
if (actor_action == AF(InitActorAttack) && actor->user.ID == ZOMBIE_RUN_R0 && actor->user.targetActor->user.PlayerP)
|
|
{
|
|
// Don't let zombies shoot at master
|
|
if (GetOwner(actor) == actor->user.targetActor)
|
|
return 0;
|
|
|
|
// if this player cannot take damage from this zombie(weapon) return out
|
|
if (!PlayerTakeDamage(actor->user.targetActor->user.PlayerP, actor))
|
|
return 0;
|
|
}
|
|
|
|
ASSERT(actor_action != nullptr);
|
|
|
|
if (actor_action != AF(InitActorDecide))
|
|
{
|
|
// NOT staying put
|
|
actor->callFunction(actor_action);
|
|
}
|
|
else
|
|
{
|
|
// Actually staying put
|
|
actor->setStateGroup(NAME_Stand);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
/*
|
|
!AIC KEY - Routines handle moving toward the player.
|
|
*/
|
|
//---------------------------------------------------------------------------
|
|
|
|
int InitActorMoveCloser(DSWActor* actor)
|
|
{
|
|
actor->user.ActorActionFunc = AF(DoActorMoveCloser);
|
|
|
|
if (!actor->checkStateGroup(NAME_Run))
|
|
actor->setStateGroup(NAME_Run);
|
|
|
|
actor->callAction();
|
|
|
|
return 0;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
int DoActorCantMoveCloser(DSWActor* actor)
|
|
{
|
|
actor->user.track = FindTrackToPlayer(actor);
|
|
|
|
if (actor->user.track >= 0)
|
|
{
|
|
auto tp = Track[actor->user.track].TrackPoint + actor->user.point;
|
|
actor->spr.Angles.Yaw = (tp->pos - actor->spr.pos).Angle();
|
|
|
|
DoActorSetSpeed(actor, MID_SPEED);
|
|
actor->user.Flags |= (SPR_FIND_PLAYER);
|
|
|
|
actor->setActionDecide();
|
|
actor->setStateGroup(NAME_Run);
|
|
}
|
|
else
|
|
{
|
|
// Try to move closer
|
|
InitActorReposition(actor);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
int DoActorMoveCloser(DSWActor* actor)
|
|
{
|
|
// if cannot move the sprite
|
|
if (!move_actor(actor, DVector3(actor->spr.Angles.Yaw.ToVector() * actor->vel.X, 0)))
|
|
{
|
|
if (ActorMoveHitReact(actor))
|
|
return 0;
|
|
|
|
DoActorCantMoveCloser(actor);
|
|
return 0;
|
|
}
|
|
|
|
// Do a noise if ok
|
|
DoActorNoise(actor, ChooseNoise(actor->user.Personality->Broadcast));
|
|
|
|
// after moving a ways check to see if player is still in sight
|
|
if (actor->user.DistCheck > 34.375)
|
|
{
|
|
actor->user.DistCheck = 0;
|
|
|
|
// If player moved out of sight
|
|
if (!CanSeePlayer(actor))
|
|
{
|
|
// stay put and choose another option
|
|
InitActorDecide(actor);
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
// turn to face player
|
|
actor->spr.Angles.Yaw = (actor->user.targetActor->spr.pos - actor->spr.pos).Angle();
|
|
}
|
|
}
|
|
|
|
// Should be a random value test
|
|
if (actor->user.Dist > 32 * 3)
|
|
{
|
|
InitActorDecide(actor);
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
/*
|
|
!AIC - Find tracks of different types. Toward target, away from target, etc.
|
|
*/
|
|
//---------------------------------------------------------------------------
|
|
|
|
int FindTrackToPlayer(DSWActor* actor)
|
|
{
|
|
int point, track_dir, track;
|
|
int i, size;
|
|
const uint16_t* type;
|
|
double zdiff;
|
|
|
|
static const uint16_t PlayerAbove[] =
|
|
{
|
|
BIT(TT_LADDER),
|
|
BIT(TT_STAIRS),
|
|
BIT(TT_JUMP_UP),
|
|
BIT(TT_TRAVERSE),
|
|
BIT(TT_OPERATE),
|
|
BIT(TT_SCAN)
|
|
};
|
|
|
|
static const uint16_t PlayerBelow[] =
|
|
{
|
|
BIT(TT_JUMP_DOWN),
|
|
BIT(TT_STAIRS),
|
|
BIT(TT_TRAVERSE),
|
|
BIT(TT_OPERATE),
|
|
BIT(TT_SCAN)
|
|
};
|
|
|
|
static const uint16_t PlayerOnLevel[] =
|
|
{
|
|
BIT(TT_DUCK_N_SHOOT),
|
|
BIT(TT_HIDE_N_SHOOT),
|
|
BIT(TT_TRAVERSE),
|
|
BIT(TT_EXIT),
|
|
BIT(TT_OPERATE),
|
|
BIT(TT_SCAN)
|
|
};
|
|
|
|
zdiff = ActorUpperZ(actor->user.targetActor) - (actor->spr.pos.Z - ActorSizeZ(actor) + 8);
|
|
|
|
if (abs(zdiff) <= 20)
|
|
{
|
|
type = PlayerOnLevel;
|
|
size = SIZ(PlayerOnLevel);
|
|
}
|
|
else
|
|
{
|
|
if (zdiff < 0)
|
|
{
|
|
type = PlayerAbove;
|
|
size = SIZ(PlayerAbove);
|
|
}
|
|
else
|
|
{
|
|
type = PlayerBelow;
|
|
size = SIZ(PlayerBelow);
|
|
}
|
|
}
|
|
|
|
|
|
for (i = 0; i < size; i++)
|
|
{
|
|
track = ActorFindTrack(actor, 1, type[i], &point, &track_dir);
|
|
|
|
if (track >= 0)
|
|
{
|
|
actor->user.point = point;
|
|
actor->user.track_dir = track_dir;
|
|
Track[track].flags |= (TF_TRACK_OCCUPIED);
|
|
|
|
return track;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
int FindTrackAwayFromPlayer(DSWActor* actor)
|
|
{
|
|
int point, track_dir, track;
|
|
unsigned int i;
|
|
|
|
static const int16_t RunAwayTracks[] =
|
|
{
|
|
BIT(TT_EXIT),
|
|
BIT(TT_LADDER),
|
|
BIT(TT_TRAVERSE),
|
|
BIT(TT_STAIRS),
|
|
BIT(TT_JUMP_UP),
|
|
BIT(TT_JUMP_DOWN),
|
|
BIT(TT_DUCK_N_SHOOT),
|
|
BIT(TT_HIDE_N_SHOOT),
|
|
BIT(TT_OPERATE),
|
|
BIT(TT_SCAN)
|
|
};
|
|
|
|
for (i = 0; i < SIZ(RunAwayTracks); i++)
|
|
{
|
|
track = ActorFindTrack(actor, -1, RunAwayTracks[i], &point, &track_dir);
|
|
|
|
if (track >= 0)
|
|
{
|
|
actor->user.point = point;
|
|
actor->user.track_dir = track_dir;
|
|
Track[track].flags |= (TF_TRACK_OCCUPIED);
|
|
|
|
return track;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
int FindWanderTrack(DSWActor* actor)
|
|
{
|
|
int point, track_dir, track;
|
|
unsigned int i;
|
|
|
|
static const int16_t WanderTracks[] =
|
|
{
|
|
BIT(TT_DUCK_N_SHOOT),
|
|
BIT(TT_HIDE_N_SHOOT),
|
|
BIT(TT_WANDER),
|
|
BIT(TT_JUMP_DOWN),
|
|
BIT(TT_JUMP_UP),
|
|
BIT(TT_TRAVERSE),
|
|
BIT(TT_STAIRS),
|
|
BIT(TT_LADDER),
|
|
BIT(TT_EXIT),
|
|
BIT(TT_OPERATE)
|
|
};
|
|
|
|
for (i = 0; i < SIZ(WanderTracks); i++)
|
|
{
|
|
track = ActorFindTrack(actor, -1, WanderTracks[i], &point, &track_dir);
|
|
|
|
if (track >= 0)
|
|
{
|
|
actor->user.point = point;
|
|
actor->user.track_dir = track_dir;
|
|
Track[track].flags |= (TF_TRACK_OCCUPIED);
|
|
|
|
return track;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
int InitActorRunAway(DSWActor* actor)
|
|
{
|
|
actor->setActionDecide();
|
|
actor->setStateGroup(NAME_Run);
|
|
|
|
actor->user.track = FindTrackAwayFromPlayer(actor);
|
|
|
|
if (actor->user.track >= 0)
|
|
{
|
|
auto tp = Track[actor->user.track].TrackPoint + actor->user.point;
|
|
actor->spr.Angles.Yaw = (tp->pos - actor->spr.pos).Angle();
|
|
DoActorSetSpeed(actor, FAST_SPEED);
|
|
actor->user.Flags |= (SPR_RUN_AWAY);
|
|
}
|
|
else
|
|
{
|
|
actor->user.Flags |= (SPR_RUN_AWAY);
|
|
InitActorReposition(actor);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
int InitActorRunToward(DSWActor* actor)
|
|
{
|
|
actor->setActionDecide();
|
|
actor->setStateGroup(NAME_Run);
|
|
|
|
InitActorReposition(actor);
|
|
DoActorSetSpeed(actor, FAST_SPEED);
|
|
|
|
return 0;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
/*
|
|
!AIC - Where actors do their attacks. There is some special case code throughout
|
|
these. Both close and long range attacks are handled here by transitioning to
|
|
the correct attack state.
|
|
*/
|
|
//---------------------------------------------------------------------------
|
|
|
|
int InitActorAttack(DSWActor* actor)
|
|
{
|
|
// zombie is attacking a player
|
|
if (actor->user.ID == ZOMBIE_RUN_R0 && actor->user.targetActor->hasU() && actor->user.targetActor->user.PlayerP)
|
|
{
|
|
// Don't let zombies shoot at master
|
|
if (GetOwner(actor) == actor->user.targetActor)
|
|
return 0;
|
|
|
|
// if this player cannot take damage from this zombie(weapon) return out
|
|
if (!PlayerTakeDamage(actor->user.targetActor->user.PlayerP, actor))
|
|
return 0;
|
|
}
|
|
|
|
if ((actor->user.targetActor->spr.cstat & CSTAT_SPRITE_TRANSLUCENT))
|
|
{
|
|
InitActorRunAway(actor);
|
|
return 0;
|
|
}
|
|
|
|
if (actor->user.targetActor->hasU() && actor->user.targetActor->user.Health <= 0)
|
|
{
|
|
DoActorPickClosePlayer(actor);
|
|
InitActorReposition(actor);
|
|
return 0;
|
|
}
|
|
|
|
if (!CanHitPlayer(actor))
|
|
{
|
|
InitActorReposition(actor);
|
|
return 0;
|
|
}
|
|
|
|
// if the guy you are after is dead, look for another and
|
|
// reposition
|
|
if (actor->user.targetActor->hasU() && actor->user.targetActor->user.PlayerP &&
|
|
(actor->user.targetActor->user.PlayerP->Flags & PF_DEAD))
|
|
{
|
|
DoActorPickClosePlayer(actor);
|
|
InitActorReposition(actor);
|
|
return 0;
|
|
}
|
|
|
|
actor->user.ActorActionFunc = AF(DoActorAttack);
|
|
|
|
// move into standing frame
|
|
//actor->setStateGroup(NAME_Stand);
|
|
|
|
// face player when attacking
|
|
actor->spr.Angles.Yaw = (actor->user.targetActor->spr.pos - actor->spr.pos).Angle();
|
|
|
|
// If it's your own kind, lay off!
|
|
if (actor->user.ID == actor->user.targetActor->user.ID && !actor->user.targetActor->user.PlayerP)
|
|
{
|
|
InitActorRunAway(actor);
|
|
return 0;
|
|
}
|
|
|
|
// Hari Kari for Ninja's
|
|
if (actor->hasState(NAME_Death2))
|
|
{
|
|
const int SUICIDE_HEALTH_VALUE = 38;
|
|
|
|
if (actor->user.Health < SUICIDE_HEALTH_VALUE)
|
|
{
|
|
if (CHOOSE2(100))
|
|
{
|
|
actor->setActionDecide();
|
|
actor->setStateGroup(NAME_Death2);
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
actor->callAction();
|
|
|
|
return 0;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
int DoActorAttack(DSWActor* actor)
|
|
{
|
|
int rand_num;
|
|
|
|
DoActorNoise(actor, ChooseNoise(actor->user.Personality->Broadcast));
|
|
|
|
double dist =(actor->spr.pos.XY() - actor->user.targetActor->spr.pos.XY()).Length();
|
|
|
|
auto pActor = GetPlayerSpriteNum(actor);
|
|
if ((actor->user.__legacyState.ActorActionSet->CloseAttack[0] && dist < CloseRangeDist(actor, actor->user.targetActor)) ||
|
|
(pActor && pActor->hasU() && pActor->user.WeaponNum == WPN_FIST)) // JBF: added null check
|
|
{
|
|
rand_num = ChooseActionNumber(actor->user.__legacyState.ActorActionSet->CloseAttackPercent);
|
|
|
|
actor->setStateGroup(NAME_CloseAttack, rand_num);
|
|
}
|
|
else
|
|
{
|
|
ASSERT(actor->user.WeaponNum != 0);
|
|
|
|
rand_num = ChooseActionNumber(actor->user.__legacyState.ActorActionSet->AttackPercent);
|
|
|
|
ASSERT(rand_num < actor->user.WeaponNum);
|
|
|
|
actor->setStateGroup(NAME_Attack, rand_num);
|
|
actor->setActionDecide();
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
int InitActorEvade(DSWActor* actor)
|
|
{
|
|
// Evade is same thing as run away except when you get to the end of the track
|
|
// you stop and take up the fight again.
|
|
|
|
actor->setActionDecide();
|
|
actor->setStateGroup(NAME_Run);
|
|
|
|
actor->user.track = FindTrackAwayFromPlayer(actor);
|
|
|
|
if (actor->user.track >= 0)
|
|
{
|
|
auto tp = Track[actor->user.track].TrackPoint + actor->user.point;
|
|
actor->spr.Angles.Yaw = (tp->pos - actor->spr.pos).Angle();
|
|
DoActorSetSpeed(actor, FAST_SPEED);
|
|
// NOT doing a RUN_AWAY
|
|
actor->user.Flags &= ~(SPR_RUN_AWAY);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
int InitActorWanderAround(DSWActor* actor)
|
|
{
|
|
actor->setActionDecide();
|
|
actor->setStateGroup(NAME_Run);
|
|
|
|
DoActorPickClosePlayer(actor);
|
|
|
|
actor->user.track = FindWanderTrack(actor);
|
|
|
|
if (actor->user.track >= 0)
|
|
{
|
|
auto tp = Track[actor->user.track].TrackPoint + actor->user.point;
|
|
actor->spr.Angles.Yaw = (tp->pos - actor->spr.pos).Angle();
|
|
DoActorSetSpeed(actor, NORM_SPEED);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
int InitActorFindPlayer(DSWActor* actor)
|
|
{
|
|
actor->setActionDecide();
|
|
actor->setStateGroup(NAME_Run);
|
|
|
|
actor->user.track = FindTrackToPlayer(actor);
|
|
|
|
if (actor->user.track >= 0)
|
|
{
|
|
auto tp = Track[actor->user.track].TrackPoint + actor->user.point;
|
|
actor->spr.Angles.Yaw = (tp->pos - actor->spr.pos).Angle();
|
|
DoActorSetSpeed(actor, MID_SPEED);
|
|
actor->user.Flags |= (SPR_FIND_PLAYER);
|
|
|
|
actor->setActionDecide();
|
|
actor->setStateGroup(NAME_Run);
|
|
}
|
|
else
|
|
{
|
|
InitActorReposition(actor);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
int InitActorDuck(DSWActor* actor)
|
|
{
|
|
if (!actor->hasState(NAME_Duck))
|
|
{
|
|
actor->setActionDecide();
|
|
return 0;
|
|
}
|
|
|
|
actor->user.ActorActionFunc = AF(DoActorDuck);
|
|
actor->setStateGroup(NAME_Duck);
|
|
|
|
double dist = (actor->spr.pos.XY() - actor->user.targetActor->spr.pos.XY()).LengthSquared();
|
|
|
|
if (dist > 500*500)
|
|
{
|
|
actor->user.WaitTics = 190;
|
|
}
|
|
else
|
|
{
|
|
//actor->user.WaitTics = 120;
|
|
actor->user.WaitTics = 60;
|
|
}
|
|
|
|
|
|
actor->callAction();
|
|
|
|
return 0;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
int DoActorDuck(DSWActor* actor)
|
|
{
|
|
if ((actor->user.WaitTics -= ACTORMOVETICS) < 0)
|
|
{
|
|
actor->setStateGroup(NAME_Rise);
|
|
actor->setActionDecide();
|
|
actor->user.Flags &= ~(SPR_TARGETED);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
int DoActorMoveJump(DSWActor* actor)
|
|
{
|
|
move_actor(actor, DVector3(actor->spr.Angles.Yaw.ToVector() * actor->vel.X, 0));
|
|
|
|
if (!(actor->user.Flags & (SPR_JUMPING|SPR_FALLING)))
|
|
{
|
|
InitActorDecide(actor);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
Collision move_scan(DSWActor* actor, DAngle ang, double dst, DVector3& stop)
|
|
{
|
|
uint32_t cliptype = CLIPMASK_ACTOR;
|
|
|
|
DSWActor* highActor;
|
|
DSWActor* lowActor;
|
|
sectortype* lo_sectp,* hi_sectp, *ssp;
|
|
|
|
|
|
// moves out a bit but keeps the sprites original postion/sector.
|
|
|
|
// save off position info
|
|
auto pos = actor->spr.pos;
|
|
auto sang = actor->spr.Angles.Yaw;
|
|
auto loz = actor->user.loz;
|
|
auto hiz = actor->user.hiz;
|
|
lowActor = actor->user.lowActor;
|
|
highActor = actor->user.highActor;
|
|
lo_sectp = actor->user.lo_sectp;
|
|
hi_sectp = actor->user.hi_sectp;
|
|
ssp = actor->sector();
|
|
|
|
// do the move
|
|
actor->spr.Angles.Yaw = ang;
|
|
auto vec = ang.ToVector() * dst;
|
|
|
|
Collision ret = move_sprite(actor, DVector3(vec, 0), actor->user.ceiling_dist, actor->user.floor_dist, cliptype, 1);
|
|
// move_sprite DOES do a getzrange point?
|
|
|
|
// should I look down with a FAFgetzrange to see where I am?
|
|
|
|
// remember where it stopped
|
|
stop = actor->spr.pos;
|
|
|
|
// reset position information
|
|
actor->spr.pos = pos;
|
|
actor->spr.Angles.Yaw = sang;
|
|
actor->user.loz = loz;
|
|
actor->user.hiz = hiz;
|
|
actor->user.lowActor = lowActor;
|
|
actor->user.highActor = highActor;
|
|
actor->user.lo_sectp = lo_sectp;
|
|
actor->user.hi_sectp = hi_sectp;
|
|
ChangeActorSect(actor, ssp);
|
|
|
|
return ret;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
enum
|
|
{
|
|
TOWARD = 1,
|
|
AWAY = -1
|
|
};
|
|
|
|
DAngle FindNewAngle(DSWActor* actor, int dir, double DistToMove)
|
|
{
|
|
static const int16_t toward_angle_delta[4][9] =
|
|
{
|
|
{ -160, -384, 160, 384, -256, 256, -512, 512, -99},
|
|
{ -384, -160, 384, 160, -256, 256, -512, 512, -99},
|
|
{ 160, 384, -160, -384, 256, -256, 512, -512, -99},
|
|
{ 384, 160, -384, -160, 256, -256, 512, -512, -99}
|
|
};
|
|
|
|
static const int16_t away_angle_delta[4][8] =
|
|
{
|
|
{ -768, 768, -640, 640, -896, 896, 1024, -99},
|
|
{ 768, -768, 640, -640, -896, 896, 1024, -99},
|
|
{ 896, -896, -768, 768, -640, 640, 1024, -99},
|
|
{ 896, -896, 768, -768, 640, -640, 1024, -99}
|
|
};
|
|
|
|
|
|
const int16_t* adp = nullptr;
|
|
|
|
DAngle new_ang;
|
|
DAngle save_ang = -minAngle;
|
|
bool save_set = false;
|
|
|
|
int set;
|
|
// start out with mininum distance that will be accepted as a move
|
|
double save_dist = 31.25;
|
|
|
|
// if on fire, run shorter distances
|
|
if (ActorFlaming(actor))
|
|
DistToMove *= .375;
|
|
|
|
// Find angle to from the player
|
|
auto oang = (actor->user.targetActor->spr.pos - actor->spr.pos).Angle();
|
|
|
|
// choose a random angle array
|
|
switch (dir)
|
|
{
|
|
case TOWARD:
|
|
set = RANDOM_P2(4<<8)>>8;
|
|
adp = &toward_angle_delta[set][0];
|
|
break;
|
|
case AWAY:
|
|
set = RANDOM_P2(4<<8)>>8;
|
|
if (CanHitPlayer(actor))
|
|
{
|
|
adp = &toward_angle_delta[set][0];
|
|
}
|
|
else
|
|
{
|
|
adp = &away_angle_delta[set][0];
|
|
}
|
|
break;
|
|
default:
|
|
Printf("FindNewAngle called with dir=%d!\n",dir);
|
|
return nullAngle;
|
|
}
|
|
|
|
for (; *adp != -99; adp++)
|
|
{
|
|
new_ang = oang + mapangle(* adp);
|
|
|
|
#if 1
|
|
// look directly ahead for a ledge
|
|
if (!(actor->user.Flags & (SPR_NO_SCAREDZ | SPR_JUMPING | SPR_FALLING | SPR_SWIMMING | SPR_DEAD)))
|
|
{
|
|
actor->spr.Angles.Yaw = new_ang;
|
|
if (DropAhead(actor, actor->user.lo_step))
|
|
{
|
|
actor->spr.Angles.Yaw = oang;
|
|
continue;
|
|
}
|
|
actor->spr.Angles.Yaw = oang;
|
|
}
|
|
#endif
|
|
|
|
DVector3 stop;
|
|
|
|
// check to see how far we can move
|
|
auto ret = move_scan(actor, new_ang, DistToMove, stop);
|
|
double dist = (actor->spr.pos.XY() - stop.XY()).Length();
|
|
|
|
if (ret.type == kHitNone)
|
|
{
|
|
// cleanly moved in new direction without hitting something
|
|
actor->user.TargetDist = dist;
|
|
return new_ang;
|
|
}
|
|
else
|
|
{
|
|
// hit something
|
|
|
|
if (dist > save_dist)
|
|
{
|
|
save_ang = new_ang;
|
|
save_set = true;
|
|
save_dist = dist;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (save_set)
|
|
{
|
|
actor->user.TargetDist = save_dist;
|
|
|
|
// If actor moved to the TargetDist it would look like he was running
|
|
// into things.
|
|
|
|
// To keep this from happening make the TargetDist is less than the
|
|
// point you would hit something
|
|
|
|
if (actor->user.TargetDist > 250)
|
|
actor->user.TargetDist -= 218.75;
|
|
|
|
actor->spr.Angles.Yaw = save_ang;
|
|
return save_ang;
|
|
}
|
|
|
|
return -minAngle;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
/*
|
|
!AIC KEY - Reposition code is called throughout this file. What this does is
|
|
pick a new direction close to the target direction (or away from the target
|
|
direction if running away) and a distance to move in and tries to move there
|
|
with move_scan(). If it hits something it will try again. No movement is
|
|
actually acomplished here. This is just testing for clear paths to move in.
|
|
Location variables that are changed are saved and reset. FindNewAngle() and
|
|
move_scan() are two routines (above) that go with this. This is definately
|
|
not called every time through the loop. It would be majorly slow.
|
|
*/
|
|
//---------------------------------------------------------------------------
|
|
|
|
int InitActorReposition(DSWActor* actor)
|
|
{
|
|
DAngle ang;
|
|
int rnum;
|
|
double dist;
|
|
|
|
static const int16_t AwayDist[8] =
|
|
{
|
|
17000 / 16,
|
|
20000 / 16,
|
|
26000 / 16,
|
|
26000 / 16,
|
|
26000 / 16,
|
|
32000 / 16,
|
|
32000 / 16,
|
|
42000 / 16
|
|
};
|
|
|
|
static const int16_t TowardDist[8] =
|
|
{
|
|
10000 / 16,
|
|
15000 / 16,
|
|
20000 / 16,
|
|
20000 / 16,
|
|
25000 / 16,
|
|
30000 / 16,
|
|
35000 / 16,
|
|
40000 / 16
|
|
};
|
|
|
|
static const int16_t PlayerDist[8] =
|
|
{
|
|
2000 / 16,
|
|
3000 / 16,
|
|
3000 / 16,
|
|
5000 / 16,
|
|
5000 / 16,
|
|
5000 / 16,
|
|
9000 / 16,
|
|
9000 / 16
|
|
};
|
|
|
|
actor->user.Dist = 0;
|
|
|
|
rnum = RANDOM_P2(8<<8)>>8;
|
|
dist = (actor->spr.pos.XY() - actor->user.targetActor->spr.pos.XY()).Length();
|
|
|
|
if (dist < PlayerDist[rnum] || (actor->user.Flags & SPR_RUN_AWAY))
|
|
{
|
|
rnum = RANDOM_P2(8<<8)>>8;
|
|
ang = FindNewAngle(actor, AWAY, AwayDist[rnum]);
|
|
if (ang == -minAngle)
|
|
{
|
|
actor->user.Vis = 8;
|
|
InitActorPause(actor);
|
|
return 0;
|
|
}
|
|
|
|
actor->spr.Angles.Yaw = ang;
|
|
DoActorSetSpeed(actor, FAST_SPEED);
|
|
actor->user.Flags &= ~(SPR_RUN_AWAY);
|
|
}
|
|
else
|
|
{
|
|
// try to move toward player
|
|
rnum = RANDOM_P2(8<<8)>>8;
|
|
ang = FindNewAngle(actor, TOWARD, TowardDist[rnum]);
|
|
if (ang == -minAngle)
|
|
{
|
|
// try to move away from player
|
|
rnum = RANDOM_P2(8<<8)>>8;
|
|
ang = FindNewAngle(actor, AWAY, AwayDist[rnum]);
|
|
if (ang == -minAngle)
|
|
{
|
|
actor->user.Vis = 8;
|
|
InitActorPause(actor);
|
|
return 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// pick random speed to move toward the player
|
|
if (RANDOM_P2(1024) < 512)
|
|
DoActorSetSpeed(actor, NORM_SPEED);
|
|
else
|
|
DoActorSetSpeed(actor, MID_SPEED);
|
|
}
|
|
|
|
actor->spr.Angles.Yaw = ang;
|
|
}
|
|
|
|
|
|
actor->user.ActorActionFunc = AF(DoActorReposition);
|
|
if (!(actor->user.Flags & SPR_SWIMMING))
|
|
actor->setStateGroup(NAME_Run);
|
|
|
|
actor->callAction();
|
|
|
|
return 0;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
int DoActorReposition(DSWActor* actor)
|
|
{
|
|
// still might hit something and have to handle it.
|
|
if (!move_actor(actor, DVector3(actor->spr.Angles.Yaw.ToVector() * actor->vel.X, 0)))
|
|
{
|
|
if (ActorMoveHitReact(actor))
|
|
return 0;
|
|
|
|
actor->user.Vis = 6;
|
|
InitActorPause(actor);
|
|
return 0;
|
|
}
|
|
|
|
// if close to target distance do a Decision again
|
|
if (actor->user.TargetDist < 3.125)
|
|
{
|
|
InitActorDecide(actor);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
int InitActorPause(DSWActor* actor)
|
|
{
|
|
actor->user.ActorActionFunc = AF(DoActorPause);
|
|
|
|
actor->callAction();
|
|
|
|
return 0;
|
|
}
|
|
|
|
//---------------------------------------------------------------------------
|
|
//
|
|
//
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
int DoActorPause(DSWActor* actor)
|
|
{
|
|
// Using Vis instead of WaitTics, var name sucks, but it's the same type
|
|
// WaitTics is used by too much other actor code and causes problems here
|
|
if ((actor->user.Vis -= ACTORMOVETICS) < 0)
|
|
{
|
|
actor->setActionDecide();
|
|
actor->user.Flags &= ~(SPR_TARGETED);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
END_SW_NS
|