Prepare fixtures

This commit is contained in:
dkanus 2026-07-14 20:27:09 +07:00
commit 9c94356263
6021 changed files with 722805 additions and 22 deletions

View file

@ -0,0 +1,34 @@
// adds ammunition to current weapon
class ACTION_AddAmmo extends ScriptedAction;
var(Action) int AmmoToAdd;
var(Action) int FireMode;
enum EAmmoAmount
{
AMMO_Constant,
AMMO_Max,
AMMO_SuperMax
};
var(Action) EAmmoAmount AmmoAmount;
function bool InitActionFor(ScriptedController C)
{
if ( C.Pawn.Weapon != none ) {
switch ( AmmoAmount ) {
case AMMO_Constant: C.Pawn.Weapon.AddAmmo(AmmoToAdd, FireMode); break;
case AMMO_Max: C.Pawn.Weapon.MaxOutAmmo(); break;
case AMMO_SuperMax: C.Pawn.Weapon.SuperMaxOutAmmo(); break;
}
}
return false;
}
defaultproperties
{
ActionString="add ammo"
}

View file

@ -0,0 +1,43 @@
// adds health and shields to possesed Pawn or to matching Tag
class ACTION_AddShield extends ScriptedAction;
var(Action) class<Pawn> PawnClass; // leave none to heal possed pawn
var(Action) name PawnTag;
var(Action) int ShieldToAdd;
var(Action) int HealthToAdd;
var(Action) bool bDelayedHealing; // use GiveHealth() for KFPawns
function ProceedPawn(Pawn P)
{
if ( HealthToAdd > 0 ) {
if ( bDelayedHealing && KFPawn(P) != none )
KFPawn(P).GiveHealth(HealthToAdd, P.HealthMax);
else
P.Health = min(P.HealthMax, P.Health + HealthToAdd);
}
if ( ShieldToAdd > 0 )
P.AddShieldStrength(ShieldToAdd);
}
function bool InitActionFor(ScriptedController C)
{
local Actor A;
if ( PawnClass == none ) {
ProceedPawn(C.Pawn);
}
else {
ForEach C.DynamicActors(PawnClass, A, PawnTag)
ProceedPawn(Pawn(A));
}
return false;
}
defaultproperties
{
ActionString="add health and armor"
}

View file

@ -0,0 +1,71 @@
class ACTION_DamagePawn extends ScriptedAction;
/**
* Who damaged pawn?
* DMG_None - nobody (none)
* DMG_Instigator - ScriptedController.Instigator.Controller
* DMG_Myself - ScriptedController
* DMG_Himself - Victim
*/
enum EDamageInstigator
{
DMG_None,
DMG_Instigator,
DMG_Myself,
DMG_Himself
};
enum EDamageCalculation
{
DCALC_Amount, // damage is calculated using MinDamageAmount and MaxDamageAmount
DCALC_HealthPct, // damage is calculated using Victim's Health, MinDamagePct and MaxDamagePct
DCALC_HealthMaxPct, // damage is calculated using Victim's HealthMax, MinDamagePct and MaxDamagePct
};
var(Action) class<Pawn> VictimClass;
var(Action) name VictimTag;
var(Action) EDamageInstigator DamageInstigator;
var(Action) class<DamageType> DamageType;
var(Action) EDamageCalculation DamageCalculation;
var(Action) int MinDamageAmount, MaxDamageAmount;
var(Action) float MinDamagePct, MaxDamagePct; // used if MinDamageAmount==MaxDamageAmount==0
function bool InitActionFor(ScriptedController C)
{
local Actor A;
local Pawn Victim, Instigator;
local int Damage;
switch ( DamageInstigator ) {
case DMG_Instigator: Instigator = C.Instigator; break;
case DMG_Myself: Instigator = C.Pawn; break;
}
ForEach C.DynamicActors(VictimClass, A, VictimTag) {
Victim = Pawn(A);
if ( DamageInstigator == DMG_Himself )
Instigator = Victim;
switch ( DamageCalculation )
{
case DCALC_Amount: Damage = RandRange(MinDamageAmount, MaxDamageAmount); break;
case DCALC_HealthPct: Damage = Victim.Health * ( MinDamagePct + frand()*(MaxDamagePct - MinDamagePct) ); break;
case DCALC_HealthMaxPct: Damage = Victim.HealthMax * ( MinDamagePct + frand()*(MaxDamagePct - MinDamagePct) ); break;
}
Victim.TakeDamage(Damage, Instigator, Victim.Location, vect(0,0,0), DamageType);
}
return false;
}
function string GetActionString()
{
return ActionString @ GetItemName(String(VictimClass)) @ VictimTag;
}
defaultproperties
{
VictimClass=Class'Old2k4.Monster'
ActionString="Damage pawn"
}

View file

@ -0,0 +1,27 @@
// Toggles weapon's flashligh on / off
// Current weapon must have flashlight attached (Single, Shotgun etc.)
class ACTION_Flashlight extends ScriptedAction;
function bool InitActionFor(ScriptedController C)
{
local KFWeapon Weapon;
if ( (C.Pawn == None) || (C.Pawn.Weapon == None) )
return false;
Weapon = KFWeapon(C.Pawn.Weapon);
if ( Weapon == none || !Weapon.bTorchEnabled ) {
log(C.Pawn.Tag @ "trying to use flashlight on" @ C.Pawn.Weapon);
return false;
}
Weapon.LightFire();
Weapon.AdjustLightGraphic();
return false;
}
defaultproperties
{
ActionString="toggle weapon's flashlight"
}

View file

@ -0,0 +1,39 @@
// Proceeds if given actor count is in defined range
class ACTION_IfActorCount extends ScriptedAction;
var(Action) class<Actor> ActorClass;
var(Action) name ActorTag;
var(Action) int MinCount;
var(Action) int MaxCount;
function ProceedToNextAction(ScriptedController C)
{
local Actor Other;
local int ActorCount;
C.ActionNum += 1;
foreach C.DynamicActors(ActorClass, Other, ActorTag)
ActorCount++;
if ( ActorCount < MinCount || (MaxCount != 0 && ActorCount > MaxCount) )
ProceedToSectionEnd(C);
}
function bool StartsSection()
{
return true;
}
function string GetActionString()
{
if ( MaxCount == 0 )
return ActionString $ " > " $ MinCount;
return ActionString @ "["$MinCount$".."$MaxCount$"]";
}
defaultproperties
{
ActionString="If actor count"
}

View file

@ -0,0 +1,23 @@
// Same as ACTION_IfMonsterHasEnemy, but can be used with any Pawn (e.g. NPC)
class ACTION_IfHasEnemy extends ScriptedAction;
var(Action) float MaxDistance;
function ProceedToNextAction( ScriptedController C )
{
C.ActionNum += 1;
if ( C.Enemy != none && (C.Enemy.bDeleteMe || C.Enemy.Health <= 0) )
C.Enemy = none;
if ( C.Enemy == None || (MaxDistance > 0 && vsize(C.Pawn.Location - C.Enemy.Location) > MaxDistance + C.Enemy.CollisionRadius) )
ProceedToSectionEnd( C );
}
function bool StartsSection()
{
return true;
}
defaultproperties
{
}

View file

@ -0,0 +1,20 @@
// Same as ACTION_IfMonsterIsHurt, but can be used with any Pawn (e.g. NPC)
class ACTION_IfIsHurt extends ScriptedAction;
var() int HealthThreshold;
function ProceedToNextAction( ScriptedController C )
{
C.ActionNum += 1;
if ( C.Pawn.Health > HealthThreshold )
ProceedToSectionEnd( C );
}
function bool StartsSection()
{
return true;
}
defaultproperties
{
}

View file

@ -0,0 +1,23 @@
class ACTION_IfMyPlayerClose extends ScriptedAction;
var(Action) float MaxDistance;
function ProceedToNextAction( ScriptedController C )
{
local Pawn MyPlayer;
C.ActionNum += 1;
MyPlayer = C.GetMyPlayer();
if ( MyPlayer == none || vsize(MyPlayer.Location - C.Pawn.Location) > MaxDistance + C.Pawn.CollisionRadius + MyPlayer.CollisionRadius )
ProceedToSectionEnd( C );
}
function bool StartsSection()
{
return true;
}
defaultproperties
{
}

View file

@ -0,0 +1,68 @@
/*
--------------------------------------------------------------
ACTION_IfPlayerCount
--------------------------------------------------------------
Checks if player count is between MinPlayers and MaxPlayers (inclusive).
If not - goes to section end.
Setting MaxPlayers=0 (default) disables MaxPlayers restriction and only checks MinPlayers.
Set bCountOnlyAlive=True to exclude dead players
Author : PooSH
--------------------------------------------------------------
*/
class ACTION_IfPlayerCount extends ScriptedAction;
var(Action) int MinPlayers, MaxPlayers;
var(Action) bool bCountOnlyAlive; // all players should be counted or only alive?
//returns adjusted NumToSpawn to difficulty and number of players
function int GetPlayerCount(LevelInfo Level)
{
local int LivingCount;
local Controller C;
if ( !bCountOnlyAlive )
return Level.Game.NumPlayers;
for ( C=Level.ControllerList; C!=None; C=C.NextController ) {
if (C.PlayerReplicationInfo != None && C.bIsPlayer
&& !C.PlayerReplicationInfo.bOutOfLives && !C.PlayerReplicationInfo.bOnlySpectator )
LivingCount++;
}
return LivingCount;
}
function ProceedToNextAction(ScriptedController C)
{
local int PlayerCount;
C.ActionNum += 1;
PlayerCount = GetPlayerCount(C.Level);
if ( PlayerCount < MinPlayers || (MaxPlayers != 0 && PlayerCount > MaxPlayers) )
ProceedToSectionEnd(C);
}
function bool StartsSection()
{
return true;
}
function string GetActionString()
{
if ( MaxPlayers == 0 )
return ActionString $ " > " $ MinPlayers;
return ActionString $ " ["$MinPlayers$".."$MaxPlayers$"]";
}
defaultproperties
{
ActionString="If player count"
}

View file

@ -0,0 +1,28 @@
// Same as ACTION_IfMonsterSeesEnemy, but can be used with any Pawn (e.g. NPC)
// added MaxDistance
class ACTION_IfSeesEnemy extends ScriptedAction;
var(Action) float MaxDistance;
function ProceedToNextAction( ScriptedController C )
{
C.ActionNum += 1;
if ( C.Enemy != none && (C.Enemy.bDeleteMe || C.Enemy.Health <= 0) )
C.Enemy = none;
if ( C.Enemy == none || !C.CanSee(C.Enemy)
|| (MaxDistance > 0 && vsize(C.Pawn.Location - C.Enemy.Location) > MaxDistance + C.Enemy.CollisionRadius) )
{
ProceedToSectionEnd( C );
}
}
function bool StartsSection()
{
return true;
}
defaultproperties
{
}

View file

@ -0,0 +1,59 @@
class ACTION_KillPawn extends ScriptedAction;
/**
* Who killed pawn?
* KILLER_None - nobody (none)
* KILLER_Instigator - ScriptedController.Instigator.Controller
* KILLER_Myself - ScriptedController
* KILLER_Suicide - Victim's controller
*/
enum EKiller
{
KILLER_None,
KILLER_Instigator,
KILLER_Myself,
KILLER_Suicide
};
var(Action) class<Pawn> VictimClass;
var(Action) name VictimTag;
var(Action) class<DamageType> DamageType;
var(Action) EKiller Killer;
function bool InitActionFor(ScriptedController C)
{
local Actor A;
local Pawn Victim;
local Controller KillerController;
switch ( Killer ) {
case KILLER_Instigator:
if ( C.Instigator != none )
KillerController = C.Instigator.Controller;
break;
case KILLER_Myself:
KillerController = C;
break;
}
ForEach C.DynamicActors(VictimClass, A, VictimTag) {
Victim = Pawn(A);
if ( Killer == KILLER_Suicide )
KillerController = Victim.Controller;
Victim.Died(KillerController, DamageType, Victim.Location);
}
return false;
}
function string GetActionString()
{
return ActionString @ GetItemName(String(VictimClass)) @ VictimTag;
}
defaultproperties
{
VictimClass=Class'Old2k4.Monster'
ActionString="Kill pawn"
}

View file

@ -0,0 +1,38 @@
class ACTION_MoveToEnemyTimed extends LatentScriptedAction;
var(Action) float TimeLimit;
function bool MoveToGoal()
{
return true;
}
function Actor GetMoveTargetFor(ScriptedController C)
{
return C.Enemy;
}
function string GetActionString()
{
return ActionString@TimeLimit;
}
function bool InitActionFor(ScriptedController C)
{
if ( C.Enemy == none || C.Enemy.bDeleteMe || C.Enemy.Health <= 0 )
return false;
C.CurrentAction = self;
C.SetTimer(TimeLimit, false);
return true;
}
function bool CompleteWhenTimer()
{
return true;
}
defaultproperties
{
ActionString="Move to enemy or wait for timer"
}

View file

@ -0,0 +1,51 @@
// completes when pawn reaches player or on timer
class ACTION_MoveToPlayerTimed extends LatentScriptedAction;
var(Action) float TimeLimit;
// if distance between me and my player is lower that that, action is skipped.
// MinDistance doesn't include collision radiuses
var(Action) float MinDistance;
var(Action) float Cooldown; // time to wait till next action, if player is closer than MinDistance
var bool bNeedToMode;
function bool MoveToGoal()
{
return bNeedToMode;
}
function Actor GetMoveTargetFor(ScriptedController C)
{
return C.GetMyPlayer();
}
function string GetActionString()
{
return ActionString@TimeLimit;
}
function bool InitActionFor(ScriptedController C)
{
local Pawn MyPlayer;
C.CurrentAction = self;
MyPlayer = C.GetMyPlayer();
bNeedToMode = MyPlayer != none && (MinDistance <= 0 || vsize(C.Pawn.Location - MyPlayer.Location) >= MinDistance + C.Pawn.CollisionRadius + MyPlayer.CollisionRadius);
if ( bNeedToMode )
C.SetTimer(TimeLimit, false);
else
C.SetTimer(fmax(Cooldown, 0.01), false); // wait at least 1 tick
return true;
}
function bool CompleteWhenTimer()
{
return true;
}
defaultproperties
{
Cooldown=0.500000
ActionString="Move to player or wait for timer"
}

View file

@ -0,0 +1,24 @@
class ACTION_NPC_Active extends ScriptedAction;
var(Action) bool bActive;
var(Action) name NPC_Tag;
function bool InitActionFor(ScriptedController C)
{
local KF_StoryNPC P;
foreach C.DynamicActors(Class'KF_StoryNPC',P, NPC_Tag)
P.SetActive(bActive);
return false;
}
function string GetActionString()
{
return ActionString@bActive;
}
defaultproperties
{
ActionString="NPC active"
}

