mirror of
https://github.com/ptitSeb/Serious-Engine
synced 2024-11-25 19:55:54 +01:00
363 lines
10 KiB
C++
363 lines
10 KiB
C++
|
700
|
||
|
%{
|
||
|
#include "Entities/StdH/StdH.h"
|
||
|
|
||
|
#include "Entities/EnemyBase.h"
|
||
|
%}
|
||
|
|
||
|
// input parameter for watcher
|
||
|
event EWatcherInit {
|
||
|
CEntityPointer penOwner, // who owns it
|
||
|
};
|
||
|
|
||
|
// entity is seen
|
||
|
event EWatch {
|
||
|
CEntityPointer penSeen,
|
||
|
};
|
||
|
|
||
|
class export CWatcher : CRationalEntity {
|
||
|
name "Watcher";
|
||
|
thumbnail "";
|
||
|
features "CanBePredictable";
|
||
|
|
||
|
properties:
|
||
|
1 CEntityPointer m_penOwner, // entity which owns it
|
||
|
2 FLOAT m_tmDelay = 5.0f, // delay between checking moments - set depending on distance of closest player
|
||
|
|
||
|
20 FLOAT m_fClosestPlayer = UpperLimit(0.0f), // distance from closest player to owner of this watcher
|
||
|
21 INDEX m_iPlayerToCheck = 0, // sequence number for checking next player in each turn
|
||
|
|
||
|
components:
|
||
|
functions:
|
||
|
class CEnemyBase *GetOwner(void)
|
||
|
{
|
||
|
ASSERT(m_penOwner!=NULL);
|
||
|
return (CEnemyBase*)&*m_penOwner;
|
||
|
}
|
||
|
|
||
|
// find one player number by random
|
||
|
INDEX GetRandomPlayer(void)
|
||
|
{
|
||
|
// CPrintF("Getting random number... ");
|
||
|
// get maximum number of players in game
|
||
|
INDEX ctMaxPlayers = GetMaxPlayers();
|
||
|
// find actual number of players
|
||
|
INDEX ctActivePlayers = 0;
|
||
|
{for(INDEX i=0; i<ctMaxPlayers; i++) {
|
||
|
if (GetPlayerEntity(i)!=NULL) {
|
||
|
ctActivePlayers++;
|
||
|
}
|
||
|
}}
|
||
|
// CPrintF("active players %d, ", ctActivePlayers);
|
||
|
// if none
|
||
|
if (ctActivePlayers==0) {
|
||
|
// return first index anyway
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
|
||
|
// choose one by random
|
||
|
INDEX iChosenActivePlayer = IRnd()%ctActivePlayers;
|
||
|
// CPrintF("chosen %d, ", iChosenActivePlayer);
|
||
|
|
||
|
// find its physical index
|
||
|
INDEX iActivePlayer = 0;
|
||
|
{for(INDEX i=0; i<ctMaxPlayers; i++) {
|
||
|
if (GetPlayerEntity(i)!=NULL) {
|
||
|
if (iActivePlayer==iChosenActivePlayer) {
|
||
|
// CPrintF("actual index %d\n", iActivePlayer);
|
||
|
return i;
|
||
|
}
|
||
|
iActivePlayer++;
|
||
|
}
|
||
|
}}
|
||
|
ASSERT(FALSE);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
// find closest player
|
||
|
CEntity *FindClosestPlayer(void)
|
||
|
{
|
||
|
CEntity *penClosestPlayer = NULL;
|
||
|
FLOAT fClosestPlayer = UpperLimit(0.0f);
|
||
|
// for all players
|
||
|
for (INDEX iPlayer=0; iPlayer<GetMaxPlayers(); iPlayer++) {
|
||
|
CEntity *penPlayer = GetPlayerEntity(iPlayer);
|
||
|
// if player is alive and visible
|
||
|
if (penPlayer!=NULL && penPlayer->GetFlags()&ENF_ALIVE && !(penPlayer->GetFlags()&ENF_INVISIBLE)) {
|
||
|
// calculate distance to player
|
||
|
FLOAT fDistance =
|
||
|
(penPlayer->GetPlacement().pl_PositionVector-m_penOwner->GetPlacement().pl_PositionVector).Length();
|
||
|
// update if closer
|
||
|
if (fDistance<fClosestPlayer) {
|
||
|
fClosestPlayer = fDistance;
|
||
|
penClosestPlayer = penPlayer;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
// if no players found
|
||
|
if (penClosestPlayer==NULL) {
|
||
|
// behave as if very close - must check for new ones
|
||
|
fClosestPlayer = 10.0f;
|
||
|
}
|
||
|
m_fClosestPlayer = fClosestPlayer;
|
||
|
return penClosestPlayer;
|
||
|
}
|
||
|
|
||
|
// notify owner that a player has been seen
|
||
|
void SendWatchEvent(CEntity *penPlayer)
|
||
|
{
|
||
|
EWatch eWatch;
|
||
|
eWatch.penSeen = penPlayer;
|
||
|
m_penOwner->SendEvent(eWatch);
|
||
|
}
|
||
|
|
||
|
void CheckIfPlayerVisible(void)
|
||
|
{
|
||
|
// if the owner is blind
|
||
|
if( GetOwner()->m_bBlind) {
|
||
|
// don't even bother checking
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// get maximum number of players in game
|
||
|
INDEX ctPlayers = GetMaxPlayers();
|
||
|
// find first one after current sequence
|
||
|
CEntity *penPlayer = NULL;
|
||
|
m_iPlayerToCheck = (m_iPlayerToCheck+1)%ctPlayers;
|
||
|
INDEX iFirstChecked = m_iPlayerToCheck;
|
||
|
FOREVER {
|
||
|
penPlayer = GetPlayerEntity(m_iPlayerToCheck);
|
||
|
if (penPlayer!=NULL) {
|
||
|
break;
|
||
|
}
|
||
|
m_iPlayerToCheck++;
|
||
|
m_iPlayerToCheck%=ctPlayers;
|
||
|
if (m_iPlayerToCheck==iFirstChecked) {
|
||
|
return; // we get here if there are no players at all
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// if this one is dead or invisible
|
||
|
if (!(penPlayer->GetFlags()&ENF_ALIVE) || (penPlayer->GetFlags()&ENF_INVISIBLE)) {
|
||
|
// do nothing
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// if inside view angle and visible
|
||
|
if (GetOwner()->SeeEntity(penPlayer, Cos(GetOwner()->m_fViewAngle/2.0f))) {
|
||
|
// send event to owner
|
||
|
SendWatchEvent(penPlayer);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// set new watch time
|
||
|
void SetWatchDelays(void)
|
||
|
{
|
||
|
const FLOAT tmMinDelay = 0.1f; // delay at closest distance
|
||
|
const FLOAT tmSeeDelay = 5.0f; // delay at see distance
|
||
|
const FLOAT tmTick = _pTimer->TickQuantum;
|
||
|
FLOAT fSeeDistance = GetOwner()->m_fIgnoreRange;
|
||
|
FLOAT fNearDistance = Min(GetOwner()->m_fStopDistance, GetOwner()->m_fCloseDistance);
|
||
|
|
||
|
// if closer than near distance
|
||
|
if (m_fClosestPlayer<=fNearDistance) {
|
||
|
// always use minimum delay
|
||
|
m_tmDelay = tmMinDelay;
|
||
|
// if further than near distance
|
||
|
} else {
|
||
|
// interpolate between near and see
|
||
|
m_tmDelay = tmMinDelay+
|
||
|
(m_fClosestPlayer-fNearDistance)*(tmSeeDelay-tmMinDelay)/(fSeeDistance-fNearDistance);
|
||
|
// round to nearest tick
|
||
|
m_tmDelay = floor(m_tmDelay/tmTick)*tmTick;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// watch
|
||
|
void Watch(void)
|
||
|
{
|
||
|
// remember original distance
|
||
|
FLOAT fOrgDistance = m_fClosestPlayer;
|
||
|
|
||
|
// find closest player
|
||
|
CEntity *penClosest = FindClosestPlayer();
|
||
|
|
||
|
FLOAT fSeeDistance = GetOwner()->m_fIgnoreRange;
|
||
|
FLOAT fStopDistance = Max(fSeeDistance*1.5f, GetOwner()->m_fActivityRange);
|
||
|
|
||
|
// if players exited enemy's scope
|
||
|
if (fOrgDistance<fStopDistance && m_fClosestPlayer>=fStopDistance) {
|
||
|
// stop owner
|
||
|
m_penOwner->SendEvent(EStop());
|
||
|
// if players entered enemy's scope
|
||
|
} else if (fOrgDistance>=fStopDistance && m_fClosestPlayer<fStopDistance) {
|
||
|
// start owner
|
||
|
m_penOwner->SendEvent(EStart());
|
||
|
}
|
||
|
|
||
|
// if the closest player is close enough to be seen
|
||
|
if (m_fClosestPlayer<fSeeDistance) {
|
||
|
// check for seeing any of the players
|
||
|
CheckIfPlayerVisible();
|
||
|
}
|
||
|
|
||
|
// if the closest player is inside sense range
|
||
|
FLOAT fSenseRange = GetOwner()->m_fSenseRange;
|
||
|
if (penClosest!=NULL && fSenseRange>0 && m_fClosestPlayer<fSenseRange) {
|
||
|
// detect it immediately
|
||
|
SendWatchEvent(penClosest);
|
||
|
}
|
||
|
|
||
|
// set new watch time
|
||
|
SetWatchDelays();
|
||
|
};
|
||
|
|
||
|
// this is called directly from enemybase to check if another player has come too close
|
||
|
CEntity *CheckCloserPlayer(CEntity *penCurrentTarget, FLOAT fRange)
|
||
|
{
|
||
|
// if the owner is blind
|
||
|
if( GetOwner()->m_bBlind) {
|
||
|
// don't even bother checking
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
CEntity *penClosestPlayer = NULL;
|
||
|
FLOAT fClosestPlayer =
|
||
|
(penCurrentTarget->GetPlacement().pl_PositionVector-m_penOwner->GetPlacement().pl_PositionVector).Length();
|
||
|
fClosestPlayer = Min(fClosestPlayer, fRange); // this is maximum considered range
|
||
|
|
||
|
// for all other players
|
||
|
for (INDEX iPlayer=0; iPlayer<GetMaxPlayers(); iPlayer++) {
|
||
|
CEntity *penPlayer = GetPlayerEntity(iPlayer);
|
||
|
if (penPlayer==NULL || penPlayer==penCurrentTarget) {
|
||
|
continue;
|
||
|
}
|
||
|
// if player is alive and visible
|
||
|
if ((penPlayer->GetFlags()&ENF_ALIVE) && !(penPlayer->GetFlags()&ENF_INVISIBLE)) {
|
||
|
// calculate distance to player
|
||
|
FLOAT fDistance =
|
||
|
(penPlayer->GetPlacement().pl_PositionVector-m_penOwner->GetPlacement().pl_PositionVector).Length();
|
||
|
// if closer than current and you can see him
|
||
|
if (fDistance<fClosestPlayer &&
|
||
|
GetOwner()->SeeEntity(penPlayer, Cos(GetOwner()->m_fViewAngle/2.0f))) {
|
||
|
// update
|
||
|
fClosestPlayer = fDistance;
|
||
|
penClosestPlayer = penPlayer;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return penClosestPlayer;
|
||
|
}
|
||
|
|
||
|
// this is called directly from enemybase to attack multiple players (for really big enemies)
|
||
|
CEntity *CheckAnotherPlayer(CEntity *penCurrentTarget)
|
||
|
{
|
||
|
// if the owner is blind, or no current target
|
||
|
if( GetOwner()->m_bBlind || penCurrentTarget==NULL) {
|
||
|
// don't even check
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
// get allowed distance
|
||
|
CEntity *penClosestPlayer = NULL;
|
||
|
FLOAT fCurrentDistance =
|
||
|
(penCurrentTarget->GetPlacement().pl_PositionVector-m_penOwner->GetPlacement().pl_PositionVector).Length();
|
||
|
FLOAT fRange = fCurrentDistance*1.5f;
|
||
|
|
||
|
// find a random offset to start searching
|
||
|
INDEX iOffset = GetRandomPlayer();
|
||
|
|
||
|
// for all other players
|
||
|
INDEX ctPlayers = GetMaxPlayers();
|
||
|
for (INDEX iPlayer=0; iPlayer<ctPlayers; iPlayer++) {
|
||
|
CEntity *penPlayer = GetPlayerEntity((iPlayer+iOffset)%ctPlayers);
|
||
|
if (penPlayer==NULL || penPlayer==penCurrentTarget) {
|
||
|
continue;
|
||
|
}
|
||
|
// if player is alive and visible
|
||
|
if ((penPlayer->GetFlags()&ENF_ALIVE) && !(penPlayer->GetFlags()&ENF_INVISIBLE)) {
|
||
|
// calculate distance to player
|
||
|
FLOAT fDistance =
|
||
|
(penPlayer->GetPlacement().pl_PositionVector-m_penOwner->GetPlacement().pl_PositionVector).Length();
|
||
|
// if inside allowed range and visible
|
||
|
if (fDistance<fRange &&
|
||
|
GetOwner()->SeeEntity(penPlayer, Cos(GetOwner()->m_fViewAngle/2.0f))) {
|
||
|
// attack that one
|
||
|
return penPlayer;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return penCurrentTarget;
|
||
|
}
|
||
|
|
||
|
procedures:
|
||
|
// watching
|
||
|
Active() {
|
||
|
// repeat
|
||
|
while (TRUE) {
|
||
|
// check all players
|
||
|
Watch();
|
||
|
// wait for given delay
|
||
|
wait(m_tmDelay) {
|
||
|
on (EBegin) : { resume; }
|
||
|
on (ETimer) : { stop; }
|
||
|
// stop looking
|
||
|
on (EStop) : { jump Inactive(); }
|
||
|
// force re-checking if receiving start or teleport
|
||
|
on (EStart) : { stop; }
|
||
|
on (ETeleport) : { stop; }
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// not watching
|
||
|
Inactive(EVoid) {
|
||
|
wait() {
|
||
|
on (EBegin) : { resume; }
|
||
|
on (EStart) : { jump Active(); }
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// dummy mode
|
||
|
Dummy(EVoid)
|
||
|
{
|
||
|
// ignores all events forever
|
||
|
wait() {
|
||
|
on (EBegin) : { resume; }
|
||
|
otherwise() : { resume; };
|
||
|
};
|
||
|
}
|
||
|
|
||
|
Main(EWatcherInit eInit) {
|
||
|
// remember the initial parameters
|
||
|
ASSERT(eInit.penOwner!=NULL);
|
||
|
m_penOwner = eInit.penOwner;
|
||
|
|
||
|
// init as nothing
|
||
|
InitAsVoid();
|
||
|
SetPhysicsFlags(EPF_MODEL_IMMATERIAL);
|
||
|
SetCollisionFlags(ECF_IMMATERIAL);
|
||
|
|
||
|
// if in flyover game mode
|
||
|
if (GetSP()->sp_gmGameMode == CSessionProperties::GM_FLYOVER) {
|
||
|
// go to dummy mode
|
||
|
jump Dummy();
|
||
|
// NOTE: must not destroy self, because owner has a pointer
|
||
|
}
|
||
|
|
||
|
// generate random number of player to check next
|
||
|
// (to provide even distribution of enemies among players)
|
||
|
m_iPlayerToCheck = GetRandomPlayer()-1;
|
||
|
|
||
|
// start in disabled state
|
||
|
autocall Inactive() EEnd;
|
||
|
|
||
|
// cease to exist
|
||
|
Destroy();
|
||
|
|
||
|
return;
|
||
|
};
|
||
|
};
|