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 { }