View file

@ -0,0 +1,26 @@
// can be used only on KF_StoryNPC_Spawnable_SE
class ACTION_NPC_Bleed extends ScriptedAction;
var(Action) bool bBleed;
var(Action) name NPC_Tag;
function bool InitActionFor(ScriptedController C)
{
local KF_StoryNPC_Spawnable_SE P;
foreach C.DynamicActors(Class'KF_StoryNPC_Spawnable_SE',P, NPC_Tag)
P.bBleed = bBleed;
return false;
}
function string GetActionString()
{
return ActionString@bBleed;
}
defaultproperties
{
ActionString="NPC bleed"
}

View file

@ -0,0 +1,43 @@
class ACTION_PlayKFReplicatedAmbientSound extends ACTION_PlayAmbientSound;
var(Action) edfindable protected Actor SoundActor; // actor to set ambientsound on
// used to find SoundActor, if it is not set. Tag must be unique!
var(Action) edfindable name SoundActorTag;
function bool InitActionFor(ScriptedController C)
{
// play appropriate sound
if ( AmbientSound != None )
{
if ( SoundActor == none )
foreach C.AllActors(class'Actor', SoundActor, SoundActorTag)
break;
if ( SoundActor != none ) {
// this probabaly isn't working,
// because actor must have those values set in defaultproperties
if ( SoundActor.RemoteRole < ROLE_SimulatedProxy )
SoundActor.RemoteRole = ROLE_SimulatedProxy;
SoundActor.bAlwaysRelevant = true; //
SoundActor.AmbientSound = AmbientSound;
SoundActor.SoundVolume = SoundVolume;
SoundActor.SoundPitch = SoundPitch;
SoundActor.SoundRadius = SoundRadius;
SoundActor.NetUpdateTime = SoundActor.Level.TimeSeconds - 1;
}
}
return false;
}
function string GetActionString()
{
return ActionString@AmbientSound;
}
defaultproperties
{
}

View file

@ -0,0 +1,32 @@
// Allows to adjust KFLevelRules_Story.Rules_Monsters
class ACTION_STORY_MonsterRules extends ScriptedAction;
/* absolute maximum number of zombies we can have in this story map at one time */
var(Action) int MaxEnemiesAtOnce;
/* should the game kill off ZEDs which haven't been seen by players for a while? */
var(Action) bool bAutoKillStragglers;
/* Auto Kill threshold if bAutoKilLStragglers is true */
var(Action) int MaxStragglers;
function bool InitActionFor(ScriptedController C)
{
local KFLevelRules_Story KFLR;
foreach C.AllActors(class'KFLevelRules_Story', KFLR) {
if ( MaxEnemiesAtOnce > 0 )
KFLR.MaxEnemiesAtOnce = MaxEnemiesAtOnce;
KFLR.bAutoKillStragglers = bAutoKillStragglers;
KFLR.MaxStragglers = MaxStragglers;
}
return false;
}
defaultproperties
{
bAutoKillStragglers=True
MaxStragglers=5
ActionString="story monster rules"
}

View file

@ -0,0 +1,79 @@
class ACTION_SetEnemy extends ScriptedAction;
enum EEnemyChanging
{
ECH_Never,
ECH_Always,
ECH_IfOutOfRange,
};
enum EEnemySelection
{
ESEL_First,
ESEL_Closest,
};
var(Action) class<Pawn> EnemyClass;
var(Action) name EnemyTag;
var(Action) bool bOnlyVisible;
var(Action) float MaxDistance;
var(Action) bool bSetViewTarget; // auto-set target on enemy
var(Action) EEnemyChanging AllowChangingEnemies;
var(Action) EEnemySelection EnemySelection;
function bool InitActionFor(ScriptedController C)
{
local Controller EC;
local float BestDist, CurDist;
if ( EnemyTag == 'Clear' ) {
C.Enemy = none;
if (bSetViewTarget)
C.ScriptedFocus = none;
}
else {
if ( C.Enemy != none && (C.Enemy.bDeleteMe || C.Enemy.Health <= 0) )
C.Enemy = none;
if ( C.Enemy != none && (AllowChangingEnemies == ECH_Never
|| (AllowChangingEnemies == ECH_IfOutOfRange
&& ((bOnlyVisible && !C.CanSee(C.Enemy)) || vsize(C.Pawn.Location - C.Enemy.Location) > MaxDistance))) )
{
return false;
}
BestDist = Square(MaxDistance);
for ( EC = C.Level.ControllerList; EC != none; EC = EC.nextController ) {
if ( EC.Pawn != none && ClassIsChildOf(EC.Pawn.class, EnemyClass)
&& !EC.Pawn.bPendingDelete && !EC.Pawn.bDeleteMe && EC.Pawn.Health > 0
&& (EC.Pawn.Tag == EnemyTag || EnemyTag == '') )
{
if ( bOnlyVisible && !C.CanSee(EC.Pawn) )
continue;
CurDist = VSizeSquared(C.Pawn.Location - EC.Pawn.Location);
if ( CurDist < BestDist ) {
C.Enemy = EC.Pawn;
BestDist = CurDist;
if ( EnemySelection == ESEL_First)
break;
}
}
}
if ( bSetViewTarget && C.Enemy != none )
C.ScriptedFocus = C.Enemy;
}
return false;
}
defaultproperties
{
EnemyClass=Class'Old2k4.Monster'
MaxDistance=500.000000
ActionString="set enemy"
bValidForTrigger=False
}

View file

@ -0,0 +1,47 @@
// Set Physics to all actors with designed Tag
class ACTION_SetPhysicsOther extends ScriptedAction;
var(Action) class<Actor> BaseClass;
var(Action) name OtherTag;
var(Action) Actor.EPhysics NewPhysicsMode;
var(Action) float NewNetUpdateFrequency; // in cases we need to force replication to clients, e.g. dropping down pickup
function bool InitActionFor(ScriptedController C)
{
local Actor Other;
local Pickup Pickup;
foreach C.AllActors(BaseClass, Other, OtherTag) {
Other.SetPhysics(NewPhysicsMode);
if ( Other.Level.NetMode != NM_StandAlone && NewPhysicsMode != PHYS_None ) {
if ( Other.NetUpdateFrequency < NewNetUpdateFrequency ) {
Other.NetUpdateFrequency = NewNetUpdateFrequency;
Other.NetUpdateTime = Other.Level.TimeSeconds - 1;
}
if ( NewPhysicsMode == PHYS_Falling) {
Pickup = Pickup(Other);
if ( Pickup != none ) {
// copied from Pickup.InitDroppedPickupFor()
Pickup.bOnlyReplicateHidden = false;
Pickup.bUpdateSimulatedPosition = true;
Pickup.bIgnoreEncroachers = false;
Pickup.NetUpdateFrequency = 8;
}
}
}
}
return false;
}
function string GetActionString()
{
return ActionString@NewPhysicsMode;
}
defaultproperties
{
baseClass=Class'Engine.Actor'
ActionString="change physics to "
}

View file

@ -0,0 +1,70 @@
/*
--------------------------------------------------------------
ACTION_SpawnRandomPickups
--------------------------------------------------------------
Spawns random number of pickups on the map.
All pickups must have the same tag.
Author : PooSH
--------------------------------------------------------------
*/
class ACTION_SpawnRandomPickups extends ScriptedAction;
var() class<xPickUpBase> PickupType;
var() name PickupTag;
var() int NumToSpawn;
var() float PlayerCountScale; // extra scaling per player above 1
//returns adjusted NumToSpawn to difficulty and number of players
function int GetAdjustedNumToSpawn(LevelInfo Level)
{
local int LivingCount;
local Controller C;
for ( C=Level.ControllerList; C!=None; C=C.NextController ) {
if (C.PlayerReplicationInfo != None && C.bIsPlayer
&& !C.PlayerReplicationInfo.bOutOfLives && !C.PlayerReplicationInfo.bOnlySpectator )
LivingCount++;
}
if ( LivingCount <= 1 )
return NumToSpawn;
return NumToSpawn * (1.0 + PlayerCountScale*(LivingCount-1));
}
function bool InitActionFor(ScriptedController C)
{
local Actor a;
local xPickUpBase x;
local array<xPickUpBase> xPickups;
local int i, count;
Count = GetAdjustedNumToSpawn(C.Level);
if ( Count <= 0 )
return false;
foreach C.AllActors(PickupType, a, PickupTag) {
x = xPickUpBase(a);
if ( x.myPickUp==none )
xPickups[xPickups.length] = x;
}
while ( xPickups.length > Count )
xPickups.remove(rand(xPickups.length), 1);
log("Spawning "$xPickups.length$" pickups of "$PickupTag$" ("$PickupType$")...");
for ( i=0; i<xPickups.length; ++i )
xPickups[i].SpawnPickup();
return false;
}
defaultproperties
{
PickupType=Class'Engine.xPickUpBase'
}

View file

@ -0,0 +1,121 @@
/**
Spawns zed (kfmonster). Respects current game event (KFMonstersCollection)
Supports custom monsters.
ZedKind What kind of zed should be spawned:
ZED_Custom specified by CustomZedClass
ZED_Boss spawn end game boss
ZED_Clot..ZED_Husk spawn standard monsters. Monster class will be
taken from KFMonstersCollection, so it supports events.
CustomZedClass Custom zed class name WITHOUT package name, e.g. "ZombieBrute".
Code will search KFMonstersCollection.MonsterClasses for a given class.
If class will no be found, monster will not spawn!
NewTag Tag to be assigned to the spawned monster
LocationTag Tag of the spawn location
RotateToTag Tag of the actor for spawned zed to loot at
bOffsetFromScriptedPawn If true, scripted pawn will be used for offset calculation.
LocationTag has higher priority than bOffsetFromScriptedPawn.
LocationOffset Spawn offset from the chosen location
RotationOffset Offset from the chosen rotation
@author PooSH
*/
class ACTION_SpawnZED extends ScriptedAction;
enum EZEDKind {
ZED_Custom,
ZED_Boss,
ZED_Clot,
ZED_Crawler,
ZED_Gorefast,
ZED_Stalker,
ZED_Scrake,
ZED_Fleshpound,
ZED_Bloat,
ZED_Siren,
ZED_Husk,
};
var(Action) EZEDKind ZedKind;
var(Action) string CustomZedClass;
var(Action) name NewTag;
var(Action) name LocationTag;
var(Action) name RotateToTag;
var(Action) bool bOffsetFromScriptedPawn;
var(Action) vector LocationOffset;
var(Action) rotator RotationOffset;
function bool InitActionFor(ScriptedController C)
{
local vector L;
local rotator R;
local actor a;
local class<KFMonstersCollection> MC;
local class<KFMonster> MonsterClass;
local int i;
if ( KFGameType(C.Level.Game) == none ) {
log("ACTION_SpawnZED can be used only with KFGameType!");
return false;
}
MC = KFGameType(C.Level.Game).MonsterCollection;
if ( ZedKind == ZED_Custom ) {
for ( i=0; i < MC.default.MonsterClasses.length; ++i ) {
if ( CustomZedClass ~= GetItemName(MC.default.MonsterClasses[i].MClassName) ) {
MonsterClass = class<KFMonster>(DynamicLoadObject(MC.default.MonsterClasses[i].MClassName, Class'class'));
break;
}
}
}
else if ( ZedKind == ZED_Boss )
MonsterClass = class<KFMonster>(DynamicLoadObject(MC.default.EndGameBossClass, Class'class'));
else
MonsterClass = class<KFMonster>(DynamicLoadObject(MC.default.MonsterClasses[int(ZedKind)-2].MClassName, Class'class'));
if ( MonsterClass == none )
return false;
if ( LocationTag != '' ) {
foreach C.AllActors(class'Actor', a, LocationTag)
break;
}
if ( a != none )
L = a.Location + LocationOffset;
else if ( bOffsetFromScriptedPawn && C.Pawn != none )
L = C.Pawn.Location + LocationOffset;
else
L = C.SequenceScript.Location + LocationOffset;
a = none;
if ( RotateToTag != '' ) {
foreach C.AllActors(class'Actor', a, RotateToTag)
break;
}
if ( a != none )
R = rotator(a.Location - L) + RotationOffset;
else if ( bOffsetFromScriptedPawn && C.Pawn != none )
R = C.Pawn.Rotation + RotationOffset;
else
R = C.SequenceScript.Rotation + RotationOffset;
C.Spawn(MonsterClass,,NewTag,L,R);
return false;
}
function string GetActionString()
{
return ActionString@GetEnum(enum'ACTION_SpawnZED.EZEDKind', ZedKind)@CustomZedClass;
}
defaultproperties
{
ActionString="Spawn ZED"
}

View file

@ -0,0 +1,54 @@
// ACTION_WaitForPlayer succeds only if MyPlayerController.Pawn is nearby
// ACTION_WaitForAnyPlayer succeeds if any players are nearby
class ACTION_WaitForAnyPlayer extends ACTION_WaitForPlayer;
var(Action) float CheckTime; // how often to look for a player?
var ScriptedController MyScriptedController;
function bool InitActionFor(ScriptedController C)
{
MyScriptedController = C;
if ( CheckIfNearPlayer(C) )
return false;
C.CurrentAction = self;
C.SetTimer(fmax(CheckTime, 0.1),true);
return true;
}
function bool WaitForPlayer()
{
if ( MyScriptedController != none )
CheckIfNearPlayer(MyScriptedController); // update MyScriptedController.MyPlayerController
return true;
}
function bool CheckIfNearPlayer(ScriptedController SC)
{
local Controller C;
local PlayerController PC, BACKUP_MyPlayerController;
if ( SC.MyPlayerController != none && SC.MyPlayerController.Pawn != none && SC.CheckIfNearPlayer(Distance) )
return true;
BACKUP_MyPlayerController = SC.MyPlayerController;
for ( C = SC.Level.ControllerList; C != none; C = C.nextController ) {
PC = PlayerController(C);
if ( PC != none && PC.Pawn != none ) {
SC.MyPlayerController = PC;
if ( SC.CheckIfNearPlayer(Distance) )
return true;
}
}
// restore original MyPlayerController, if no players are nearby - just in case
SC.MyPlayerController = BACKUP_MyPlayerController;
return false;
}
defaultproperties
{
CheckTime=1.000000
}

View file

