511 lines
16 KiB
Ucode
511 lines
16 KiB
Ucode
//==============================================================================
|
|
// NicePack / NiceMonsterController
|
|
//==============================================================================
|
|
// New base class for zeds that makes it easier to implement various changes
|
|
// and bug fixes.
|
|
// Functionality:
|
|
// - Removed threat assessment functionality in favor of vanilla's
|
|
// distance-based behavior
|
|
// - Doesn't support 'bNoAutoHuntEnemies' flag from 'KFMonster'
|
|
//==============================================================================
|
|
// 'Nice pack' source
|
|
// Do whatever the fuck you want with it
|
|
// Author: dkanus
|
|
// E-mail: dkanus@gmail.com
|
|
//==============================================================================
|
|
class NiceMonsterController extends KFMonsterController;
|
|
|
|
var float startleTimeLeft;
|
|
|
|
// Just reset threat assesment flag, since it's not used in NicePack
|
|
function PostBeginPlay(){
|
|
super.PostBeginPlay();
|
|
bUseThreatAssessment = true;
|
|
}
|
|
event bool NotifyBump(Actor other){
|
|
local Pawn otherPawn;
|
|
Disable('NotifyBump');
|
|
otherPawn = Pawn(other);
|
|
if(otherPawn == none || otherPawn.controller == none) return false;
|
|
if(enemy == otherPawn) return false;
|
|
if(SetEnemy(otherPawn)){
|
|
WhatToDoNext(4);
|
|
return false;
|
|
}
|
|
if(enemy == otherPawn) return false;
|
|
if(!AdjustAround(otherPawn))
|
|
CancelCampFor(otherPawn.controller);
|
|
return false;
|
|
}
|
|
|
|
function Startle(Actor Feared)
|
|
{
|
|
if ( Monster(Pawn) != none)
|
|
{
|
|
GoalString = "STARTLED!";
|
|
StartleActor = Feared;
|
|
GotoState('Startled');
|
|
}
|
|
}
|
|
|
|
function StartleFor(Actor feared, float startleDuration)
|
|
{
|
|
startleTimeLeft = startleDuration;
|
|
Startle(feared);
|
|
}
|
|
|
|
state Startled{
|
|
ignores EnemyNotVisible,SeePlayer,HearNoise;
|
|
function Startle(Actor feared)
|
|
{
|
|
goalString = "STARTLED!";
|
|
startleActor = feared;
|
|
BeginState();
|
|
}
|
|
function StartleFor(Actor feared, float startleDuration)
|
|
{
|
|
startleTimeLeft = startleDuration;
|
|
Startle(feared);
|
|
}
|
|
function BeginState(){
|
|
if(startleActor == none){
|
|
GotoState('');
|
|
return;
|
|
}
|
|
pawn.acceleration = pawn.location - startleActor.location;
|
|
pawn.acceleration.Z = 0;
|
|
pawn.bIsWalking = false;
|
|
pawn.bWantsToCrouch = false;
|
|
if(pawn.acceleration == vect(0,0,0))
|
|
pawn.acceleration = VRand();
|
|
pawn.acceleration = pawn.accelRate * Normal(pawn.acceleration);
|
|
}
|
|
Begin:
|
|
/*if( NiceHumanPawn(StartleActor) == none
|
|
|| KFGameType(Level.Game) == none
|
|
|| KFGameType(Level.Game).bZEDTimeActive ){
|
|
Sleep(0.5);
|
|
WhatToDoNext(11);
|
|
}
|
|
else{
|
|
Sleep(0.25);
|
|
Goto('Begin');
|
|
}*/
|
|
startleTimeLeft -= 0.5;
|
|
if (startleTimeLeft <= 0) {
|
|
WhatToDoNext(11);
|
|
}
|
|
else {
|
|
Sleep(0.5);
|
|
Goto('Begin');
|
|
}
|
|
}
|
|
function bool IsMonsterDecapitated(){
|
|
local NiceMonster niceZed;
|
|
niceZed = NiceMonster(self.pawn);
|
|
if(niceZed == none) return false;
|
|
return niceZed.bDecapitated || niceZed.HeadHealth <= 0;
|
|
}
|
|
function bool IsMonsterMad(){
|
|
local NiceMonster niceZed;
|
|
niceZed = NiceMonster(self.pawn);
|
|
if(niceZed == none) return false;
|
|
return niceZed.madnessCountDown > 0.0;
|
|
}
|
|
function bool FindNewEnemy(){
|
|
local bool bSeeBest;
|
|
local bool bDecapitated, bAttacksAnything;
|
|
local float bestScore, newScore;
|
|
local Pawn bestEnemy;
|
|
local Controller ctrlIter;
|
|
if(pawn == none) return false;
|
|
bDecapitated = IsMonsterDecapitated();
|
|
bAttacksAnything = bDecapitated || IsMonsterMad();
|
|
for(ctrlIter = Level.controllerList;
|
|
ctrlIter != none;
|
|
ctrlIter = ctrlIter.nextController){
|
|
if(ctrlIter == none || ctrlIter.pawn == none) continue;
|
|
if(ctrlIter.pawn.health <= 0 || ctrlIter.pawn == self.pawn) continue;
|
|
if(ctrlIter.bPendingDelete || ctrlIter.pawn.bPendingDelete) continue;
|
|
// Shouldn't normally attack healthy zeds
|
|
if( !bAttacksAnything && NiceMonster(ctrlIter.pawn) != none
|
|
&& ctrlIter.pawn.health > 15) continue;
|
|
// Can only stand up to fleshpound if we're decapitated
|
|
if(NiceZombieFleshpound(ctrlIter.pawn) != none && !bDecapitated)
|
|
continue;
|
|
|
|
// NicePack doesn't use threat assesment, so just find closest target
|
|
newScore = VSizeSquared(ctrlIter.pawn.Location - pawn.Location);
|
|
if(bestEnemy == none || newScore < bestScore){
|
|
bestEnemy = ctrlIter.pawn;
|
|
bestScore = newScore;
|
|
bSeeBest = CanSee(bestEnemy);
|
|
}
|
|
}
|
|
if(bestEnemy == enemy) return false;
|
|
if(bestEnemy != none){
|
|
ChangeEnemy(bestEnemy, bSeeBest);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
function UpdatePathFindState(){
|
|
if(pathFindState == 0){
|
|
initialPathGoal = FindRandomDest();
|
|
pathFindState = 1;
|
|
}
|
|
if(pathFindState == 1){
|
|
if(initialPathGoal == none)
|
|
pathFindState = 2;
|
|
else if(ActorReachable(initialPathGoal)){
|
|
MoveTarget = initialPathGoal;
|
|
pathFindState = 2;
|
|
return;
|
|
}
|
|
else if(FindBestPathToward(initialPathGoal, true, true))
|
|
return;
|
|
else
|
|
pathFindState = 2;
|
|
}
|
|
}
|
|
function PickRandomDestination(){
|
|
local bool bCloseToEnemy;
|
|
local bool bCanTrackEnemy;
|
|
local NiceMonster niceZed;
|
|
niceZed = NiceMonster(pawn);
|
|
if(niceZed == none) return;
|
|
if(enemy != none)
|
|
bCloseToEnemy = VSizeSquared(niceZed.Location - enemy.Location) < 40000;
|
|
// Can we track our enemy?
|
|
if(enemy != none && niceZed.headHealth > 0 && FRand() < 0.5){
|
|
bCanTrackEnemy = MoveTarget == enemy;
|
|
if(!ActorReachable(enemy))
|
|
bCanTrackEnemy = false;
|
|
if(niceZed.default.health < 500 && !bCloseToEnemy)
|
|
bCanTrackEnemy = false;
|
|
}
|
|
// Choose random location
|
|
if(bCanTrackEnemy)
|
|
destination = enemy.location + VRand() * 50;
|
|
else
|
|
destination = niceZed.location + VRand() * 200;
|
|
}
|
|
state ZombieHunt{
|
|
function BeginState(){
|
|
local float zDif;
|
|
|
|
if(pawn.collisionRadius > 27 || pawn.collisionHeight > 46){
|
|
zDif = Pawn.collisionHeight - 44;
|
|
Pawn.SetCollisionSize(24, 44);
|
|
Pawn.MoveSmooth(vect(0,0,-1) * zDif);
|
|
}
|
|
}
|
|
function EndState(){
|
|
local float zDif;
|
|
local bool bCollisionSizeChanged;
|
|
|
|
bCollisionSizeChanged =
|
|
pawn.collisionRadius != pawn.default.collisionRadius;
|
|
bCollisionSizeChanged = bCollisionSizeChanged ||
|
|
pawn.collisionHeight != pawn.default.collisionHeight;
|
|
|
|
if(pawn != none && bCollisionSizeChanged){
|
|
zDif = pawn.Default.collisionRadius - 44;
|
|
pawn.MoveSmooth(vect(0,0,1) * zDif);
|
|
pawn.SetCollisionSize( pawn.Default.collisionRadius,
|
|
pawn.Default.collisionHeight);
|
|
}
|
|
}
|
|
function Timer(){
|
|
if(pawn.Velocity == vect(0,0,0))
|
|
GotoState('ZombieRestFormation', 'Moving');
|
|
SetCombatTimer();
|
|
StopFiring();
|
|
}
|
|
function PickDestinationEnemyDied(){
|
|
}
|
|
function PickDestination(){
|
|
// Change behaviour in case we're 'BRAINS_Retarded'
|
|
if(KFM.intelligence == BRAINS_Retarded){
|
|
// Some of the TWI's code
|
|
if(FindFreshBody()) return;
|
|
if(enemy != none && !KFM.bCannibal && enemy.health <= 0){
|
|
enemy = none;
|
|
WhatToDoNext(23);
|
|
return;
|
|
}
|
|
UpdatePathFindState();
|
|
if(pawn.JumpZ > 0)
|
|
pawn.bCanJump = true;
|
|
// And just pick random location
|
|
PickRandomDestination();
|
|
return;
|
|
}
|
|
else
|
|
super.PickDestination();
|
|
}
|
|
}
|
|
function NotifyTakeHit( Pawn InstigatedBy,
|
|
Vector HitLocation,
|
|
int damage,
|
|
class<DamageType> damageType,
|
|
Vector momentum){
|
|
local KFMonster zed;
|
|
local bool bZedCanVomit;
|
|
if(class<DamTypeBlowerThrower>(damageType) == none || damage <= 0) return;
|
|
foreach VisibleCollidingActors(class'KFMonster', zed, 1000, pawn.location){
|
|
bZedCanVomit = zed.IsA('NiceZombieBloatBase');
|
|
bZedCanVomit = bZedCanVomit || zed.IsA('NiceZombieSickBase');
|
|
if(bZedCanVomit && zed != pawn && KFHumanPawn(instigatedBy) != none){
|
|
if(KFMonster(pawn) != none)
|
|
SetEnemy(zed, true, KFMonster(pawn).HumanBileAggroChance);
|
|
return;
|
|
}
|
|
}
|
|
super.NotifyTakeHit(InstigatedBy,HitLocation, damage, damageType, momentum);
|
|
}
|
|
state ZombieCharge{
|
|
function SeePlayer(Pawn seen){
|
|
if(KFM.intelligence == BRAINS_Human)
|
|
SetEnemy(Seen);
|
|
}
|
|
function DamageAttitudeTo(Pawn other, float damage){
|
|
if(KFM.intelligence >= BRAINS_Mammal && other!=none && SetEnemy(other))
|
|
SetEnemy(other);
|
|
}
|
|
function HearNoise(float loudness, Actor noiseMaker){
|
|
if(KFM.intelligence != BRAINS_Human) return;
|
|
if(noiseMaker == none && noiseMaker.Instigator == none) return;
|
|
|
|
if(FastTrace(noiseMaker.location, pawn.location))
|
|
SetEnemy(noiseMaker.Instigator);
|
|
}
|
|
function bool StrafeFromDamage( float damage,
|
|
class<DamageType> damageType,
|
|
bool bFindDest){
|
|
return false;
|
|
}
|
|
function bool TryStrafe(vector sideDir){
|
|
return false;
|
|
}
|
|
Begin:
|
|
if(pawn.physics == PHYS_Falling){
|
|
focus = enemy;
|
|
destination = enemy.location;
|
|
WaitForLanding();
|
|
}
|
|
if(enemy == none)
|
|
WhatToDoNext(16);
|
|
WaitForAnim:
|
|
while(KFM.bShotAnim)
|
|
Sleep(0.35);
|
|
if(!FindBestPathToward(enemy, false, true))
|
|
GotoState('TacticalMove');
|
|
Moving:
|
|
if(KFM.intelligence == BRAINS_Retarded){
|
|
if( KFMonster(pawn).HeadHealth > 0 && moveTarget == enemy
|
|
&& FRand() < 0.5
|
|
&& ( KFMonster(pawn).default.Health >= 500
|
|
|| VSize(pawn.location - moveTarget.location) < 200)
|
|
)
|
|
MoveTo(moveTarget.location + VRand() * 50, none);
|
|
else
|
|
MoveTo(pawn.location + VRand() * 200, none);
|
|
}
|
|
else
|
|
MoveToward(moveTarget, FaceActor(1),, ShouldStrafeTo(moveTarget));
|
|
WhatToDoNext(17);
|
|
if (bSoaking)
|
|
SoakStop("STUCK IN CHARGING!");
|
|
}
|
|
state DoorBashing{
|
|
ignores EnemyNotVisible,SeeMonster;
|
|
function Timer(){
|
|
Disable('NotifyBump');
|
|
}
|
|
function AttackDoor(){
|
|
target = targetDoor;
|
|
KFM.Acceleration = vect(0,0,0);
|
|
KFM.DoorAttack(target);
|
|
}
|
|
function SeePlayer( Pawn Seen ){
|
|
if( KFM.intelligence == BRAINS_Human
|
|
&& ActorReachable(Seen) && SetEnemy(Seen))
|
|
WhatToDoNext(23);
|
|
}
|
|
function DamageAttitudeTo(Pawn Other, float Damage){
|
|
if( KFM.intelligence >= BRAINS_Mammal && Other != none
|
|
&& ActorReachable(Other) && SetEnemy(Other))
|
|
WhatToDoNext(32);
|
|
}
|
|
function HearNoise(float Loudness, Actor NoiseMaker){
|
|
if( KFM.intelligence == BRAINS_Human && NoiseMaker != none
|
|
&& NoiseMaker.Instigator != none
|
|
&& ActorReachable(NoiseMaker.Instigator)
|
|
&& SetEnemy(NoiseMaker.Instigator))
|
|
WhatToDoNext(32);
|
|
}
|
|
function Tick(float delta){
|
|
Global.Tick(delta);
|
|
|
|
// Don't move while we are bashing a door!
|
|
moveTarget = none;
|
|
moveTimer = -1;
|
|
pawn.acceleration = vect(0,0,0);
|
|
pawn.groundSpeed = 1;
|
|
pawn.accelRate = 0;
|
|
}
|
|
function EndState(){
|
|
if(NiceMonster(pawn) != none){
|
|
pawn.accelRate = pawn.default.accelRate;
|
|
pawn.groundSpeed = NiceMonster(pawn).GetOriginalGroundSpeed();
|
|
}
|
|
}
|
|
Begin:
|
|
WaitForLanding();
|
|
KeepMoving:
|
|
while(KFM.bShotAnim)
|
|
Sleep(0.25);
|
|
while( TargetDoor != none && !TargetDoor.bHidden && TargetDoor.bSealed
|
|
&& !TargetDoor.bZombiesIgnore){
|
|
AttackDoor();
|
|
while(KFM.bShotAnim)
|
|
Sleep(0.25);
|
|
Sleep(0.1);
|
|
if( KFM.intelligence >= BRAINS_Mammal && Enemy!=none
|
|
&& ActorReachable(Enemy) )
|
|
WhatToDoNext(14);
|
|
}
|
|
WhatToDoNext(152);
|
|
Moving:
|
|
MoveToward(TargetDoor);
|
|
WhatToDoNext(17);
|
|
if(bSoaking)
|
|
SoakStop("STUCK IN CHARGING!");
|
|
}
|
|
state Freeze{
|
|
Ignores SeePlayer,HearNoise,Timer,EnemyNotVisible,NotifyBump,Startle;
|
|
// Don't do this in this state
|
|
function GetOutOfTheWayOfShot(vector ShotDirection, vector ShotOrigin){}
|
|
function BeginState(){
|
|
bUseFreezeHack = false;
|
|
}
|
|
function Tick(float delta){
|
|
Global.Tick(delta);
|
|
if(bUseFreezeHack){
|
|
moveTarget = none;
|
|
moveTimer = -1;
|
|
pawn.acceleration = vect(0,0,0);
|
|
pawn.groundSpeed = 1;
|
|
pawn.accelRate = 0;
|
|
}
|
|
}
|
|
function EndState(){
|
|
if(pawn != none){
|
|
pawn.accelRate = pawn.default.AccelRate;
|
|
pawn.groundSpeed = NiceMonster(pawn).GetOriginalGroundSpeed();
|
|
}
|
|
bUseFreezeHack = false;
|
|
if(enemy == none)
|
|
FindNewEnemy();
|
|
if(choosingAttackLevel == 0)
|
|
WhatToDoNext(99);
|
|
}
|
|
}
|
|
state WaitForAnim{
|
|
Ignores SeePlayer,HearNoise,Timer,EnemyNotVisible,NotifyBump,Startle;
|
|
// Don't do this in this state
|
|
function GetOutOfTheWayOfShot(vector ShotDirection, vector ShotOrigin){}
|
|
event AnimEnd(int Channel){
|
|
pawn.AnimEnd(Channel);
|
|
if ( !Monster(pawn).bShotAnim )
|
|
WhatToDoNext(99);
|
|
}
|
|
function BeginState(){
|
|
bUseFreezeHack = False;
|
|
}
|
|
function Tick( float Delta ){
|
|
Global.Tick(Delta);
|
|
if( bUseFreezeHack )
|
|
{
|
|
MoveTarget = none;
|
|
MoveTimer = -1;
|
|
pawn.acceleration = vect(0,0,0);
|
|
pawn.groundSpeed = 1;
|
|
pawn.accelRate = 0;
|
|
}
|
|
}
|
|
function EndState(){
|
|
if(NiceMonster(pawn) != none){
|
|
pawn.accelRate = pawn.Default.AccelRate;
|
|
pawn.groundSpeed = NiceMonster(pawn).GetOriginalGroundSpeed();
|
|
}
|
|
bUseFreezeHack = False;
|
|
}
|
|
Begin:
|
|
while(KFM.bShotAnim){
|
|
Sleep(0.15);
|
|
}
|
|
WhatToDoNext(99);
|
|
}
|
|
|
|
function bool SetEnemy( pawn newEnemy,
|
|
optional bool bHateMonster,
|
|
optional float MonsterHateChanceOverride)
|
|
{
|
|
local NiceMonster niceZed;
|
|
local NiceZombieFleshpound niceFP;
|
|
local bool bCanForceFight;
|
|
|
|
// Can we fight anything?
|
|
niceZed = NiceMonster(pawn);
|
|
niceFP = NiceZombieFleshpound(niceZed);
|
|
|
|
if (niceZed != none)
|
|
{
|
|
bCanForceFight =
|
|
KFMonster(pawn).HeadHealth <= 0
|
|
|| KFMonster(pawn).bDecapitated
|
|
|| (newEnemy != none && newEnemy.Health <= 15);
|
|
}
|
|
|
|
if (niceFP != none && niceFP.IsInState('RageCharging'))
|
|
bCanForceFight = false;
|
|
|
|
if (newEnemy != none)
|
|
bCanForceFight = bCanForceFight && newEnemy.Health > 0 && newEnemy != enemy;
|
|
else
|
|
bCanForceFight = false;
|
|
|
|
// Do fight if we can
|
|
if(bCanForceFight)
|
|
{
|
|
ChangeEnemy(newEnemy, true);
|
|
FightEnemy(false);
|
|
return true;
|
|
}
|
|
|
|
// Otherwise - do the usual stupid stuff
|
|
return super.SetEnemy(newEnemy, bHateMonster, monsterHateChanceOverride);
|
|
}
|
|
|
|
simulated function AddKillAssistant(Controller PC, float damage){
|
|
local bool bIsalreadyAssistant;
|
|
local int i;
|
|
if(PC == none) return;
|
|
for(i = 0;i < KillAssistants.length;i ++)
|
|
if(PC == KillAssistants[i].PC){
|
|
bIsalreadyAssistant = true;
|
|
KillAssistants[i].damage += damage;
|
|
break;
|
|
}
|
|
if(!bIsalreadyAssistant){
|
|
KillAssistants.Insert(0, 1);
|
|
KillAssistants[0].PC = PC;
|
|
KillAssistants[0].damage = damage;
|
|
}
|
|
}
|
|
defaultproperties
|
|
{
|
|
}
|