/* -------------------------------------------------------------- LD Placeable NPC actor. Can be controlled by AI Scripts (for non-combat behaviour). Or with the KF_StoryNPC_AI controller for simple combat AI. Author : Alex Quick -------------------------------------------------------------- */ class KF_StoryNPC extends KFHumanPawn placeable hidecategories(DeRes,Bob,Blur,UDamage); var () localized array NPCDialogue; var () localized string NPCName; var KF_DialogueSpot DialogueTrigger; var () bool bRunning; var name RunningAnims[8]; var () float BaseGroundSpeed; var () float RespawnTime; var () bool bStartActive; var () bool bIndestructible; var (AI) int TeamIndex; var (AI) bool bFireAtWill; var (AI) float BaseAIThreatRating; var (AI) array< class > OnlyThreateningTo,NotThreateningTo; var (AI) bool bNoThreatToZEDs; var bool bShotAnim; var () bool bHasInfiniteAmmo; var bool bInitialFireAtWill; var () bool bDropInventoryOnDeath; /* If true, this pawn is marked Hidden while inactive. */ var (Display) bool bOnlyVisibleWhenActive; /* Initial location this NPC was placed at by the L.D */ var vector PlacedLoc; var rotator PlacedRot; /* Modifier to apply to incoming damage from friendly pawns */ var() float FriendlyFireDamageScale; var() bool bShowHealthBar; var(Movement) bool bUseDefaultPhysics; var() float NPCHealth; var() float StartingHealthPct; var() bool bDamageable; var bool bActive; var bool bInitialActive; /* Event to fire off when this NPC becomes 'Active' */ var(Events) name ActivationEvent,DeActivationEvent,HealedEvent; var(Pawn) bool bUseHitPoints; enum ENPCTriggerAction { TA_ToggleHoldFire, TA_CommitSuicide, TA_ToggleActive, }; var () ENPCTriggerAction TriggerAction; replication { reliable if(Role == Role_Authority) bActive; } /* Stub. The objective just changed. Called from KFStoryGameinfo.SetActiveObjective() */ function OnObjectiveChanged(name OldObjectiveName, name NewObjectiveName){} function SetMovemetPhysics() { if(!bUseDefaultPhysics) { Super.SetMovementPhysics(); } } function bool HealDamage(int Amount, Controller Healer, class DamageType) { Health = Min(Health + Amount, HealthMax); return true; } function ProcessLocationalDamage(int Damage, Pawn instigatedBy, Vector hitlocation, Vector momentum, class damageType, array PointsHit ) { if(!bUseHitPoints) { TakeDamage(Damage,instigatedBy,HitLocation,Momentum,damageType); } else { Super.ProcessLocationalDamage(Damage,InstigatedBy,HitLocation,Momentum,DamageType,PointsHit); } } function SaveHealthState(){} function Reset() { bFireAtWill = bInitialFireAtWill ; SetActive(bInitialActive); ResurrectNPC(); } simulated function PostBeginPlay() { local AIScript A; Super.PostbeginPlay(); SpawnDialogueTrigger(); bInitialFireAtWill = bFireAtWill; if(bStartActive) { SetActive(true); } if(bOnlyVisibleWhenActive) { UpdateVisibility(bActive); } bInitialActive = bActive; PlacedLoc = Location; PlacedRot = Rotation; GroundSpeed = BaseGroundSpeed; MenuName = NPCName; Health = NPCHealth * StartingHealthPct; HealthMax = NPCHealth; // automatically add controller to pawns which were placed in level // NOTE: pawns spawned during gameplay are not automatically possessed by a controller if ( (Health > 0) && !bDontPossess ) { // check if I have an AI Script if ( AIScriptTag != '' ) { ForEach AllActors(class'AIScript',A,AIScriptTag) break; // let the AIScript spawn and init my controller if ( A != None ) { A.SpawnControllerFor(self); if ( Controller != None ) return; } } if ( (ControllerClass != None) && (Controller == None) ) Controller = spawn(ControllerClass); if ( Controller != None ) { Controller.Possess(self); AIController(Controller).Skill += SkillModifier; } } } function ResurrectNPC() { if(Health < NPCHealth) { Health = NPCHealth * StartingHealthPct; HealthMax = NPCHealth; SetCollision(true,true); bHidden = false; RepositionNPC(); } } function RepositionNPC() { if(bMovable) { SetRotation(PlacedRot); SetLocation(PlacedLoc); } } function SpawnDialogueTrigger() { local int i; if(DialogueTrigger == none && NPCDialogue.length > 0) { DialogueTrigger = Spawn(class 'KF_DialogueSpot'); DialogueTrigger.bTouchTriggered = true; DialogueTrigger.bRandomize = true; DialogueTrigger.bHardAttach = true; DialogueTrigger.SetBase(self); DialogueTrigger.SetCollisionSize(CollisionRadius * 1.5,CollisionHeight) ; for(i = 0 ; i < NPCDialogue.length ; i ++) { DialogueTrigger.Dialogues.length = i + 1; DialogueTrigger.Dialogues[i].Display.Dialogue_text = NPCDialogue[i]; DialogueTrigger.Dialogues[i].Display.Dialogue_header = NPCName; DialogueTrigger.Dialogues[i].BroadcastScope = InstigatorOnly; DialogueTrigger.Dialogues[i].VoiceOver.SourceActor = self; } } } /* Doing this here instead of in Gameinfo.ReduceDamage() because of how hilariously hacked up KFGameType's implementation of that function is ... */ simulated function TakeDamage(int Damage,pawn instigatedBy, Vector HitLocation, vector Momentum, class damageType, optional int HitIndex) { local int ReducedDamage; /* log("=============================================================",'Story_Debug'); log(self@"Took Damage : "@Damage@" from : "@instigatedBy@" Am I damageable ? : "@bDamageable@" Current Health : "@Health,'Story_Debug'); */ if(!bDamageable || Health <= 0) { return; } ReducedDamage = Damage; // friendly fire ... scale down the damage if(/*instigatedBy.Controller.GetTeamNum() == Controller.GetTeamNum()*/ PlayerController(instigatedBy.Controller) != none) // Yeah looks like this guy's never actually getting assigned a correct team. { ReducedDamage *= FriendlyFireDamageScale; } Super.TakeDamage(ReducedDamage,InstigatedBy,HitLocation,Momentum,damageType,HitIndex); } function bool GiveHealth(int HealAmount, int HealMax) { local bool bHealed; bHealed = Super.GiveHealth(HealAmount,HealMax); if(HealedEvent != '' && HealAmount > 0 && bHealed) { TriggerEvent(HealedEvent,self,Instigator); } return bHealed; } /* function Died(Controller Killer, class damageType, vector HitLocation) { if(!bPlayedDeath && !bIndestructible) { SpawnDummyCorpse(Killer,damageType,HitLocation); } } simulated function PlayDying(class DamageType, vector HitLoc) { Super(xPawn).PlayDying(DamageType,HitLoc); }*/ simulated function SpawnDummyCorpse(Controller Killer, class damageType, vector HitLocation) { local KFPawn DummyCorpse; local int i, EquipLength; SetCollision(false,false); bHidden = true; DummyCorpse = Spawn(class 'KFHumanPawn',,,Location,Rotation); if(DummyCorpse != none) { DummyCorpse.LinkMesh(Mesh); DummyCorpse.tag = tag; DummyCorpse.Event = Event; if(bDropInventoryOnDeath) { EquipLength = arraycount(RequiredEquipment); for(i = 0 ; i < EquipLength ; i ++) { DummyCorpse.RequiredEquipment[i] = RequiredEquipment[i] ; } DummyCorpse.AddDefaultInventory(); } DummyCorpse.Died(Killer,damageType,HitLocation); } } function Trigger( Actor Other, Pawn EventInstigator ) { switch(TriggerAction) { case TA_CommitSuicide : Died(Controller,class 'Suicided', Location) ; break; case TA_ToggleHoldFire : bFireAtWill = !bFireAtWill; break; case TA_ToggleActive : SetActive(!bActive); break; } } function UpdateVisibility(bool On) { if(bOnlyVisibleWhenActive) { bHidden = !On; SetCollision(!bHidden,!bHidden); } } /* turns this NPC on / off ie. Makes it damageable and decides whether AI ignores it Network : Server */ function SetActive(bool On) { if(Role < Role_Authority) { return; } UpdateVisibility(On); bActive = On; bDamageable = bActive; log(self@" is now bActive : "@On,'Story_Debug'); if(On && ActivationEvent != '') { TriggerEvent(ActivationEvent,self,self); } else if(!On && DeActivationEvent != '') { TriggerEvent(DeActivationEvent,self,self); } } simulated function int GetTeamNum() { return TeamIndex; } /* === AI functions =====================================*/ function float AssessThreatTo(KFMonsterController Monster, optional bool CheckDistance) { if(bNoThreatToZEDs || TeamIndex == 255 || Health <= 0 || !bActive || !bDamageable || (!IsThreateningTo(Monster.Pawn)) ) { return -1.f; } return Super.AssessThreatTo(Monster,CheckDistance) + BaseAIThreatRating ; } /* Returns true if the supplied Monster considers this NPC a threat (and is prepared to attack it) */ function bool IsThreateningTo( Pawn Monster) { local int i; local bool Result; if(Monster == none) { return false; } Result = true; for(i = 0 ; i < NotThreateningTo.length ; i ++) { if(ClassisChildOf(Monster.class,NotThreateningTo[i])) { Result = false; break; } } for(i = 0 ; i < OnlyThreateningTo.length ; i ++) { Result = false; if(ClassisChildOf(Monster.class,OnlyThreateningTo[i])) { Result = true; break; } } return Result; } function bool IsPacifist() { local Inventory Inv; local int Count; local Weapon Weap; local bool bUsingLethalGear; for( Inv=Inventory; Inv!=None && Count < 1000; Inv=Inv.Inventory ) { Weap = Weapon(Inv); bUsingLethalGear = Weap != none && (Weap.bMeleeWeapon || Weap.HasAmmo()) ; if(bUsingLethalGear && bFireAtWill) { return false; } Count++; } return true; } simulated event PostRender2D(Canvas C, float ScreenLocX, float ScreenLocY) // called if bScriptPostRender is true, overrides native team beacon drawing code { local PlayerController PC; local float Opacity; // local float Dist; local HUDKillingFloor KFHUD; local vector CameraLocation,CamDir; local rotator CameraRotation; if(!bShowHealthBar || !bActive || GetStateName() == 'Dying') { return; } PC = Level.GetLocalPlayerController(); if ( PC != None ) { KFHUD = HUDKillingFloor(PC.myHUD); if(KFHUD != none) { C.GetCameraLocation(CameraLocation, CameraRotation); CamDir = vector(CameraRotation); /* Rendering behind us... */ if ( (Normal(Location - CameraLocation) dot CamDir) < 0 ) { return; } Opacity = FClamp(1.f - (VSize(PC.CalcViewLocation - Location) / 3000.f),0.25f,1.f) ; KFHUD.DrawKFBar(C,ScreenLocX,ScreenLocY,Health/HealthMax,Byte(Opacity * 255.f),false); } } } function RosterEntry GetPlacedRoster() { return None; } function string GetPlayerName() { return NPCName; } defaultproperties { /* AccelRate=1000.0 AirControl=+0.15 GroundSpeed=200.0 WaterSpeed=200.000000 AirSpeed=230.000000 JumpZ=325.000000 MaxFallSpeed=600.000000 BaseEyeHeight=44.0 EyeHeight=44.0 CrouchHeight=34.000000 CollisionHeight=50.000000 Mesh=SkeletalMesh'KF_Soldier_Trip.Punk_Harold' MovementAnims(0) ="WalkF_HuskGun" MovementAnims(1) ="WalkB_HuskGun" MovementAnims(2) ="WalkL_HuskGun" MovementAnims(3) ="WalkR_HuskGun" RunningAnims(0) = "JogF_Bullpup" RunningAnims(1) = "JogB_Bullpup" RunningAnims(2) = "JogL_Bullpup" RunningAnims(3) = "JogR_Bullpup" IdleWeaponAnim="Idle_Knife" IdleRestAnim="Idle_Knife" TurnLeftAnim="TurnL" TurnRightAnim="TurnR" */ NPCName ="An NPC" bNoDelete = true bFireAtWill = true ControllerClass = class 'KF_StoryNPC_AI' TeamIndex = 0 NPCHealth = 100 FriendlyFireDamageScale = 1.f bDropInventoryOnDeath = true bStartActive = true bDamageable = true StartingHealthPct = 1.f bAlwaysRelevant = true bUseHitPoints = true TriggerAction = TA_ToggleActive }