@ -0,0 +1,31 @@
class ACTION_WaitForTimerRandom extends LatentScriptedAction;
var(Action) float MinTime;
var(Action) float MaxTime;
function bool InitActionFor(ScriptedController C)
{
C.CurrentAction = self;
C.SetTimer(MinTime + frand()*(MaxTime-MinTime), false);
return true;
}
function bool CompleteWhenTriggered()
{
return true;
}
function bool CompleteWhenTimer()
{
return true;
}
function string GetActionString()
{
return ActionString@MinTime$".."$MaxTime;
}
defaultproperties
{
ActionString="Wait for timer"
}

View file

@ -0,0 +1,16 @@
// turns on zed time
class ACTION_ZEDTIME extends ScriptedAction;
var(Action) float Possibility;
var(Action) float Duration; // leave 0 for default ZED time duration
function bool InitActionFor(ScriptedController C)
{
KFGameType(C.Level.Game).DramaticEvent(Possibility, Duration);
return false;
}
defaultproperties
{
Possibility=1.000000
}

View file

@ -0,0 +1,21 @@
class DLGFILTER_Deaths extends DialoguePlayerFilter;
function float CalcPlayerValue(KF_PlayerDialogue Dialogue, int DlgIdx, PlayerReplicationInfo PRI)
{
local Controller C;
local float TopValue;
for ( C = PRI.Level.ControllerList; C != None; C = C.NextController ) {
if ( C.PlayerReplicationInfo != none )
TopValue = fmax(TopValue, C.PlayerReplicationInfo.Deaths);
}
if ( TopValue == 0.0 )
return 0.0;
return Value * PRI.Deaths / TopValue;
}
defaultproperties
{
}

View file

@ -0,0 +1,14 @@
class DLGFILTER_Female extends DialoguePlayerFilter;
function float CalcPlayerValue(KF_PlayerDialogue Dialogue, int DlgIdx, PlayerReplicationInfo PRI)
{
if ( PRI.bIsFemale )
return Value;
return 0.f;
}
defaultproperties
{
}

View file

@ -0,0 +1,14 @@
class DLGFILTER_Instigator extends DialoguePlayerFilter;
function float CalcPlayerValue(KF_PlayerDialogue Dialogue, int DlgIdx, PlayerReplicationInfo PRI)
{
if ( Dialogue.DialogueInstigator == PRI.Owner )
return Value;
return 0.f;
}
defaultproperties
{
}

View file

@ -0,0 +1,30 @@
class DLGFILTER_KillAssists extends DialoguePlayerFilter;
var private transient float TopValue;
var private transient float TopValueTime; // lst time when TopValue was set
function float CalcPlayerValue(KF_PlayerDialogue Dialogue, int DlgIdx, PlayerReplicationInfo PRI)
{
local Controller C;
if ( KFPlayerReplicationInfo(PRI) == none )
return 0.0;
if ( TopValueTime != PRI.Level.TimeSeconds ) {
TopValue = 0.0;
TopValueTime = PRI.Level.TimeSeconds;
for ( C = PRI.Level.ControllerList; C != None; C = C.NextController ) {
if ( KFPlayerReplicationInfo(C.PlayerReplicationInfo) != none )
TopValue = fmax(TopValue, KFPlayerReplicationInfo(C.PlayerReplicationInfo).KillAssists);
}
}
if ( TopValue == 0.0 )
return 0.0;
return Value * KFPlayerReplicationInfo(PRI).KillAssists / TopValue;
}
defaultproperties
{
}

View file

@ -0,0 +1,27 @@
// more kills = higher value
class DLGFILTER_Kills extends DialoguePlayerFilter;
var private transient float TopValue;
var private transient float TopValueTime; // lst time when TopValue was set
function float CalcPlayerValue(KF_PlayerDialogue Dialogue, int DlgIdx, PlayerReplicationInfo PRI)
{
local Controller C;
if ( TopValueTime != PRI.Level.TimeSeconds ) {
TopValue = 0.0;
TopValueTime = PRI.Level.TimeSeconds;
for ( C = PRI.Level.ControllerList; C != None; C = C.NextController ) {
if ( C.PlayerReplicationInfo != none )
TopValue = fmax(TopValue, C.PlayerReplicationInfo.Kills);
}
}
if ( TopValue == 0.0 )
return 0.0;
return Value * PRI.Kills / TopValue;
}
defaultproperties
{
}

View file

@ -0,0 +1,61 @@
class DLGFILTER_Perk extends DialoguePlayerFilter;
enum EPerk
{
PERK_Medic,
PERK_Support,
PERK_Sharpshooter,
PERK_Commando,
PERK_Berserker,
PERK_Firebug,
PERK_Demolitions,
};
var(Filter) EPerk Perk;
function float CalcPlayerValue(KF_PlayerDialogue Dialogue, int DlgIdx, PlayerReplicationInfo PRI)
{
local KFPlayerReplicationInfo KFPRI;
KFPRI = KFPlayerReplicationInfo(PRI);
if ( KFPRI == none || KFPRI.ClientVeteranSkill == none )
return 0.0;
switch (Perk) {
case PERK_Medic:
if ( KFPRI.ClientVeteranSkill.static.GetSyringeChargeRate(KFPRI) > 1.01 )
return Value;
break;
case PERK_Support:
if ( KFPRI.ClientVeteranSkill.static.GetWeldSpeedModifier(KFPRI) > 1.01 )
return Value;
break;
case PERK_Sharpshooter:
if ( KFPRI.ClientVeteranSkill.static.GetHeadShotDamMulti(KFPRI, none, class'DamTypeCrossbow') > 1.01 )
return Value;
break;
case PERK_Commando:
if ( KFPRI.ClientVeteranSkill.static.ShowStalkers(KFPRI) )
return Value;
break;
case PERK_Berserker:
if ( KFPRI.ClientVeteranSkill.static.GetMeleeMovementSpeedModifier(KFPRI) > 0.01 )
return Value;
break;
case PERK_Firebug:
if ( KFPRI.ClientVeteranSkill.static.AddDamage(KFPRI, none, none, 100, class'DamTypeBurned') == 100 )
return Value;
break;
case PERK_Demolitions:
if ( KFPRI.ClientVeteranSkill.static.AddDamage(KFPRI, none, none, 100, class'DamTypeM79Grenade') == 100 )
return Value;
break;
}
return 0.f;
}
defaultproperties
{
}

View file

@ -0,0 +1,26 @@
class DLGFILTER_Score extends DialoguePlayerFilter;
var private transient float TopValue;
var private transient float TopValueTime; // lst time when TopValue was set
function float CalcPlayerValue(KF_PlayerDialogue Dialogue, int DlgIdx, PlayerReplicationInfo PRI)
{
local Controller C;
if ( TopValueTime != PRI.Level.TimeSeconds ) {
TopValue = 0.0;
TopValueTime = PRI.Level.TimeSeconds;
for ( C = PRI.Level.ControllerList; C != None; C = C.NextController ) {
if ( C.PlayerReplicationInfo != none )
TopValue = fmax(TopValue, C.PlayerReplicationInfo.Score);
}
}
if ( TopValue == 0.0 )
return 0.0;
return Value * PRI.Score / TopValue;
}
defaultproperties
{
}

View file

@ -0,0 +1,6 @@
class DamTypeBleedFixed extends DamTypeBleedOut;
defaultproperties
{
bArmorStops=False
}

View file

@ -0,0 +1,16 @@
class DialoguePlayerFilter extends Object
abstract
hidecategories(Object)
collapsecategories
editinlinenew;
var(Filter) const float Value;
function float CalcPlayerValue(KF_PlayerDialogue Dialogue, int DlgIdx, PlayerReplicationInfo PRI)
{
return Value;
}
defaultproperties
{
}

View file

@ -0,0 +1,35 @@
class Dialogue_EventListener_SE extends Dialogue_EventListener
dependson(KF_DialogueSpot_SE);
function Trigger( actor Other, pawn EventInstigator )
{
local KF_DialogueSpot_SE DlgOwnerSE;
DlgOwnerSE = KF_DialogueSpot_SE(DlgOwner);
if ( DlgOwnerSE == none || DlgOwnerSE.RequiredEventType == RET_Trigger ) {
super.Trigger(Other, EventInstigator);
return;
}
if(DlgOwner.bDebugDialogue)
{
log("----- DIALOGUE DEBUG ------ DialogueListener was triggered for Index "@AssociatedIndex@"Received event : "@DlgOwner.Dialogues[AssociatedIndex].Events.RequiredEvent@",triggered by : "@Other@" .. Proceeding ", 'Story_Debug');
}
// if bLooping=True, dialogue is DISABLED
switch (DlgOwnerSE.RequiredEventType) {
case RET_Enable:
DlgOwner.Dialogues[AssociatedIndex].bWasTriggered = false;
break;
case RET_Disable:
DlgOwner.Dialogues[AssociatedIndex].bWasTriggered = true;
break;
case RET_Toggle:
DlgOwner.Dialogues[AssociatedIndex].bWasTriggered = !DlgOwner.Dialogues[AssociatedIndex].bWasTriggered;
break;
}
}
defaultproperties
{
}

View file

@ -0,0 +1,43 @@
// if player already has nades, it gives +1 ammo
class FragPickup_SE extends FragPickup;
auto state pickup
{
function bool GiveAmmo(KFHumanPawn Receiver)
{
local Inventory Inv;
local KFWeapon W;
if ( Receiver == none )
return false;
for ( Inv=Receiver.Inventory; Inv!=none; Inv=Inv.Inventory ) {
W = KFWeapon(Inv);
if ( W != none && ClassIsChildOf(Inv.class, InventoryType) ) {
if ( !W.AmmoMaxed(0) ) {
KFWeapon(Inv).AddAmmo(AmmoAmount[0], 0);
PlaySound( PickupSound,SLOT_Interact );
SetRespawn();
}
return true;
}
}
return false;
}
function Touch(Actor Other)
{
// if player have nades (weapon) - give him weapon
// otherwise - give weapon
if ( KFHumanPawn(Other) != none && !GiveAmmo(KFHumanPawn(Other)) )
super.Touch(Other);
}
}
defaultproperties
{
cost=40
BuyClipSize=1
AmmoAmount(0)=1
}

View file

@ -0,0 +1,36 @@
class KFDestroyableStaticMesh_SE extends RODestroyableStaticMeshBase
placeable;
var(Sound) sound DamagedSound; // Ambient sound for damaged mesh
var(Sound) bool bUseDamagedSound; // use DamagedSound?
var protected transient int OriginalHealth;
auto state Working
{
function BeginState()
{
if ( OriginalHealth == 0 )
OriginalHealth = Health;
super.BeginState();
Health = OriginalHealth; // fix health resetting to default 0 and ignoring value set in editor
}
function EndState()
{
super.EndState();
if ( bUseDamagedSound ) {
AmbientSound = DamagedSound;
}
}
}
defaultproperties
{
TypesCanDamage(0)=Class'KFMod.DamTypeFrag'
StaticMesh=StaticMesh'IndustrySM2.Doors.fact_metaldoor_04'
bWorldGeometry=False
Tag="DestroyableStaticMesh"
}

View file

@ -0,0 +1,165 @@
/*
--------------------------------------------------------------
KF_BreakerBoxNPC_SE
--------------------------------------------------------------
Enhanced version of KF_BreakerBoxNPC:
- TickEvent can be set by map author.
- Doesn't trigger TickEvent when inactive
- can retain visibility while inactive
- can be started active
- Min and max threat rating
- Threat rating for ScrN Balance mod
Author : PooSH
Original Author: Alex Quick
--------------------------------------------------------------
*/
class KF_BreakerBoxNPC_SE extends KF_BreakerBoxNPC;
var(Events) name TickEvent;
var(AI) float MinAIThreatRating, MaxAIThreatRating; // only if parent method return value > 0
// used in AssessThreatTo()
var protected transient KFMonsterController LastThreatMonster;
var protected transient float LastThreatTime, LastThreat;
// restrict healing
function bool GiveHealth(int HealAmount, int HealMax)
{
return false;
}
function bool HealDamage(int Amount, Controller Healer, class<DamageType> DamageType)
{
return false;
}
simulated function PostBeginPlay()
{
Super(KF_StoryNPC_Static).PostbeginPlay();
CheckHealthCondition(self);
}
function GetEvents(out array<name> TriggeredEvents, out array<name> ReceivedEvents)
{
super.GetEvents(TriggeredEvents, ReceivedEvents);
if( TickEvent != '' )
TriggeredEvents[TriggeredEvents.length] = TickEvent;
}
function SetActive(bool On)
{
Super(KF_StoryNPC_Static).SetActive(On);
}
simulated function Tick(float DeltaTime)
{
Super(KF_StoryNPC_Static).Tick(DeltaTime);
if( TickEvent != '' && bActive && !bDestroyed && Level.TimeSeconds - LastTriggerEventTime > 1.0)
{
LastTriggerEventTime = Level.TimeSeconds;
TriggerEvent(TickEvent, self, self);
}
}
/**
Monster threat assessment functionality
Changes by PooSH:
1) KFGameType.bUseZEDThreatAssessment set to true, i.e. in regular game new AssessThreatTo()
function will be used too.
2) Distance between monster and player will always be in place.
3) AssessThreatTo will always return value > 0. Because zeds should not ignore players.
4) Added randomization - zeds can choose different targets in the same circumstances.
5) Blood smell. Wounded players will attract zeds slightly more than their healthy teammates.
* @param Monster Monster's controller, for which we are calculating the threat level
* @param CheckDistance Not used!
* @return threat level between 0 and 100, where 100 is the max threat level.
*
* @author PooSH
*/
function float AssessThreatTo(KFMonsterController Monster, optional bool CheckDistance)
{
local float DistancePart, RandomPart, TacticalPart;
local float DistanceSquared; // squared distance is calculated faster
if ( !Level.Game.IsA('ScrnStoryGameInfo') ) {
LastThreat = super.AssessThreatTo(Monster, CheckDistance);
// don't use MinAIThreatRating for vanilla game, cuz then zeds won't focus on players at all
if ( LastThreat > MaxAIThreatRating )
LastThreat = MaxAIThreatRating;
return LastThreat;
}
// the following code is for ScrnBalance only
if(Monster == none || KFMonster(Monster.Pawn) == none)
{
return -1.f;
}
if(bNoThreatToZEDs ||
TeamIndex == 255 ||
Health <= 0 ||
!bActive ||
!bDamageable ||
(!IsThreateningTo(Monster.Pawn)) )
{
return -1.f;
}
if ( LastThreatMonster == Monster && LastThreatTime == Level.TimeSeconds )
return LastThreat; // threat level for the given monster has been calculated already during the current tick
DistanceSquared = VSizeSquared(Monster.Pawn.Location - Location);
DistancePart = 65.0;
RandomPart = MaxAIThreatRating * 0.35;
// TacticalPart is useless for static NPCs
/*
// let zeds smell blood within 25m radius - wounded players attract zeds more
if ( DistanceSquared < 1562500.0 )
TacticalPart += RandomPart * 0.40 * (HealthMax - Health) / HealthMax;
// more chance to attack the same enemy multiple times
if ( Monster.Enemy == self || Monster.Target == self )
TacticalPart += RandomPart * 0.25;
// more chance to focus on the player, who are attacking the monster
if ( KFMonster(Monster.Pawn).LastDamagedBy == self)
TacticalPart += RandomPart * 0.25;
RandomPart = 100.0 - DistancePart - TacticalPart;
*/
// If target is closer than 1 meter, max DistancePart value will be used,
// otherwise DistancePart is lowering by 10% per meter
// 1 meter = 50 ups (2500 squared)
if ( DistanceSquared > 2500.0 )
DistancePart /= 1.0 + DistanceSquared / 250000.0;
RandomPart *= frand();
// save threat level for this tick
LastThreatMonster = Monster;
LastThreatTime = Level.TimeSeconds;
LastThreat = DistancePart + TacticalPart + RandomPart;
//LastThreat *= InventoryThreatModifier();
LastThreat += BaseAIThreatRating;
LastThreat = fclamp(LastThreat, MinAIThreatRating, MaxAIThreatRating);
return LastThreat;
}
defaultproperties
{
MaxAIThreatRating=40.000000
bWorldGeometry=True
bBlockHitPointTraces=True
}

