256 lines
7.4 KiB
Ucode
256 lines
7.4 KiB
Ucode
class FriendlyMonsterController extends MonsterController;
|
|
|
|
var Controller Master;
|
|
var Emitter Effect;
|
|
|
|
function Possess(Pawn aPawn)
|
|
{
|
|
Super(ScriptedController).Possess(aPawn);
|
|
InitializeSkill(DeathMatch(Level.Game).AdjustedDifficulty);
|
|
Pawn.MaxFallSpeed = 1.1 * Pawn.default.MaxFallSpeed; // so bots will accept a little falling damage for shorter routes
|
|
Pawn.SetMovementPhysics();
|
|
if (Pawn.Physics == PHYS_Walking)
|
|
Pawn.SetPhysics(PHYS_Falling);
|
|
enable('NotifyBump');
|
|
}
|
|
|
|
|
|
function SetMaster(Controller NewMaster)
|
|
{
|
|
|
|
Master = NewMaster;
|
|
if (Master.PlayerReplicationInfo != None && Master.PlayerReplicationInfo.Team != None)
|
|
{
|
|
PlayerReplicationInfo = spawn(class'PlayerReplicationInfo', self);
|
|
PlayerReplicationInfo.PlayerName = Master.PlayerReplicationInfo.PlayerName$"'s"@Pawn.GetHumanReadableName();
|
|
PlayerReplicationInfo.bIsSpectator = true;
|
|
PlayerReplicationInfo.bBot = true;
|
|
PlayerReplicationInfo.Team = Master.PlayerReplicationInfo.Team;
|
|
PlayerReplicationInfo.RemoteRole = ROLE_None;
|
|
Pawn.PlayerReplicationInfo = PlayerReplicationInfo;
|
|
Pawn.bNoTeamBeacon = true;
|
|
}
|
|
|
|
//Effect = Pawn.spawn(class'FriendlyMonsterEffect', Pawn);
|
|
// Effect.SetBase(Pawn);
|
|
//Effect.MasterPRI = Master.PlayerReplicationInfo;
|
|
//Effect.Initialize();
|
|
|
|
}
|
|
|
|
function bool FindNewEnemy()
|
|
{
|
|
local Pawn BestEnemy;
|
|
local float BestDist;
|
|
local Controller C;
|
|
|
|
BestDist = 50000.f;
|
|
for (C = Level.ControllerList; C != None; C = C.NextController)
|
|
if ( C != Master && C != self && C.Pawn != None && (FriendlyMonsterController(C) == None || FriendlyMonsterController(C).Master != Master) && !C.SameTeamAs(Master)
|
|
&& VSize(C.Pawn.Location - Pawn.Location) < BestDist && !Monster(Pawn).SameSpeciesAs(C.Pawn) && CanSee(C.Pawn) )
|
|
{
|
|
BestEnemy = C.Pawn;
|
|
BestDist = VSize(C.Pawn.Location - Pawn.Location);
|
|
}
|
|
|
|
if ( BestEnemy == Enemy )
|
|
return false;
|
|
|
|
if ( BestEnemy != None )
|
|
{
|
|
ChangeEnemy(BestEnemy, true);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function bool SetEnemy(Pawn NewEnemy, optional bool bThisIsNeverUsed, optional float MonsterHateChanceOverride)
|
|
{
|
|
local float EnemyDist;
|
|
|
|
if (NewEnemy == None || NewEnemy.Health <= 0 || NewEnemy.Controller == None || NewEnemy == Enemy)
|
|
return false;
|
|
if ( Master != None && ( (Master.Pawn != None && NewEnemy == Master.Pawn)
|
|
|| (FriendlyMonsterController(NewEnemy.Controller) != None && FriendlyMonsterController(NewEnemy.Controller).Master == Master) ) )
|
|
return false;
|
|
if (NewEnemy.Controller.SameTeamAs(Master) || !CanSee(NewEnemy))
|
|
return false;
|
|
|
|
if (Enemy == None)
|
|
{
|
|
ChangeEnemy(NewEnemy, CanSee(NewEnemy));
|
|
return true;
|
|
}
|
|
|
|
EnemyDist = VSize(Enemy.Location - Pawn.Location);
|
|
if ( EnemyDist < Pawn.MeleeRange )
|
|
return false;
|
|
|
|
if ( EnemyDist > 1.7 * VSize(NewEnemy.Location - Pawn.Location))
|
|
{
|
|
ChangeEnemy(NewEnemy, CanSee(NewEnemy));
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function ChangeEnemy(Pawn NewEnemy, bool bCanSeeNewEnemy)
|
|
{
|
|
Super.ChangeEnemy(NewEnemy, bCanSeeNewEnemy);
|
|
|
|
//hack for invasion monsters so they'll fight back
|
|
if ( MonsterController(NewEnemy.Controller) != None && FriendlyMonsterController(NewEnemy.Controller) == None
|
|
&& (NewEnemy.Controller.Enemy == Master.Pawn || FRand() < 0.5) )
|
|
MonsterController(NewEnemy.Controller).ChangeEnemy(Pawn, NewEnemy.Controller.CanSee(Pawn));
|
|
}
|
|
|
|
function HearNoise(float Loudness, Actor NoiseMaker)
|
|
{
|
|
}
|
|
|
|
event SeePlayer(Pawn SeenPlayer)
|
|
{
|
|
if (Enemy == None && ((ChooseAttackCounter < 2) || (ChooseAttackTime != Level.TimeSeconds)) && SetEnemy(SeenPlayer))
|
|
WhatToDoNext(3);
|
|
if ( Enemy == SeenPlayer )
|
|
{
|
|
VisibleEnemy = Enemy;
|
|
EnemyVisibilityTime = Level.TimeSeconds;
|
|
bEnemyIsVisible = true;
|
|
}
|
|
}
|
|
|
|
function ExecuteWhatToDoNext()
|
|
{
|
|
bHasFired = false;
|
|
GoalString = "WhatToDoNext at "$Level.TimeSeconds;
|
|
if ( Pawn == None )
|
|
{
|
|
warn(GetHumanReadableName()$" WhatToDoNext with no pawn");
|
|
return;
|
|
}
|
|
|
|
// if we don't have a master or it switched teams, then we should die
|
|
if ( Master == None || Master.PlayerReplicationInfo == None || Master.PlayerReplicationInfo.bOnlySpectator
|
|
|| (PlayerReplicationInfo != None && PlayerReplicationInfo.Team != Master.PlayerReplicationInfo.Team) )
|
|
{
|
|
Pawn.Suicide();
|
|
return;
|
|
}
|
|
|
|
if ( bPreparingMove && Monster(Pawn).bShotAnim )
|
|
{
|
|
Pawn.Acceleration = vect(0,0,0);
|
|
GotoState('WaitForAnim');
|
|
return;
|
|
}
|
|
if (Pawn.Physics == PHYS_None)
|
|
Pawn.SetMovementPhysics();
|
|
if ( (Pawn.Physics == PHYS_Falling) && DoWaitForLanding() )
|
|
return;
|
|
if ( (Enemy != None) && ((Enemy.Health <= 0) || (Enemy.Controller == None)) )
|
|
Enemy = None;
|
|
|
|
if ( (Enemy == None) || !EnemyVisible() )
|
|
FindNewEnemy();
|
|
|
|
if ( Enemy != None )
|
|
ChooseAttackMode();
|
|
else if (Master.Pawn != None)
|
|
FollowMaster();
|
|
else
|
|
{
|
|
GoalString = "WhatToDoNext Wander or Camp at "$Level.TimeSeconds;
|
|
WanderOrCamp(true);
|
|
}
|
|
}
|
|
|
|
function FollowMaster()
|
|
{
|
|
if ( VSize(Master.Pawn.Location - Pawn.Location) > 1000 || VSize(Master.Pawn.Velocity) > Master.Pawn.WalkingPct * Master.Pawn.GroundSpeed
|
|
|| !LineOfSightTo(Master.Pawn) )
|
|
{
|
|
GoalString = "Follow Master "$Master.PlayerReplicationInfo.PlayerName;
|
|
if (FindBestPathToward(Master.Pawn, false, Pawn.bCanPickupInventory))
|
|
{
|
|
if ( Enemy != None )
|
|
GotoState('Fallback');
|
|
else
|
|
GotoState('Roaming');
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
GoalString = "Wander or Camp at "$Level.TimeSeconds;
|
|
WanderOrCamp(true);
|
|
}
|
|
|
|
|
|
|
|
function NotifyKilled(Controller Killer, Controller Killed, pawn KilledPawn)
|
|
{
|
|
if (Killer == self || Killer == Master)
|
|
Celebrate();
|
|
if (KilledPawn == Enemy)
|
|
{
|
|
Enemy = None;
|
|
FindNewEnemy();
|
|
}
|
|
}
|
|
|
|
function Destroyed()
|
|
{
|
|
if (PlayerReplicationInfo != None)
|
|
PlayerReplicationInfo.Destroy();
|
|
if (Effect != None)
|
|
Effect.Destroy();
|
|
|
|
Super.Destroyed();
|
|
}
|
|
|
|
state RestFormation
|
|
{
|
|
function BeginState()
|
|
{
|
|
Enemy = None;
|
|
Pawn.bCanJump = false;
|
|
Pawn.bAvoidLedges = true;
|
|
Pawn.bStopAtLedges = true;
|
|
Pawn.SetWalking(true);
|
|
MinHitWall += 0.15;
|
|
if (Master != None && Master.Pawn != None)
|
|
StartMonitoring(Master.Pawn, 1000);
|
|
}
|
|
}
|
|
|
|
state Fallback extends MoveToGoalWithEnemy
|
|
{
|
|
function MayFall()
|
|
{
|
|
Pawn.bCanJump = ( (MoveTarget != None)
|
|
&& ((MoveTarget.Physics != PHYS_Falling) || !MoveTarget.IsA('Pickup')) );
|
|
}
|
|
|
|
Begin:
|
|
SwitchToBestWeapon();
|
|
WaitForLanding();
|
|
|
|
Moving:
|
|
if (InventorySpot(MoveTarget) != None)
|
|
MoveTarget = InventorySpot(MoveTarget).GetMoveTargetFor(self,0);
|
|
MoveToward(MoveTarget,FaceActor(1),,ShouldStrafeTo(MoveTarget));
|
|
WhatToDoNext(14);
|
|
if ( bSoaking )
|
|
SoakStop("STUCK IN FALLBACK!");
|
|
goalstring = goalstring$" STUCK IN FALLBACK!";
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
defaultproperties
|
|
{
|
|
}
|