View file

@ -0,0 +1,225 @@
/*
--------------------------------------------------------------
KF_DialogueSpot_SE
--------------------------------------------------------------
Author : PooSH
Original Author: Alex Quick
--------------------------------------------------------------
*/
class KF_DialogueSpot_SE extends KF_DialogueSpot
placeable;
/**
ERequiredEventType - determines what to do on RequiredEvent triggered:
RET_Trigger Act the same as original KF_DialogueSpot - start dialogue from triggered index
RET_Enable Just enables triggered index (doesn't show dialogue).
RET_Disable Disables triggered index (doesn't show dialogue)
RET_Toggle Toggles between enabled and disabled
In all cases except RET_Trigger, bLooping shows if dialogue is initially DISABLED
*/
enum ERequiredEventType
{
RET_Trigger,
RET_Enable,
RET_Disable,
RET_Toggle
};
var(KF_DialogueSpot) const ERequiredEventType RequiredEventType;
var(KF_DialogueSpot) float DisplayTimePerWord;
var(KF_DialogueSpot) float DisplayTimePerChar;
var(KF_DialogueSpot) float MinDisplayTime;
var(KF_DialogueSpot) float MaxDisplayTime;
function PostBeginPlay()
{
local int i;
local Dialogue_EventListener NewListener;
/* No KFO Gametype, no Initialization */
if(KFStoryGameinfo(Level.Game) == none)
{
return;
}
// Spawn Event listeners for Dialogue entries which require them.
for(i = 0 ; i < Dialogues.length ; i ++)
{
NewListener = Spawn(class'Dialogue_EventListener_SE',self,Dialogues[i].Events.RequiredEvent);
NewListener.AssociatedIndex = i ;
Dialogues[i].EventListener = NewListener;
if ( RequiredEventType != RET_Trigger && Dialogues[i].Events.RequiredEvent != '' )
Dialogues[i].bWasTriggered = Dialogues[i].bLooping;
}
}
// C&P to set bLooping as default bWasTriggered state -- PooSH
function Reset()
{
local int i, SavedIdx;
local array<byte> SavedTriggerStates;
Super.Reset();
/* Partial reset using saved positions*/
bTraversing = false;
if(!bFinished)
{
SavedIdx = KFStoryGameInfo(Level.Game).CurrentCheckPoint.GetSavedDialogueIndexFor(self,SavedTriggerStates) ;
CurrentMsgIdx = SavedIdx;
if(bDebugDialogue)
{
log("----- DIALOGUE DEBUG ------ Resetting "@self@". Dialogue will play back from Index : "@SavedIdx, 'Story_Debug');
}
for(i = 0 ; i < Dialogues.length ; i ++)
{
Dialogues[i].bWasTriggered = bool(SavedTriggerStates[i]);
}
}
else /* Full reset from the start. */
{
for(i = 0 ; i < Dialogues.length ; i ++)
{
if ( RequiredEventType != RET_Trigger && Dialogues[i].Events.RequiredEvent != '' )
Dialogues[i].bWasTriggered = Dialogues[i].bLooping;
else
Dialogues[i].bWasTriggered = false;
}
CurrentMsgIdx = 0;
bFinished = false;
}
}
function int GetNextDlgRequiredEventIdx(int TestIndex)
{
if ( RequiredEventType == RET_Trigger )
return super.GetNextDlgRequiredEventIdx(TestIndex);
return -1; // for other event types - event is not required, it just enables/disables that dialogue
}
function int GetRandomEnabledDialogueIndex()
{
local array<int> AvaliableIndicies;
local int i;
for ( i=0; i<Dialogues.length; ++i ) {
if ( !Dialogues[i].bWasTriggered )
AvaliableIndicies[AvaliableIndicies.Length] = i;
}
if ( AvaliableIndicies.length == 0 )
return -1;
if ( AvaliableIndicies.length == 1)
return 0;
return AvaliableIndicies[RandRange(0,AvaliableIndicies.length)];
}
function TraverseDialogue()
{
local float DisplayDur;
if ( RequiredEventType == RET_Trigger ) {
super.TraverseDialogue();
return;
}
/* Hit the end. early out */
if( bTraversing || bFinished )
return;
if(bRandomize) {
CurrentMsgIdx = GetRandomEnabledDialogueIndex();
if ( CurrentMsgIdx == -1 ) {
OnDialogueCompleted();
return;
}
}
else if ( CurrentMsgIdx >= Dialogues.length ) {
return;
}
while ( CurrentMsgIdx < Dialogues.length && Dialogues[CurrentMsgIdx].bWasTriggered )
CurrentMsgIdx++;
if ( CurrentMsgIdx >= Dialogues.length ) {
OnDialogueCompleted();
return;
}
if(bDebugDialogue)
{
log("----- DIALOGUE DEBUG ------ Showing Dialogue at index .. "@CurrentMsgIdx, 'Story_Debug');
}
ShowDialogue(CurrentMsgIdx);
if( !bRandomize ) {
if(CurrentMsgIdx + 1 < Dialogues.length )
{
bTraversing = true;
DisplayDur = GetCurrentDisplayDur();
if(DisplayDur > 0)
{
SetTimer(DisplayDur,false);
}
else
{
Timer();
}
}
else
{
/* we're finished. Reset the actor */
OnDialogueCompleted();
}
}
}
function Timer()
{
if ( RequiredEventType == RET_Trigger ) {
super.Timer();
return;
}
if(bTraversing) {
bTraversing = false;
CurrentMsgIdx++ ;
TraverseDialogue();
}
}
/* Helper function - calculates the amount of time a string should
be displayed for based on its word length */
function float CalcDisplayTime(string InString)
{
local array<string> Words;
if ( DisplayTimePerWord > 0 )
Split(InString," ",Words);
return FClamp(Words.length * DisplayTimePerWord + Len(InString)*DisplayTimePerChar, MinDisplayTime, MaxDisplayTime);
}
defaultproperties
{
DisplayTimePerWord=0.300000
MinDisplayTime=3.000000
MaxDisplayTime=60.000000
}

View file

@ -0,0 +1,282 @@
/*
--------------------------------------------------------------
KF_PlayerDialogue
--------------------------------------------------------------
Displayes player's portrait and nickname as title to act as
if it is a player to speaks it.
Author : PooSH
--------------------------------------------------------------
*/
class KF_PlayerDialogue extends KF_DialogueSpot_SE
placeable;
// Trigers when dialogue was triggered but didn't matched player conditions, so it was skipped
// You can set SkippedEvent=Tag, to force dialogue displaying, if requirements were not matched.
// In that case Intigator will be used as speaker, or random player, if instigated by non-player.
var(Events) name SkippedEvent;
var() byte MinPlayerCount;
var() byte MaxPlayerCount;
var() bool bOnlyAlivePlayers;
/*
SpeakerName Unique name which dialogue assosiates with a player matching the following criterias.
If player is already assigned to a local name, then he will be used. Otherwice
player lookup will be made and the result assigned to a local name.
ExcludeNames list of local names that can not be used for the current dialogue
bUseInstigator Use dialogue's instigator (DialogueInstigator) for the current dialogue.
bSetInstigator Change dialogue's instigator to the chosen speaker
bForceDisplayEvents If true, DisplayingEvent and DisplayedEvent will be triggered even if dialogue is skipped.
Filters Array of DialoguePlayerFilter instances, each of them returns a number between 0 and defined Value
depending of specific player stats. Player with highest total value will be chosen for speaking.
MinFilterValue Players with filter value < MinFilterValue are skipped and can't be used for displaying the dialogue.
MinFilterValue=0 (default) turns off min value filtering! Use MinFilterValue=0.000001 to set it next to 0.
If no players found with a given criteria, dialogue will not be displayed, so use it with caution!
WARNING! If both bUseInstigator=True and SpeakerName was set, then use priority depends from bSetInstigator:
If bSetInstigator=True, then SpeakerName has higher priority,
i.e. speaker will be chosen by name and set as instigator
If bSetInstigator=False, then bUseInstigator has higher priority,
i.e. instigator will be used as a speaker, overriding the value stored by SpeakerName
*/
struct SDialogueFilter
{
var() name SpeakerName;
var() name ExcludeNames[5];
var() bool bUseInstigator;
var() bool bSetInstigator;
var() bool bForceDisplayEvents;
var() export editinline Array<DialoguePlayerFilter> Filters;
var() float MinFilterValue;
};
var() array<SDialogueFilter> DialogueFilters;
var protected NamedPRITable NamedPRIs;
var PlayerDialogueReplicationInfo PlayerDialogueReplicationInfo;
function PostBeginPlay()
{
local int i;
super.PostBeginPlay();
NamedPRIs = NamedPRITable(class'NamedPRITable'.static.InitTable(Level));
if ( Level.NetMode != NM_Standalone ) {
PlayerDialogueReplicationInfo = spawn(class'PlayerDialogueReplicationInfo', self);
if ( PlayerDialogueReplicationInfo != none ) {
PlayerDialogueReplicationInfo.DialogueActorName = self.name;
}
}
for ( i=0; i<Dialogues.length; ++i )
Dialogues[i].Display.Dialogue_Header = ""; // names of speaking players will be put here later
}
function GetEvents(out array<name> TriggeredEvents, out array<name> ReceivedEvents)
{
super.GetEvents(TriggeredEvents, ReceivedEvents);
if ( SkippedEvent != '' )
ReceivedEvents[ReceivedEvents.length] = SkippedEvent;
}
function DialogueSkipped()
{
if ( SkippedEvent != '' )
TriggerEvent(SkippedEvent, self, GetInstigator());
}
function Pawn GetInstigator()
{
if ( DialogueInstigator == none )
return none;
return DialogueInstigator.Pawn;
}
function RetrieveAllPRIs(out array<PlayerReplicationInfo> PRIs)
{
local Controller C;
PRIs.length = 0;
for ( C = Level.ControllerList; C != None; C = C.NextController ) {
if ( C.PlayerReplicationInfo != none && PlayerController(C) != none
&& !C.PlayerReplicationInfo.bOnlySpectator
&& (!bOnlyAlivePlayers || (C.Pawn != none && !C.Pawn.bDeleteMe && C.Pawn.Health > 0) ) )
PRIs[PRIs.length] = C.PlayerReplicationInfo;
}
}
function PlayerReplicationInfo GetSpeakingPRI(int DlgIdx)
{
local int i, j;
local array<PlayerReplicationInfo> PRIs;
local PlayerReplicationInfo result, NamedPRI;
local float TopValue, FilteredValue;
local array<float> FilteredValues;
if ( DlgIdx < DialogueFilters.length ) {
// if PRI is assigned to SpeakerName already - use it
if ( DialogueFilters[DlgIdx].SpeakerName != '' );
NamedPRI = NamedPRIs.GetPRI(DialogueFilters[DlgIdx].SpeakerName);
if ( DialogueFilters[DlgIdx].bUseInstigator ) {
if ( PlayerController(DialogueInstigator) != none && DialogueInstigator.PlayerReplicationInfo != none )
result = DialogueInstigator.PlayerReplicationInfo;
if ( NamedPRI != none && NamedPRI != result ) {
// if bSetInstigator=True, then SpeakerName has higher priority than instigator
if ( result == none || DialogueFilters[DlgIdx].bSetInstigator ) {
result = NamedPRI;
if ( DialogueFilters[DlgIdx].bSetInstigator )
DialogueInstigator = PlayerController(NamedPRI.Owner);
}
}
}
else {
if ( NamedPRI != none )
result = NamedPRI;
else {
RetrieveAllPRIs(PRIs);
// removed excluded names
for ( i=0; i<5; ++i) {
if ( DialogueFilters[DlgIdx].ExcludeNames[i] != '' ) {
NamedPRI = NamedPRIs.GetPRI(DialogueFilters[DlgIdx].ExcludeNames[i]);
if ( NamedPRI != none ) {
for ( j=0; j<PRIs.length; ++j )
if ( PRIs[j] == NamedPRI )
PRIs.remove(j--, 1);
}
}
}
NamedPRI = none; //reset value
// now PRIs contains only valid speakers
if ( DialogueFilters[DlgIdx].Filters.length > 0 ) {
TopValue = DialogueFilters[DlgIdx].MinFilterValue;
// calculate total filtered value of each PRI
for ( j=0; j<PRIs.length; ++j ) {
FilteredValue = 0;
for ( i=0; i<DialogueFilters[DlgIdx].Filters.length; ++i )
FilteredValue += DialogueFilters[DlgIdx].Filters[i].CalcPlayerValue(self, DlgIdx, PRIs[j]);
FilteredValues[j] = FilteredValue;
if ( FilteredValue > TopValue || (j == 0 && TopValue == 0.f) )
TopValue = FilteredValue;
}
// leave only PRIs with TopValue
for ( j=0; j<PRIs.length; ++j ) {
if ( TopValue - FilteredValues[j] > 0.01 ) {
PRIs.remove(j, 1);
FilteredValues.remove(j, 1);
j--;
}
}
}
result = PRIs[rand(PRIs.length)];
}
}
if ( DialogueFilters[DlgIdx].SpeakerName != '' && result != NamedPRI ) {
NamedPRI = result;
NamedPRIs.SetPRI(DialogueFilters[DlgIdx].SpeakerName, NamedPRI);
}
}
else {
RetrieveAllPRIs(PRIs);
result = PRIs[rand(PRIs.length)];
}
return result;
}
function bool RequirementsMatched()
{
local int PlayerCount;
if ( bOnlyAlivePlayers )
PlayerCount = KFStoryGameInfo(Level.Game).GetTotalActivePlayers();
else
PlayerCount = Level.Game.NumPlayers;
return PlayerCount >= MinPlayerCount && (PlayerCount <= MaxPlayerCount || MaxPlayerCount == 0);
}
// set protrait and title from speaking player, then call TraverseDialogue
function TraverseDialogue()
{
local bool bOriginalRandomize;
if( Dialogues.length <= CurrentMsgIdx || bTraversing || (bFinished && !bAllowRepeatDialogue) )
return;
if ( !RequirementsMatched() ) {
DialogueSkipped();
CurrentMsgIdx = Dialogues.length;
return;
}
if ( bRandomize ) {
// we need to know dialogue index already
bOriginalRandomize = true;
bRandomize = false;
CurrentMsgIdx = rand(Dialogues.length);
}
SetPlayerDialogue(CurrentMsgIdx, GetSpeakingPRI(CurrentMsgIdx));
if ( Dialogues[CurrentMsgIdx].Display.Dialogue_Header == "" ) {
if ( CurrentMsgIdx < DialogueFilters.length && DialogueFilters[CurrentMsgIdx].bForceDisplayEvents ) {
if ( Dialogues[CurrentMsgIdx].Events.DisplayingEvent != '' )
TriggerEvent(Dialogues[CurrentMsgIdx].Events.DisplayingEvent,self,GetInstigator());
if ( Dialogues[CurrentMsgIdx].Events.DisplayedEvent != '' )
TriggerEvent(Dialogues[CurrentMsgIdx].Events.DisplayedEvent,self,GetInstigator());
}
if ( !bOriginalRandomize ) {
CurrentMsgIdx++;
TraverseDialogue();
}
}
else {
super.TraverseDialogue();
}
bRandomize = bOriginalRandomize;
}
function SetPlayerDialogue(int DlgIdx, PlayerReplicationInfo SpeakerPRI)
{
if ( DlgIdx < 0 || DlgIdx >= Dialogues.length )
return;
if ( SpeakerPRI != none ) {
Dialogues[DlgIdx].Display.Portrait_Material = SpeakerPRI.GetPortrait();
Dialogues[DlgIdx].Display.Dialogue_Header = SpeakerPRI.PlayerName;
//avoid skipping a dialogue in cases when player doesn't have a name (is it possible?)
if ( Dialogues[DlgIdx].Display.Dialogue_Header == "" )
Dialogues[DlgIdx].Display.Dialogue_Header = "Fresh Meat";
if ( PlayerDialogueReplicationInfo != none ) {
PlayerDialogueReplicationInfo.DlgIndex = DlgIdx;
PlayerDialogueReplicationInfo.SpeakerPRI = SpeakerPRI;
PlayerDialogueReplicationInfo.NetUpdateTime = Level.TimeSeconds - 1;
}
}
else {
Dialogues[DlgIdx].Display.Portrait_Material = none;
Dialogues[DlgIdx].Display.Dialogue_Header = "";
}
}
defaultproperties
{
bOnlyAlivePlayers=True
}

View file

@ -0,0 +1,353 @@
class KF_StoryNPC_Spawnable_SE extends KF_RingMasterNPC;
var(Bleed) bool bBleed; // should NPC bleed at all?
var(Bleed) float MinBleedDamage, MaxBleedDamage; // minand max amount of health that NPC looses per bleed
var(KF_StoryNPC) bool bActivateOnHealing;
var(KF_StoryNPC) bool bActivateOnHealingOnlyOnce;
var(KF_StoryNPC) float HealthPctToActivate;
var(Events) name DeathEvent;
var(Events) name UseEvent;
var(KF_StoryNPC) bool bHealthBarOnlyIfVisible; // draw HP bar only if player can see NPC
var(KF_StoryNPC) float HealthBarMaxDistance;
var protected float HealthBarMaxDistanceSquared;
var(KF_StoryNPC) float ZombieDamageReduction;
var bool bUseCoverAnim;
// display message when touching by a player
var() localized string TouchMessage;
var float NextTouchMessageTime;
var() name TouchMessageType;
var Touchable Touchable;
var() bool bTouchable; // players can touch me, i.e. call Touch() event
var() bool bUsable; // players can use me, i.e. press <use> button on me, calling UsedBy() event
// used in AssessThreatTo()
var protected transient KFMonsterController LastThreatMonster;
var protected transient float LastThreatTime, LastThreat;
simulated function PostBeginPlay()
{
super.PostBeginPlay();
HealthBarMaxDistanceSquared = Square(HealthBarMaxDistance);
if ( bTouchable || bUsable )
SpawnTouchable();
}
function GetEvents(out array<name> TriggeredEvents, out array<name> ReceivedEvents)
{
super.GetEvents(TriggeredEvents, ReceivedEvents);
if( DeathEvent != '' )
TriggeredEvents[TriggeredEvents.length] = DeathEvent;
if( UseEvent != '' )
TriggeredEvents[TriggeredEvents.length] = UseEvent;
}
simulated function Tick(float DeltaTime)
{
if( bBleed && bActive && Level.TimeSeconds - LastBleedOutTime > BleedOutInterval) {
TakeDamage(RandRange(MinBleedDamage, MaxBleedDamage),self,Location + Vect(0,0,1)*CollisionHeight,vect(0,0,0),class'DamTypeBleedFixed');
LastBleedOutTime = Level.TimeSeconds;
}
Super(KF_StoryNPC_Spawnable).Tick(DeltaTime);
// do we really need this? -- PooSH
if ( Controller == none || Controller.Enemy == none ) {
/* Update his idle state to reflect his injuries , etc. */
IdleRestAnim = GetIdleAnim();
IdleWeaponAnim = IdleRestAnim;
}
}
simulated function AddHealth()
{
super.AddHealth();
if ( bActivateOnHealing && !bActive && float(Health)/HealthMax >= HealthPctToActivate ) {
SetActive(true);
if ( bActivateOnHealingOnlyOnce )
bActivateOnHealing = false;
}
}
simulated function name GetIdleAnim()
{
if( !bActive )
{
return IdleSeatedAnim;
}
return IdleStandingInjuredAnim;
}
function Died(Controller Killer, class<DamageType> damageType, vector HitLocation)
{
if( DeathEvent != '' )
{
TriggerEvent(DeathEvent,Killer,self);
}
if ( Touchable != none )
Touchable.Destroy();
super.Died(Killer, damageType, HitLocation);
}
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
&& (HealthBarMaxDistanceSquared == 0 || VSizeSquared(PC.Location - Location) < HealthBarMaxDistanceSquared)
&& (!bHealthBarOnlyIfVisible || PC.CanSee(self)) )
{
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 TakeDamage(int Damage, Pawn instigatedBy, Vector hitlocation, Vector momentum, class<DamageType> damageType, optional int HitIdx )
{
// Reduce damage for zombie attacks
if( (class<DamTypeZombieAttack>(damageType) != none || class<DamTypeBurned>(damageType) != none
|| class<DamTypeLawRocketImpact>(damageType) != none) && Damage * ZombieDamageReduction >= 1 )
{
Damage *= ZombieDamageReduction;
}
// Cancel out the 5 we are going to lose when TakeDamage is called in the super
// This is done so that healing works better for the Ringmaster, as his constant
// bleedout is making healing not work well
if( damageType == class'DamTypeBleedOut' && healthToGive > 0 )
{
healthtoGive+=5;
}
//log("Taking "$Damage$" damage of type "$damageType);
// pushing the Ringmaster off his path can be problematic, so zero the momentum
super(KF_StoryNPC).TakeDamage( Damage, instigatedBy, hitlocation, vect(0,0,0), damageType, HitIdx );
}
simulated function Destroyed()
{
if ( Touchable != none )
Touchable.Destroy();
super.Destroyed();
}
function SpawnTouchable()
{
if ( Touchable != none )
return;
Touchable = Spawn(class'Touchable');
Touchable.SetMyPawn(self);
Touchable.bTouchable = bTouchable;
Touchable.bUsable = bUsable;
}
function Touch( Actor Other )
{
local Pawn P;
if( TouchMessage != "" && Level.TimeSeconds > NextTouchMessageTime ) {
// Send a string message to the toucher.
P = Pawn(Other);
if ( P != None && P.Controller != none && P.Controller.CanSee(self) )
{
P.ClientMessage( TouchMessage, TouchMessageType );
NextTouchMessageTime = Level.TimeSeconds + 1.0;
}
}
}
function UsedBy( Pawn user )
{
if ( KFHumanPawn(user) != none && PlayerController(user.Controller) != none ) {
if ( UseEvent != '' )
TriggerEvent(UseEvent, user, self);
}
}
/**
Monster threat assessment functionality
Changes by PooSH:
1) KFGameType.bUseZEDThreatAssessment set to true, i.e. in regular game new AssessThreatTo()
function will be used too.
2) Distance between monster and player will always be in place.
3) AssessThreatTo will always return value > 0. Because zeds should not ignore players.
4) Added randomization - zeds can choose different targets in the same circumstances.
5) Blood smell. Wounded players will attract zeds slightly more than their healthy teammates.
* @param Monster Monster's controller, for which we are calculating the threat level
* @param CheckDistance Not used!
* @return threat level between 0 and 100, where 100 is the max threat level.
*
* @author PooSH
*/
function float AssessThreatTo(KFMonsterController Monster, optional bool CheckDistance)
{
local float DistancePart, RandomPart, TacticalPart;
local float DistanceSquared; // squared distance is calculated faster
if ( !Level.Game.IsA('ScrnStoryGameInfo') )
return super.AssessThreatTo(Monster, CheckDistance);
// the following code is for ScrnBalance only
if(Monster == none || KFMonster(Monster.Pawn) == none)
{
return -1.f;
}
if(bNoThreatToZEDs ||
TeamIndex == 255 ||
Health <= 0 ||
!bActive ||
!bDamageable ||
(!IsThreateningTo(Monster.Pawn)) )
{
return -1.f;
}
if ( LastThreatMonster == Monster && LastThreatTime == Level.TimeSeconds )
return LastThreat; // threat level for the given monster has been calculated already during the current tick
DistanceSquared = VSizeSquared(Monster.Pawn.Location - Location);
DistancePart = 65.0;
RandomPart = 100.0 - DistancePart;
// let zeds smell blood within 25m radius - wounded players attract zeds more
if ( DistanceSquared < 1562500.0 )
TacticalPart += RandomPart * 0.40 * (HealthMax - Health) / HealthMax;
// more chance to attack the same enemy multiple times
if ( Monster.Enemy == self || Monster.Target == self )
TacticalPart += RandomPart * 0.25;
// more chance to focus on the player, who are attacking the monster
if ( KFMonster(Monster.Pawn).LastDamagedBy == self)
TacticalPart += RandomPart * 0.25;
RandomPart = 100.0 - DistancePart - TacticalPart;
// If target is closer than 1 meter, max DistancePart value will be used,
// otherwise DistancePart is lowering by 10% per meter
// 1 meter = 50 ups (2500 squared)
if ( DistanceSquared > 2500.0 )
DistancePart /= 1.0 + DistanceSquared / 250000.0;
RandomPart *= frand();
// save threat level for this tick
LastThreatMonster = Monster;
LastThreatTime = Level.TimeSeconds;
LastThreat = DistancePart + TacticalPart + RandomPart;
LastThreat *= InventoryThreatModifier();
LastThreat += BaseAIThreatRating;
return LastThreat;
}
// used in story game mode to attract zeds when holding some mission items
function float InventoryThreatModifier()
{
local float ThreatRating;
local Inventory CurInv;
local KF_StoryInventoryItem StoryInv;
ThreatRating = 1.0;
/* Factor in story Items which adjust your desirability to ZEDs */
for ( CurInv = Inventory; CurInv != none; CurInv = CurInv.Inventory ) {
StoryInv = KF_StoryInventoryItem(CurInv);
if(StoryInv != none)
ThreatRating *= StoryInv.AIThreatModifier ;
}
return ThreatRating;
}
simulated function PlayDirectionalHit(Vector HitLoc)
{
if ( bUseCoverAnim )
super.PlayDirectionalHit(HitLoc);
else
super(KFHumanPawn).PlayDirectionalHit(HitLoc);
}
function SetScriptedAnimData(name BaseAnim, float BlendInTime, float BlendOutTime, float AnimRate,
byte AnimIterations, bool bLoopAnim, float StartFrame)
{
super.SetScriptedAnimData(BaseAnim, BlendInTime, BlendOutTime, AnimRate,
AnimIterations, bLoopAnim, StartFrame);
// tell server to replicate animation data immediately -- PooSH
// but it doesn't work anyway...
NetUpdateTime = Level.TimeSeconds - 1;
}
defaultproperties
{
ZombieDamageReduction=1.000000
bUseCoverAnim=True
CowerAnim="CHIdle_LAW"
IdleSeatedAnim="CHIdle_Knife"
IdleStandingAnim="Profile_Idle"
IdleStandingInjuredAnim="Profile_Idle"
StandUpAnim="Profile_Idle"
BleedingSounds(0)=SoundGroup'KF_MaleVoiceOne.Automatic_Commands.Auto_Dying'
BleedingSounds(1)=SoundGroup'KF_MaleVoiceOne.Automatic_Commands.Auto_Dying'
BleedingSounds(2)=SoundGroup'KF_MaleVoiceOne.SUPPORT.MEDIC'
BleedingSounds(3)=SoundGroup'KF_MaleVoiceOne.SUPPORT.MEDIC'
BleedingSounds(4)=SoundGroup'KF_MaleVoiceOne.SUPPORT.MEDIC'
HitAnims(0)="HitF_Knife"
HitAnims(1)="HitB_Knife"
HitAnims(2)="HitL_Knife"
HitAnims(3)="HitR_Knife"
SkeletonMesh=SkeletalMesh'KFSoldiers.Soldier'
RagdollOverride="British_Soldier1"
MovementAnims(0)="JogF_Knife"
MovementAnims(1)="JogB_Knife"
MovementAnims(2)="JogL_Knife"
MovementAnims(3)="JogR_Knife"
IdleChatAnim="Profile_Idle"
Mesh=SkeletalMesh'KF_Soldier_Trip.British_Soldier1'
}

View file

@ -0,0 +1,15 @@
class NPCEnemy_AI extends NPC_AI;
function bool SameTeamAs(Controller C)
{
if( MonsterController(C) != none)
{
return true;
}
return false;
}
defaultproperties
{
}

View file

@ -0,0 +1,15 @@
class NPCFriendly_AI extends NPC_AI;
function bool SameTeamAs(Controller C)
{
if(MonsterController(C) != none)
{
return false;
}
return true;
}
defaultproperties
{
}

View file

@ -0,0 +1,128 @@
// todo: extend KFInvasionBot, which will support scripting
class NPC_AI extends ScriptedController;
var KF_StoryNPC StoryPawn;
var float ReloadTime; // hack to force magazine update
var bool bPendingReload;
// made NPC not following dead bodies
function Pawn GetMyPlayer()
{
local Controller C;
if ( MyPlayerController == None || MyPlayerController.Pawn == None || MyPlayerController.Pawn.Health <= 0 ) {
for ( C = Level.ControllerList; C != none; C = C.nextController ) {
if ( PlayerController(C) != none && C.Pawn != none && C.Pawn.Health > 0 ) {
MyPlayerController = PlayerController(C);
break;
}
}
}
if ( MyPlayerController == None )
return None;
return MyPlayerController.Pawn;
}
simulated function int GetTeamNum()
{
if(StoryPawn == none)
return 255;
else
return StoryPawn.TeamIndex;
}
function Possess(Pawn aPawn)
{
Super.Possess(aPawn);
StoryPawn = KF_StoryNPC(Pawn);
}
state Scripting
{
// added reloading -- PooSH
function bool WeaponFireAgain(float RefireRate, bool bFinishedFire)
{
local KFWeapon W;
W = KFWeapon(Pawn.Weapon);
if ( W != none ) {
if ( bPendingReload )
return false;
if ( W.bIsReloading ) {
// make sure reload finishes
StartWeaponReload();
}
// todo: are there any weapons with MagCapacity=1, which require reload?..
if ( W.MagCapacity > 1 && W.MagAmmoRemaining == 0 ) {
StopFiring();
W.ReloadMeNow();
StartWeaponReload();
return false;
}
}
if ( Pawn(ScriptedFocus) != none && Pawn(ScriptedFocus).Health <= 0 )
ScriptedFocus = none; // stop shooting dead bodies
return super.WeaponFireAgain(RefireRate, bFinishedFire);
}
function StartWeaponReload()
{
local KFWeapon W;
W = KFWeapon(Pawn.Weapon);
if ( W == none )
return;
bPendingReload = true;
ReloadTime = Level.TimeSeconds + W.ReloadRate + 0.1;
Enable('Tick');
W.ClientReload();
W.Instigator.SetAnimAction(W.WeaponReloadAnim);
}
// lame hack, but it seems like KFWeapon.AllowReload() supports only KFInvasionBot and KFFriendlyAI
function ForceWeaponReload()
{
local KFWeapon W;
W = KFWeapon(Pawn.Weapon);
if ( W == none )
return;
W.AddReloadedAmmo();
W.ActuallyFinishReloading();
}
function Tick(float DeltaTime)
{
if ( bPendingReload ) {
if ( Level.TimeSeconds >= ReloadTime ) {
bPendingReload = false;
ForceWeaponReload();
}
}
else if ( bPendingShoot )
{
bPendingShoot = false;
MayShootTarget();
}
if ( !bPendingShoot && !bPendingReload
&& ((CurrentAction == None) || !CurrentAction.StillTicking(self,DeltaTime)) )
disable('Tick');
}
}
defaultproperties
{
}

View file

@ -0,0 +1,66 @@
class NamedObjectTable extends Info;
var private array<name> Names;
var private array<Object> Objects;
// looks for the table or creates a new one, if such doesn't exist
static function NamedObjectTable InitTable(LevelInfo Level, optional name TableTag)
{
local Actor Other;
foreach Level.AllActors(default.class, Other, TableTag)
return NamedObjectTable(Other);
return Level.spawn(default.class, Level, TableTag);
}
function ClearTable()
{
Names.length = 0;
Objects.length = 0;
}
function int TableLength()
{
return Names.Length;
}
// returns Names.length if not foud
function protected int GetIndex(name ObjectName)
{
local int i;
for ( i=0; i<Names.length; ++i )
if ( Names[i] == ObjectName )
break;
return i;
}
function SetObject(name ObjectName, Object Object)
{
local int i;
i = GetIndex(ObjectName);
Names[i] = ObjectName;
Objects[i] = Object;
}
function Object GetObject(name ObjectName)
{
local int i;
i = GetIndex(ObjectName);
if ( i < Objects.length )
return Objects[i];
return none;
}
defaultproperties
{
}

View file

@ -0,0 +1,21 @@
class NamedPRITable extends NamedObjectTable;
// ensure that nothing but PlayerReplicationInfo can be put in table
function SetObject(name ObjectName, Object Object)
{
super.SetObject(ObjectName, PlayerReplicationInfo(Object));
}
function SetPRI(name PRI_Name, PlayerReplicationInfo PRI)
{
super.SetObject(PRI_Name, PRI);
}
function PlayerReplicationInfo GetPRI(name PRI_Name)
{
return PlayerReplicationInfo(GetObject(PRI_Name));
}
defaultproperties
{
}

View file

@ -0,0 +1,73 @@
/*
--------------------------------------------------------------
Condition_ActorHealth_SE
--------------------------------------------------------------
A Condition which tracks the health state of specified Actor(s)
and is marked complete when it drops below a specified threshold.
Enhancements and fixes:
- bSearchOnlyOnce
Author : PooSH
Original Author : Alex Quick
--------------------------------------------------------------
*/
class ObjCondition_ActorHealth_SE extends ObjCondition_ActorHealth
editinlinenew;
var () bool bSearchOnlyOnce;
var transient bool bSearchCompleted;
function Reset()
{
Super.Reset();
HealthMax = 100;
}
function ConditionActivated(pawn ActivatingPlayer)
{
local Actor MyHealthActor;
Super.ConditionActivated(ActivatingPlayer);
MyHealthActor = GetTargetActor(HealthActorName);
if(MyHealthActor != none && !MyHealthActor.bPendingDelete)
bSearchCompleted = true;
}
function ConditionTick(float DeltaTime)
{
if ( bSearchOnlyOnce && bSearchCompleted && GetTargetActor(HealthActorName) == none ) {
// if bSearchOnlyOnce=true and TargetActor was searchen for and not found,
// then count it as dead
HealthMax = 100;
CurrentHealth = 0;
super(KF_ObjectiveCondition).ConditionTick(DeltaTime);
}
else {
super.ConditionTick(DeltaTime);
if ( HealthMax <= 0 )
HealthMax = 100; // prevents 0/0 showing as 100%
}
}
function UpdatePawnList()
{
if( bSearchOnlyOnce && bSearchCompleted )
return;
super.UpdatePawnList();
bSearchCompleted = true;
}
defaultproperties
{
}

View file

@ -0,0 +1,193 @@
/*
--------------------------------------------------------------
ObjCondition_Area_SE
--------------------------------------------------------------
A Condition which is marked complete when a player either
(A) leaves a volume or (B) enters a volume
Can also be configured to check ZoneInfo regions.
Enhancements and fixes:
- Fixed GetLocation() ignoring HUD_World.World_Location
- Aplied difficulty and player count scaling on duration
- LastInAreaTime, LastOutOfAreaTime, bTimingOut changed to
RequirementsMatchedTime and bRequirementsMatched
- Added bCheckDependentsFirst
Author : PooSH
Original Author: Alex Quick
--------------------------------------------------------------
*/
class ObjCondition_Area_SE extends ObjCondition_Area
editinlinenew;
/**
EAreaInstigator - determines which pawn must be used as Instigator:
Instigator_Triggered Instigator stays the one which triggered this area or activated this objective.
Pawns inside this area do not affect Instigator value.
This is default behavior of ObjCondition_Area
Instigator_First Instigator will be set to a pawn, which entered the area first.
If Instigator leaves the area, the next pawn in the area will be used
as Instigator and so on.
Instigator_Last Instigator will be set to a pawn, which entered the area first.
*/
/*
enum EAreaInstigator
{
Instigator_Triggered,
Instigator_First,
Instigator_Last
};
var() EAreaInstigator AreaInstigator;
*/
// if true, DependentConditions will be checked before the Duration
var(KF_ObjectiveCondition) bool bCheckDependentsFirst;
// call DependentConditions[].FindInstigator()?
var(KF_ObjectiveCondition) bool bCheckDependentsInstigator;
// Time since when requiremets are matched
// 0 - requrements are not matched at the current moment
var float RequirementsMatchedTime;
var bool bRequirementsMatched;
var protected float OriginalDuration;
function PostBeginPlay(KF_StoryObjective MyOwner)
{
Super.PostBeginPlay(MyOwner);
OriginalDuration = Duration;
}
function Reset()
{
Super.Reset();
bRequirementsMatched = false;
RequirementsMatchedTime = 0;
}
function ConditionActivated(pawn ActivatingPlayer)
{
Super.ConditionActivated(ActivatingPlayer);
Duration = OriginalDuration * GetTotalDifficultyModifier();
bRequirementsMatched = false;
RequirementsMatchedTime = 0;
}
function ConditionTick(Float DeltaTime)
{
super.ConditionTick(DeltaTime);
if ( CompletionMethod == Method_EnterArea )
bRequirementsMatched = NumInVolume > 0
&& (!bRequiresWholeTeam || NumInVolume >= GetObjOwner().StoryGI.GetTotalActivePlayers());
else
bRequirementsMatched = NumInVolume == 0
|| (!bRequiresWholeTeam && NumInVolume < GetObjOwner().StoryGI.GetTotalActivePlayers());
if (bRequirementsMatched) {
if (bCheckDependentsFirst && !AllowCompletion())
RequirementsMatchedTime = 0.f;
else if ( RequirementsMatchedTime == 0.f )
RequirementsMatchedTime = GetObjOwner().Level.TimeSeconds;
}
else
RequirementsMatchedTime = 0.f;
}
/* returns the percentage of completion for this condition */
function float GetCompletionPct()
{
if ( !bRequirementsMatched || RequirementsMatchedTime == 0.f )
return 0.f;
if ( Duration == 0.f )
return float(bRequirementsMatched);
return FClamp((GetObjOwner().Level.TimeSeconds - RequirementsMatchedTime) / Duration,0.f,1.f);
}
function string GetDataString()
{
local string DataString;
/* if( (CompletionMethod == Method_StayInArea && bTimingOut) ||
(CompletionMethod == Method_EnterArea && !bTimingOut) )
{
DataString = FormatTime(FMax(Duration - (GetObjOwner().Level.TimeSeconds - LastInAreaTime),0.f)) ;
}
*/
if(bRequiresWholeTeam)
{
DataString@="["$NumInVolume$"/"$GetObjOwner().StoryGI.GetTotalActivePlayers()$"]" ;
}
return DataString ;
}
/* Overriden to skip inactive dependencies -- PooSH */
function bool AllowCompletion()
{
local int i;
local array<Pawn> MyInstigators;
if ( DependentConditions.length > 0 ) {
MyInstigators = GetInstigatorList();
for(i = 0 ; i < DependentConditions.length ; i ++) {
if ( DependentConditions[i].ConditionIsActive() ) {
if( !DependentConditions[i].bComplete
|| (bCheckDependentsInstigator && !DependentConditions[i].FindInstigator(MyInstigators)) )
{
return false;
}
}
}
}
return true;
}
// fixes bug when location of KF_StoryObjective is returned instead of Volume
// -- PooSH
function vector GetLocation(optional out Actor LocActor)
{
local Volume MyAreaVolume;
if(ConditionIsActive())
{
// first look if actor is defined in HUD world settings
if ( GetWorldLocActor(LocActor) )
return LocActor.Location;
// if HUD world actor is not defined - try to use Volume, then - Zone
MyAreaVolume = Volume(GetTargetActor(AreaVolumeName));
if(MyAreaVolume != none) {
LocActor = MyAreaVolume;
return LocActor.Location;
}
// AssociatedZone is private and has no getter (including GetTargetActor())
// else if( AreaZoneName != "" && AssociatedZone != none ) {
// LocActor = AssociatedZone;
// return LocActor.Location;
// }
}
// return zero vector instead, if nothing found (not the KF_StoryObjective.Location)
return vect(0,0,0);
}
defaultproperties
{
bCheckDependentsInstigator=True
}

View file

@ -0,0 +1,66 @@
/*
--------------------------------------------------------------
ObjCondition_Counter_SE
--------------------------------------------------------------
Enhanced version if ObjCondition_Counter.
Allows to disable condition reset() on deactivation.
Usefull if condition needs to be activated/deactivated multiple
times during the mission.
- Counting disabled when objective is not active - fixed in KF v1054 too
- Completion count doesn't get lowered after player death
- ConditionIsValid() returns false if ObjOwner is none
Author : PooSH
Original Author : Alex Quick
--------------------------------------------------------------
*/
class ObjCondition_Counter_SE extends ObjCondition_Counter
editinlinenew;
var(ObjCondition_Counter) bool bResetOnDeactivation;
var transient float TotalDifficultyModifier;
function ConditionDeActivated()
{
if ( bResetOnDeactivation )
Reset();
else {
bActive = false;
}
}
// don't let counter lower after player death
function float GetTotalDifficultyModifier()
{
if ( TotalDifficultyModifier == 0 )
TotalDifficultyModifier = super.GetTotalDifficultyModifier();
return TotalDifficultyModifier;
}
function Reset()
{
super.Reset();
TotalDifficultyModifier = 0;
}
// Dunno why, but sometimes it is called if condition is inactive (even if other or none objective is running)
function bool ConditionIsValid()
{
if ( GetObjOwner() == none )
return false; // wtf?
return super.ConditionIsValid();
}
defaultproperties
{
bResetOnDeactivation=True
}

View file

@ -0,0 +1,157 @@
/*
--------------------------------------------------------------
ObjCondition_InventoryInstigator
--------------------------------------------------------------
A Condition which is marked complete when instigator is
carrying an inventory item of the specified class.
Author : PooSH
--------------------------------------------------------------
*/
class ObjCondition_Inventory_SE extends ObjCondition_Inventory
editinlinenew;
/* Array of pawns which are currently holding this item in iventory */
var protected array<Pawn> PawnInstigators;
/**
EItemDestoyMode Allows to auto-destroy the item in inventory.
Item will be destroyer only from Instigator's inventory.
If multiple paws have the same inventory, and you want to destroy
all of them, use ACTION_DropInventory instead
IDM_DontDestroy Default setting. Inventory will not be auto-destroyed.
IDM_DestroyOnComplete Inventory will be destroyed on completion.
Can be used when condition has DependentConditions,
e.g. bring inventory to the area
IDM_DestroyOnTrigger Inventory will be destroyed on trigger received.
Can be used when this condition is in DependentConditions
of parent condition, and parent condition triggers the event
on completion.
e.g. use an actor while holding the inventory
*/
enum EItemDestoyMode
{
IDM_DontDestroy, // default setting - do not destroy inventory
IDM_DestroyOnComplete, // auto destroy inventory on condition completion.
IDM_DestroyOnTrigger // destroy inventory on trigger, if condition complete
};
var(ObjCondition_Inventory) EItemDestoyMode ItemDestoyMode;
// call DependentConditions[].FindInstigator()?
var(KF_ObjectiveCondition) bool bCheckDependentsInstigator;
function Trigger(actor Other, pawn EventInstigator)
{
super.Trigger(Other, EventInstigator);
if ( ItemDestoyMode == IDM_DestroyOnTrigger && ConditionIsValid()
&& DestroyInventoryItem(EventInstigator) )
ConditionTick(0);
}
function Reset()
{
Super.Reset();
PawnInstigators.length = 0;
}
function bool DestroyInventoryItem(Pawn Holder)
{
local Inventory DelItem;
DelItem = FindDesiredItem(Holder);
if (DelItem == none)
return false;
if ( KFHumanPawn_Story(Holder) != none)
KFHumanPawn_Story(Holder).SetHasStoryItem( false );
if(KF_StoryInventoryItem(DelItem) != none)
KF_StoryInventoryItem(DelItem).UpdateHeldMaterial(Holder, none);
DelItem.DetachFromPawn(Holder);
Holder.DeleteInventory(DelItem);
DelItem.Destroy();
return true;
}
function array<Pawn> GetInstigatorList()
{
return PawnInstigators;
}
function ConditionTick(float DeltaTime)
{
local Controller C;
local Pawn MyInstigator;
local bool bInstigatorFound;
PawnInstigators.length = 0;
MyInstigator = GetInstigator();
for ( C=GetObjOwner().Level.ControllerList; C!=None; C=C.NextController ) {
if(PlayerController(C) != none && C.Pawn != none && FindDesiredItem(C.Pawn) != none ) {
PawnInstigators[PawnInstigators.length] = C.Pawn;
bInstigatorFound = bInstigatorFound || MyInstigator == C.Pawn;
}
}
bHeld = PawnInstigators.length > 0;
if ( bHeld && !bInstigatorFound ) {
MyInstigator = PawnInstigators[0];
SetTargetActor(InstigatorName, MyInstigator);
}
Super(KF_ObjectiveCondition).ConditionTick(DeltaTime);
if ( bComplete && ItemDestoyMode == IDM_DestroyOnComplete )
DestroyInventoryItem(GetInstigator());
}
function Inventory FindDesiredItem(Pawn Holder)
{
local Inventory Inv;
if ( Holder == none )
return none;
for( Inv = Holder.Inventory; Inv != None ; Inv = Inv.Inventory ) {
if( ClassIsChildOf(Inv.class,DesiredItemClass)
&& (DesiredItemTag == '' || Inv.tag == DesiredItemTag) )
return Inv;
}
return none;
}
/* Overriden to skip inactive dependencies -- PooSH */
function bool AllowCompletion()
{
local int i;
local array<Pawn> MyInstigators;
if ( DependentConditions.length > 0 ) {
MyInstigators = GetInstigatorList();
for(i = 0 ; i < DependentConditions.length ; i ++) {
if ( DependentConditions[i].ConditionIsActive() ) {
if( !DependentConditions[i].bComplete
|| (bCheckDependentsInstigator && !DependentConditions[i].FindInstigator(MyInstigators)) )
{
return false;
}
}
}
}
return true;
}
defaultproperties
{
bCheckDependentsInstigator=True
}

View file

@ -0,0 +1,142 @@
/*
--------------------------------------------------------------
Condition_Use_SE
--------------------------------------------------------------
Enhanced version if ObjCondition_Multi.
This Condition is marked complete when a player presses the 'Use'
key while in range of its owning actor.
Enhancements:
- Automatically skips invalid conditions (e.g. which didn't matched player counts)
- Option to skip inactive conditions (i.e.
it can be completed even if inactive conditons are incomplete)
- Allow to enable/disable child condition on trigger
Author : PooSH
Original Author : Alex Quick
--------------------------------------------------------------
*/
class ObjCondition_Multi_SE extends ObjCondition_Multi
editinlinenew;
var (ObjCondition_Multi) bool bSkipInactive;
var (ObjCondition_Multi) KFStoryGameInfo.EConditionActivationMethod ChildActivationMethod;
function ConditionTick(float DeltaTime)
{
local int i;
local array<KF_ObjectiveCondition> ValidConditions;
NumConditions = 0;
for(i = 0 ; i < ChildConditions.length ; i ++)
{
if(ChildConditions[i].ConditionIsRelevant() && ChildConditions[i].ConditionIsValid()
&& (!bSkipInactive || ChildConditions[i].ConditionIsActive()) )
{
ValidConditions[ValidConditions.length] = ChildConditions[i];
}
}
NumConditions = ValidConditions.length;
CompleteConditions.length = ValidConditions.length;
for(i = 0 ; i < ValidConditions.length ; i ++)
{
if(ValidConditions[i].bComplete)
{
CompleteConditions[i] = 1 ;
}
else
{
CompleteConditions[i] = 0;
}
}
NumCompleted = 0;
for(i = 0 ; i < NumConditions ; i ++)
{
if(CompleteConditions[i] == 1)
{
NumCompleted ++ ;
}
}
Super(KF_ObjectiveCondition).ConditionTick(DeltaTime);
}
function float GetCompletionPct()
{
if ( NumCompleted >= NumConditions )
return 1.f;
return float(NumCompleted) / float(NumConditions) ;
}
function Trigger(actor Other, pawn EventInstigator)
{
local int i;
super.Trigger(Other, EventInstigator);
if( ConditionIsActive() && ConditionIsValid() && ChildActivationMethod != Null )
{
if ( ChildActivationMethod == RandomlyActivate ) {
warn("RandomlyActivate can not be used as ChildActivationMethod");
return;
}
for(i = 0 ; i < ChildConditions.length ; i ++)
{
if( !ChildConditions[i].ConditionIsValid() )
continue;
switch(ChildActivationMethod)
{
case TriggerToggled :
ChildConditions[i].SetTargetActor(InstigatorName, EventInstigator);
if(!ChildConditions[i].bActive)
{
log(name $ " Activating Child Condition By Trigger - " $ ChildConditions[i].name);
GetObjOwner().ActivateCondition(ChildConditions[i]);
ChildConditions[i].ConditionActivated(EventInstigator);
}
else
{
log(name $ " Deactivating Child Condition By Trigger - " $ ChildConditions[i].name);
GetObjOwner().DeActivateCondition(ChildConditions[i]);
}
break;
case TriggerActivates :
if(!ChildConditions[i].bActive)
{
log(name $ " Activating Child Condition By Trigger - " $ ChildConditions[i].name);
ChildConditions[i].SetTargetActor(InstigatorName, EventInstigator);
GetObjOwner().ActivateCondition(ChildConditions[i]);
ChildConditions[i].ConditionActivated(EventInstigator);
}
break;
case TriggerDeActivates :
if(ChildConditions[i].bActive)
{
log(name $ " Deactivating Child Condition By Trigger - " $ ChildConditions[i].name);
ChildConditions[i].SetTargetActor(InstigatorName, EventInstigator);
GetObjOwner().DeActivateCondition(ChildConditions[i]);
}
break;
}
}
}
}
defaultproperties
{
}

View file

@ -0,0 +1,233 @@
class ObjCondition_Use_SE extends ObjCondition_Use
editinlinenew;
/*
--------------------------------------------------------------
ObjCondition_Use_SE
--------------------------------------------------------------
Enhanced version of ObjCondition_Use.
- Fixed a bug when GetCompletionPct() could return value > 1
- Added bCheckDependentsFirst
- ConditionTick() stops using if bCheckDependentsFirst and DependentConditions incomplete
- HoldUseSeconds use difficulty modifiers
- if Screen_CountStyle == Count_Down, then seconds left are displayed on the HUD
- If UseActor uses collision cylinder, it will be always taken
in place, not only when it is blocking actors
Author : PooSH
Original Author: Alex Quick
--------------------------------------------------------------
*/
// if true, DependentConditions the progression
var(KF_ObjectiveCondition) bool bCheckDependentsFirst;
// call DependentConditions[].FindInstigator()?
var(KF_ObjectiveCondition) bool bCheckDependentsInstigator;
var(Audio) sound UsingSound;
var protected sound OriginalUsingSound;
var protected transient float InitialHoldUseSeconds;
function Reset()
{
ResetSound();
Super.Reset();
SetTargetActor(CurrentUserName,none);
}
function ResetSound()
{
local Actor MyUseActor;
// restore original ambient sound (mostly - none)
if ( UsingSound != OriginalUsingSound ) {
MyUseActor = GetTargetActor(UseActorName);
if ( MyUseActor != none && MyUseActor.AmbientSound != OriginalUsingSound )
MyUseActor.AmbientSound = OriginalUsingSound;
}
}
function PostBeginPlay(KF_StoryObjective MyOwner)
{
local Actor MyUseActor;
Super.PostBeginPlay(MyOwner);
InitialHoldUseSeconds = HoldUseSeconds;
MyUseActor = GetTargetActor(UseActorName);
if ( MyUseActor != none )
OriginalUsingSound = MyUseActor.AmbientSound;
}
function SetObjOwner(KF_StoryObjective NewOwner)
{
ResetSound(); // just to be sure we won't leave ambient sound for old actor
super.SetObjOwner(NewOwner);
}
function ConditionActivated(pawn ActivatingPlayer)
{
if ( InitialHoldUseSeconds > 0 )
HoldUseSeconds = InitialHoldUseSeconds * GetTotalDifficultyModifier();
Super.ConditionActivated(ActivatingPlayer);
}
function ConditionTick(float DeltaTime)
{
Super.ConditionTick(DeltaTime);
// stop using objective, if we don't match condition
if ( bCheckDependentsFirst && GetTargetActor(CurrentUserName) != none && !AllowCompletion() )
StopUsingObj(Pawn(GetTargetActor(CurrentUserName)));
}
function bool IsTouchingUseActor(pawn Toucher)
{
local Actor A;
local float DistSq;
local Actor MyUseActor;
MyUseActor = GetTargetActor(UseActorName);
if(Toucher != none && MyUseActor != none)
{
foreach Toucher.TouchingActors(class 'Actor', A)
{
if(A == MyUseActor)
{
return true;
}
}
// added bUseCylinderCollision in cases use actor doesn't want block actors (e.g. use blocking volume)
// but just wants to collide with actors
// -- PooSH
if( MyUseActor.bBlockActors || MyUseActor.bUseCylinderCollision )
{
DistSq = VsizeSquared(MyUseActor.Location - Toucher.Location) ;
if(DistSq <= Square( (MyUseActor.CollisionRadius + (Toucher.CollisionRadius)) * 1.25 ) )
{
return true;
}
}
}
return false;
}
function Startedusing(pawn User)
{
local Pawn CurrentUser;
local KF_UseableMover ControlledMover;
local Actor MyUseActor;
local Pawn OriginalIntigator;
local bool bCantUse;
if ( bComplete )
return; // no point of using already completed objective
MyUseActor = GetTargetActor(UseActorName);
ControlledMover = KF_UseableMover(GetTargetActor(ControlledMoverName));
CurrentUser = Pawn(GetTargetActor(CurrentUserName));
if(CurrentUser == none && IsTouchingUseActor(User))
{
OriginalIntigator = GetInstigator();
// Instigator must be set to check DependentConditions
if ( OriginalIntigator != User )
SetTargetActor(InstigatorName, User);
bCantUse = bCheckDependentsFirst && !AllowCompletion();
if ( bCantUse ) {
SetTargetActor(InstigatorName, OriginalIntigator); // restore original instigator
return;
}
SetTargetActor(InstigatorName,User);
SetTargetActor(CurrentUserName,User);
LastUseTime = User.Level.TimeSeconds;
if(AllowCompletion())
{
bWasUsed = true;
}
if(ControlledMover != none)
{
ControlledMover.StartedUsing();
}
if ( HoldUseSeconds > 0 && UsingSound != none ) {
MyUseActor.AmbientSound = UsingSound;
MyUseActor.NetUpdateTime = MyUseActor.Level.TimeSeconds - 1;
}
}
}
function StopUsingObj(pawn User)
{
ResetSound();
super.StopUsingObj(User);
// ensure that we are not going abouve 100%
if ( FinishedUseSeconds > HoldUseSeconds )
FinishedUseSeconds = HoldUseSeconds;
}
// fixed progress going above 100% -- PooSH
function float GetCompletionPct()
{
return fclamp(super.GetCompletionPct(), 0.f, 1.f);
}
// count down style shows remaining time -- PooSH
function string GetDataString()
{
if(HoldUseSeconds > 0)
{
if ( HUD_Screen.Screen_CountStyle == 0 ) {
return string( (1.f-(GetRemainingUseTime() / HoldUseSeconds))*100.f) $"%" ;
}
else {
return string(GetRemainingUseTime()) ;
}
}
return "" ;
}
/* Overriden to skip inactive dependencies -- PooSH */
function bool AllowCompletion()
{
local int i;
local array<Pawn> MyInstigators;
if ( DependentConditions.length > 0 ) {
MyInstigators = GetInstigatorList();
for(i = 0 ; i < DependentConditions.length ; i ++) {
if ( DependentConditions[i].ConditionIsActive() ) {
if( !DependentConditions[i].bComplete
|| (bCheckDependentsInstigator && !DependentConditions[i].FindInstigator(MyInstigators)) )
{
return false;
}
}
}
}
return true;
}
defaultproperties
{
bCheckDependentsInstigator=True
}

View file

@ -0,0 +1,29 @@
/*
--------------------------------------------------------------
ObjCondition_Counter_SE
--------------------------------------------------------------
Bug-fixed version if ObjCondition_Counter_SE.
- ConditionIsValid() returns false if ObjOwner is none
Author : PooSH
Original Author : Alex Quick
--------------------------------------------------------------
*/
class ObjCondition_WaveCounter_SE extends ObjCondition_WaveCounter;
// Dunno why, but sometimes it is called if condition is inactive (even if other or none objective is running)
function bool ConditionIsValid()
{
if ( GetObjOwner() == none )
return false; // wtf?
return super.ConditionIsValid();
}
defaultproperties
{
}

View file

@ -0,0 +1,86 @@
class PickupModifier extends Actor
placeable;
var() class<Pickup> PickupClass; // base pickup class to apply properties on
var() name PickupTag; // pickup tag to apply properties on
var() bool bRemoveGlow; // removes UV2Texture and AmbientGlow
var() bool bRemoveUnlit; // sets bUnlit=false (can't see pickups in dark places)
var() int SellValue; // if > 0 sets weapon's sell value (only for KFWeaponPickup)
replication {
reliable if ( (bNetInitial || bNetDirty) && Role == ROLE_Authority )
bRemoveGlow, bRemoveUnlit, SellValue;
}
simulated function PostBeginPlay()
{
SetTimer(5, false); // give enough time for pickups to spawn
}
simulated function PostNetReceive()
{
ApplyMod();
}
simulated function Timer()
{
ApplyMod();
}
simulated function ApplyMod()
{
local Actor Other;
local int count;
foreach DynamicActors(PickupClass, Other, PickupTag) {
count++;
if ( bRemoveGlow ) {
Other.ScaleGlow = 0;
Other.AmbientGlow = 0;
Pickup(Other).bAmbientGlow = false;
Other.UV2Texture = none;
}
if ( bRemoveUnlit ) {
SetDisplayProperties(Style, Texture, false); // sets bUnlit
Other.bStaticLighting = false;
Other.bUseDynamicLights = true;
}
if ( SellValue > 0 && KFWeaponPickup(Other) != none ) {
KFWeaponPickup(Other).SellValue = SellValue;
}
}
Log("PickupModifier: " $ count $ " pickups were modified");
}
event Trigger( Actor Other, Pawn EventInstigator )
{
ApplyMod();
bClientTrigger = !bClientTrigger;
NetUpdateTime = Level.TimeSeconds - 1;
}
simulated function ClientTrigger()
{
Log("PickupModifier: ClientTrigger()");
ApplyMod();
}
defaultproperties
{
PickupClass=Class'KFMod.KFWeaponPickup'
bHidden=True
bAlwaysRelevant=True
bOnlyDirtyReplication=True
RemoteRole=ROLE_SimulatedProxy
NetUpdateFrequency=2.000000
Texture=Texture'Engine.S_Inventory'
bNetNotify=True
}

View file

@ -0,0 +1,66 @@
// This class is required to replicate player (speaker) names of KF_PlayerDialogue
class PlayerDialogueReplicationInfo extends ReplicationInfo;
var name DialogueActorName;
var int DlgIndex;
var PlayerReplicationInfo SpeakerPRI;
var private int NumTries;
replication {
reliable if ( bNetInitial && Role == ROLE_Authority )
DialogueActorName;
reliable if ( bNetDirty && Role == ROLE_Authority )
DlgIndex, SpeakerPRI;
}
simulated function PostNetReceive()
{
NumTries = 10; // try 10 times to find diealogue, then - give up
Timer();
}
simulated function Timer()
{
local KFPlayerController_Story MyPC;
local HUD_StoryMode MyHUD;
local int HUDDlgIndex;
local KF_DialogueSpot NewDlg;
SetTimer(0, false);
MyPC = KFPlayerController_Story(Level.GetLocalPlayerController());
if ( MyPC == none )
return;
MyHUD = HUD_StoryMode(MyPC.MyHUD);
if ( MyHUD == none )
return;
NewDlg = MyHUD.FindDialogueActor(DialogueActorName);
if(NewDlg == none)
{
log("Warning - could not find Dialogue Actor of name : "@DialogueActorName@" Aborting HUD Render. ", 'PlayerDialogueReplicationInfo');
return;
}
if(!MyHUD.FindExistingDialogue(NewDlg.Dialogues[DlgIndex].Display.Dialogue_text,HUDDlgIndex)) {
// try again later - maybe KFPlayerController_Story.ClientShowStoryDialogue() isn't replicated yet
if ( NumTries > 0) {
SetTimer(0.1, false);
NumTries--;
}
return;
}
MyHUD.Dialogues[HUDDlgIndex].Speaker = SpeakerPRI.PlayerName;
MyHUD.Dialogues[HUDDlgIndex].Portrait = SpeakerPRI.GetPortrait();
}
defaultproperties
{
NetUpdateFrequency=5.000000
bNetNotify=True
}

View file

@ -0,0 +1,51 @@
class Touchable extends Actor;
var private Pawn MyPawn;
var() bool bTouchable;
var() bool bUsable;
function Pawn GetMyPawn()
{
return MyPawn;
}
function SetMyPawn(Pawn NewPawn)
{
if ( MyPawn == NewPawn )
return;
if ( MyPawn != none ) {
bHardAttach = false;
SetBase(none);
}
MyPawn = NewPawn;
if ( MyPawn != none ) {
bHardAttach = true;
SetLocation( MyPawn.Location );
SetBase(MyPawn);
SetCollisionSize(MyPawn.CollisionRadius * 1.5, MyPawn.CollisionHeight) ;
}
}
function Touch( Actor Other )
{
if ( bTouchable && MyPawn != none && MyPawn != Other )
MyPawn.Touch(Other);
}
function UsedBy( Pawn user )
{
if ( bUsable && MyPawn != none && MyPawn != user )
MyPawn.UsedBy(user);
}
defaultproperties
{
bHidden=True
RemoteRole=ROLE_None
CollisionRadius=30.000000
CollisionHeight=40.000000
bCollideActors=True
}

View file

@ -0,0 +1,43 @@
// causes touching Pawns' damage or death on trigger
class TriggerDamageVolume extends Volume;
var() class<Pawn> PawnClass;
var() name PawnTag;
var() int Damage;
var() class<DamageType> DamageType;
var() bool bInstantKill; // instant kill pawns instead of damaging them
function Trigger( actor Other, pawn EventInstigator )
{
local Pawn P;
local vector VoidVector;
local Controller Killer;
local int i;
// log("TriggerDamageVolume triggered: " $ name $ " by " $ EventInstigator);
// PlayerController(EventInstigator.Controller).ClientMessage("TriggerDamageVolume"@name@"triggered");
if ( EventInstigator != none )
Killer = EventInstigator.Controller;
// TouchingActors doesn't work correctly, because after killing the pawn,
// it gets removed from the array, breaking the iterator
for ( i = Touching.length-1 ; i >= 0 ; --i ) {
P = Pawn(Touching[i]);
if ( P != none && (P.Tag == PawnTag || PawnTag == '') ) {
if ( bInstantKill )
P.Died(Killer, DamageType , P.Location);
else
P.TakeDamage(Damage, EventInstigator, P.Location, VoidVector, DamageType);
}
}
}
defaultproperties
{
PawnClass=Class'KFMod.KFMonster'
DamageType=Class'Engine.Suicided'
bInstantKill=True
}

View file

@ -0,0 +1,62 @@
<html>
<head><title>Index of /kf_sources/ScrnStoryGame/Classes/</title></head>
<body>
<h1>Index of /kf_sources/ScrnStoryGame/Classes/</h1><hr><pre><a href="../">../</a>
<a href="ACTION_AddAmmo.uc">ACTION_AddAmmo.uc</a> 01-Jul-2020 18:12 656
<a href="ACTION_AddShield.uc">ACTION_AddShield.uc</a> 01-Jul-2020 18:12 953
<a href="ACTION_DamagePawn.uc">ACTION_DamagePawn.uc</a> 01-Jul-2020 18:12 2134
<a href="ACTION_Flashlight.uc">ACTION_Flashlight.uc</a> 01-Jul-2020 18:12 647
<a href="ACTION_IfActorCount.uc">ACTION_IfActorCount.uc</a> 01-Jul-2020 18:12 819
<a href="ACTION_IfHasEnemy.uc">ACTION_IfHasEnemy.uc</a> 01-Jul-2020 18:12 619
<a href="ACTION_IfIsHurt.uc">ACTION_IfIsHurt.uc</a> 01-Jul-2020 18:12 416
<a href="ACTION_IfMyPlayerClose.uc">ACTION_IfMyPlayerClose.uc</a> 01-Jul-2020 18:12 482
<a href="ACTION_IfPlayerCount.uc">ACTION_IfPlayerCount.uc</a> 01-Jul-2020 18:12 1735
<a href="ACTION_IfSeesEnemy.uc">ACTION_IfSeesEnemy.uc</a> 01-Jul-2020 18:12 648
<a href="ACTION_KillPawn.uc">ACTION_KillPawn.uc</a> 01-Jul-2020 18:12 1306
<a href="ACTION_MoveToEnemyTimed.uc">ACTION_MoveToEnemyTimed.uc</a> 01-Jul-2020 18:12 661
<a href="ACTION_MoveToPlayerTimed.uc">ACTION_MoveToPlayerTimed.uc</a> 01-Jul-2020 18:12 1276
<a href="ACTION_NPC_Active.uc">ACTION_NPC_Active.uc</a> 01-Jul-2020 18:12 424
<a href="ACTION_NPC_Bleed.uc">ACTION_NPC_Bleed.uc</a> 01-Jul-2020 18:12 494
<a href="ACTION_PlayKFReplicatedAmbientSound.uc">ACTION_PlayKFReplicatedAmbientSound.uc</a> 01-Jul-2020 18:12 1174
<a href="ACTION_STORY_MonsterRules.uc">ACTION_STORY_MonsterRules.uc</a> 01-Jul-2020 18:12 992
<a href="ACTION_SetEnemy.uc">ACTION_SetEnemy.uc</a> 01-Jul-2020 18:12 1932
<a href="ACTION_SetPhysicsOther.uc">ACTION_SetPhysicsOther.uc</a> 01-Jul-2020 18:12 1336
<a href="ACTION_SpawnRandomPickups.uc">ACTION_SpawnRandomPickups.uc</a> 01-Jul-2020 18:12 1796
<a href="ACTION_SpawnZED.uc">ACTION_SpawnZED.uc</a> 01-Jul-2020 18:12 3505
<a href="ACTION_WaitForAnyPlayer.uc">ACTION_WaitForAnyPlayer.uc</a> 01-Jul-2020 18:12 1500
<a href="ACTION_WaitForTimerRandom.uc">ACTION_WaitForTimerRandom.uc</a> 01-Jul-2020 18:12 549
<a href="ACTION_ZEDTIME.uc">ACTION_ZEDTIME.uc</a> 01-Jul-2020 18:12 366
<a href="DLGFILTER_Deaths.uc">DLGFILTER_Deaths.uc</a> 01-Jul-2020 18:12 507
<a href="DLGFILTER_Female.uc">DLGFILTER_Female.uc</a> 01-Jul-2020 18:12 248
<a href="DLGFILTER_Instigator.uc">DLGFILTER_Instigator.uc</a> 01-Jul-2020 18:12 279
<a href="DLGFILTER_KillAssists.uc">DLGFILTER_KillAssists.uc</a> 01-Jul-2020 18:12 875
<a href="DLGFILTER_Kills.uc">DLGFILTER_Kills.uc</a> 01-Jul-2020 18:12 741
<a href="DLGFILTER_Perk.uc">DLGFILTER_Perk.uc</a> 01-Jul-2020 18:12 1573
<a href="DLGFILTER_Score.uc">DLGFILTER_Score.uc</a> 01-Jul-2020 18:12 711
<a href="DamTypeBleedFixed.uc">DamTypeBleedFixed.uc</a> 01-Jul-2020 18:12 101
<a href="DialoguePlayerFilter.uc">DialoguePlayerFilter.uc</a> 01-Jul-2020 18:12 297
<a href="Dialogue_EventListener_SE.uc">Dialogue_EventListener_SE.uc</a> 01-Jul-2020 18:12 1103
<a href="FragPickup_SE.uc">FragPickup_SE.uc</a> 01-Jul-2020 18:12 930
<a href="KFDestroyableStaticMesh_SE.uc">KFDestroyableStaticMesh_SE.uc</a> 01-Jul-2020 18:12 856
<a href="KF_BreakerBoxNPC_SE.uc">KF_BreakerBoxNPC_SE.uc</a> 01-Jul-2020 18:12 5245
<a href="KF_DialogueSpot_SE.uc">KF_DialogueSpot_SE.uc</a> 01-Jul-2020 18:12 5384
<a href="KF_PlayerDialogue.uc">KF_PlayerDialogue.uc</a> 01-Jul-2020 18:12 9616
<a href="KF_StoryNPC_Spawnable_SE.uc">KF_StoryNPC_Spawnable_SE.uc</a> 01-Jul-2020 18:12 11388
<a href="NPCEnemy_AI.uc">NPCEnemy_AI.uc</a> 01-Jul-2020 18:12 188
<a href="NPCFriendly_AI.uc">NPCFriendly_AI.uc</a> 01-Jul-2020 18:12 190
<a href="NPC_AI.uc">NPC_AI.uc</a> 01-Jul-2020 18:12 2814
<a href="NamedObjectTable.uc">NamedObjectTable.uc</a> 01-Jul-2020 18:12 1137
<a href="NamedPRITable.uc">NamedPRITable.uc</a> 01-Jul-2020 18:12 482
<a href="ObjCondition_ActorHealth_SE.uc">ObjCondition_ActorHealth_SE.uc</a> 01-Jul-2020 18:12 1685
<a href="ObjCondition_Area_SE.uc">ObjCondition_Area_SE.uc</a> 01-Jul-2020 18:12 5711
<a href="ObjCondition_Counter_SE.uc">ObjCondition_Counter_SE.uc</a> 01-Jul-2020 18:12 1622
<a href="ObjCondition_Inventory_SE.uc">ObjCondition_Inventory_SE.uc</a> 01-Jul-2020 18:12 4555
<a href="ObjCondition_Multi_SE.uc">ObjCondition_Multi_SE.uc</a> 01-Jul-2020 18:12 4047
<a href="ObjCondition_Use_SE.uc">ObjCondition_Use_SE.uc</a> 01-Jul-2020 18:12 6356
<a href="ObjCondition_WaveCounter_SE.uc">ObjCondition_WaveCounter_SE.uc</a> 01-Jul-2020 18:12 758
<a href="PickupModifier.uc">PickupModifier.uc</a> 01-Jul-2020 18:12 2006
<a href="PlayerDialogueReplicationInfo.uc">PlayerDialogueReplicationInfo.uc</a> 01-Jul-2020 18:12 1709
<a href="Touchable.uc">Touchable.uc</a> 01-Jul-2020 18:12 920
<a href="TriggerDamageVolume.uc">TriggerDamageVolume.uc</a> 01-Jul-2020 18:12 1267
</pre><hr></body>
</html>

View file

@ -0,0 +1,7 @@
<html>
<head><title>Index of /kf_sources/ScrnStoryGame/</title></head>
<body>
<h1>Index of /kf_sources/ScrnStoryGame/</h1><hr><pre><a href="../">../</a>
<a href="Classes/">Classes/</a> 29-Jun-2025 19:43 -
</pre><hr></body>
</html>