rott/kf_sources/ScrnBalanceSrv/Classes/TSCBaseGuardian.uc
2026-07-14 20:27:09 +07:00

676 lines
18 KiB
Ucode

class TSCBaseGuardian extends TSCTeamBase;
var UnrealTeamInfo Team;
var int Damage; // damage per seconds that Base Guardian does to enemies
var int SameTeamCounter; // if nobody of team members are at the base longer than this value, base will be lost
var TSCGameReplicationInfo TSCGRI;
var localized string strUseMessage;
var localized string strUsedMessage;
var localized string strCarryHintMessage;
var localized string strITookTheGnome;
var transient float LastTouchTime;
var transient Pawn BaseSetter;
// damage type to apply on the team when base is lost
// none (default) = no team wipe on base lost
var class<DamageType> WipeOnBaseLost;
var int StunThreshold;
var int StunDuration;
var float StunFadeoutRate;
var transient float StunDamage;
var ShopVolume MyShop;
var transient ScrnPlayerController LastHolder;
replication
{
reliable if (bNetDirty && Role == ROLE_Authority)
Team;
}
simulated function PostBeginPlay()
{
TSCGRI = TSCGameReplicationInfo(Level.GRI);
//log("TSCGRI = " $ TSCGRI, 'TSCBaseGuardian');
super.PostBeginPlay();
}
simulated function PostNetReceive()
{
if ( Role < ROLE_Authority ) {
if ( bHeld && Base != none ) {
// lame replication fix
SetRelativeLocation(GameObjOffset);
SetRelativeRotation(GameObjRot);
}
}
}
function UsedBy( Pawn user )
{
if (!ValidHolder(user))
return;
SetHolder(user.Controller);
}
function SetHolder(Controller C)
{
local PlayerController PC;
super.SetHolder(C);
PC = PlayerController(C);
if ( PC != none) {
PC.ClientMessage(strUsedMessage, 'CriticalEvent');
PC.TeamSay(strITookTheGnome);
}
}
simulated function vector GetLocation()
{
if ( bHeld ) {
if ( Base != none && !Base.bWorldGeometry )
return Base.Location;
else if ( Holder != none )
return Holder.Location;
}
return Location;
}
function bool ShouldWipeOnBaseLost()
{
return false;
}
singular function Touch(Actor Other)
{
if (!ValidHolder(Other))
return;
if ( Pawn(Other) != none && PlayerController(Pawn(Other).Controller) != none) {
if ( Level.TimeSeconds - LastTouchTime > 1 )
PlayerController(Pawn(Other).controller).ClientMessage(strUseMessage, 'CriticalEvent');
LastTouchTime = Level.TimeSeconds;
}
}
function bool SameTeam(Controller c)
{
if ( c == None || c.PlayerReplicationInfo == none || c.PlayerReplicationInfo.Team != Team )
return false;
return true;
}
function bool ValidHolder(Actor Other)
{
local Pawn p;
if( bDisabled || bActive || bHome || bHeld )
return false;
p = Pawn(other);
if ( p == None || p.Health <= 0 || !p.bCanPickupInventory || !p.IsPlayerPawn() )
return false;
if ( !SameTeam(p.Controller) )
return false;
if ( TSCGRI.TeamCarrier[Team.TeamIndex] != none ) {
if ( TSCGRI.TeamCarrier[Team.TeamIndex].Team != Team )
TSCGRI.TeamCarrier[Team.TeamIndex] = none; // carrier changed team
else
return p.PlayerReplicationInfo == TSCGRI.TeamCarrier[Team.TeamIndex]
|| p.PlayerReplicationInfo == TSCGRI.TeamCaptain[Team.TeamIndex];
}
return true;
}
function PlayerController GetBaseSetter()
{
local PlayerController PC;
if ( Holder != none )
PC = PlayerController(Holder.Controller);
if ( PC == none && BaseSetter != none )
PC = PlayerController(BaseSetter.Controller);
return PC;
}
function bool ValidPlaceForBase(Vector CheckLoc)
{
local TSCBaseGuardian EnemyBase;
local ShopVolume Shop;
local PlayerController PC;
EnemyBase = TSCBaseGuardian(TSCGRI.Teams[1-Team.TeamIndex].HomeBase);
if ( TSCGRI.AtBase(CheckLoc, EnemyBase) )
return false;
// check for Z difference to prevent vertical intersection
CheckLoc.Z += TSCGRI.MaxBaseZ + TSCGRI.MinBaseZ;
if ( TSCGRI.AtBase(CheckLoc, EnemyBase) ) {
PC = GetBaseSetter();
if ( PC != none )
PC.ReceiveLocalizedMessage(class'TSCMessages', 310);
return false;
}
// can't place a base inside a shop
foreach TouchingActors(Class'ShopVolume',Shop) {
PC = GetBaseSetter();
if ( PC != none )
PC.ReceiveLocalizedMessage(class'TSCMessages', 313);
return false;
}
if ( Holder != none ) {
foreach Holder.TouchingActors(Class'ShopVolume',Shop) {
PC = GetBaseSetter();
if ( PC != none )
PC.ReceiveLocalizedMessage(class'TSCMessages', 313);
return false;
}
}
return true;
}
function BaseSetupFailed()
{
SendHome();
}
// Score() is used for setting up base
function Score()
{
if ( bDisabled || bActive )
return;
if ( ValidPlaceForBase(GetLocation()) ) {
BaseSetter = Holder;
GotoState('SettingUp');
}
else {
log("Invalid place for base @ " $ GetLocation(), class.name);
}
}
// If base is not active, try setting it up.
// If unable to setup base, then send it home.
// Usually used at the start of the wave.
function ScoreOrHome()
{
Score();
// if base still isn't established - send it home
if ( !bActive )
BaseSetupFailed();
}
function MoveToShop(ShopVolume Shop)
{
local int i;
if ( Shop == none )
return;
BaseSetter = none;
if ( !Shop.bTelsInit )
Shop.InitTeleports();
if ( Shop.bHasTeles ) {
if ( Shop.TelList.length == 1 ) {
i = 0;
}
else if ( Team.TeamIndex == 0 ) {
i = Rand(Shop.TelList.length/2);
}
else {
i = ceil(Shop.TelList.length/2.0) + Rand(Shop.TelList.length/2);
// shouldn't happen, just to be 110% sure
if( i >= Shop.TelList.length ) {
i = Shop.TelList.length - 1;
}
}
SetLocation(Shop.TelList[i].Location + (vect(0,0,1) * CollisionHeight)) ;
SetPhysics(PHYS_Falling);
GotoState('Dropped');
}
else {
warn( Shop $ " has no teleporters" );
// if none of teleporters can accept us, just give me to the closest team member
GiveToClosestPlayer(Shop.Location);
}
}
function GiveToClosestPlayer(vector Loc)
{
local Controller C, Best;
local float BestDistSqr;
for ( C = Level.ControllerList; C != none; C = C.nextController ) {
if ( C.bIsPlayer && C.Pawn != none && ValidHolder(C.Pawn) ) {
if ( Best == none || VSizeSquared(C.Pawn.Location - Loc) < BestDistSqr )
Best = C;
}
}
if ( Best != none ) {
GotoState('Dropped');
SetHolder(Best);
}
}
auto state Home
{
ignores BaseSetupFailed;
function BeginState()
{
// log("State --> Home", class.name);
Disable('Touch');
SetCollision(false, false);
bCollideWorld=false;
bHome = true;
bHidden = true;
LightType = LT_None;
BaseSetter = none;
LastHolder = none;
}
function EndState()
{
// log("State <-- Home", class.name);
bHome = false;
bHidden = false;
TakenTime = Level.TimeSeconds;
}
function CheckTouching() {}
}
state Dropped
{
function BeginState()
{
// log("State --> Dropped", class.name);
SetCollision(true, false);
bCollideWorld=True;
bHidden = false;
LightType = LT_Pulse;
Enable('Touch');
super.BeginState();
}
function EndState()
{
// log("State <-- Dropped", class.name);
}
}
state Held
{
function BeginState()
{
// log("State --> Held", class.name);
super.BeginState();
Holder.bAlwaysRelevant = true;
BaseSetter = none;
LastHolder = ScrnPlayerController(Holder.Controller);
}
function EndState()
{
// log("State <-- Held", class.name);
if ( Holder != none ) {
Holder.bAlwaysRelevant = false;
SetLocation(Holder.Location); // prevent holder to exploit GameObjOffset
}
super.EndState();
}
function SendHome() // unignore
{
global.SendHome();
}
function MoveToShop(ShopVolume Shop)
{
Drop(PhysicsVolume.Gravity); // make sure the holder will not force his location after moving to shop
global.MoveToShop(Shop);
}
}
state SettingUp
{
ignores Score, ScoreOrHome, Touch, UsedBy;
function BeginState()
{
// log("State --> SettingUp", class.name);
bActive = true;
Disable('Touch');
SetCollision(true, false);
bCollideWorld=true;
Velocity = PhysicsVolume.Gravity;
SetPhysics(PHYS_Falling);
LightType = LT_Pulse;
SetTimer(10, false); // just in case
NetUpdateTime = Level.TimeSeconds - 1; // replicate immediately
}
function EndState()
{
// log("State <-- SettingUp", class.name);
bActive = false;
SetTimer(0, false);
}
event Landed( vector HitNormal )
{
local rotator r;
Velocity = vect(0,0,0); // just to be sure :)
if ( !ValidPlaceForBase(Location) ) {
// can't setup base here (probably dropped down to enemy base)
// give gnome back to its last holder
if ( TSCGRI.bWaveInProgress ) {
BaseSetupFailed();
}
else if ( BaseSetter != none ) {
SetPhysics(PHYS_None);
SetHolder(BaseSetter.Controller);
}
else {
GotoState('Dropped');
}
return;
}
r = Rotation;
r.Pitch = 0;
r.Roll = 0;
SetRotation(r);
SetPhysics(PHYS_Rotating);
SetTimer(5, false); // activate the base in 5 seconds
}
function Timer()
{
BroadcastLocalizedMessage(class'TSCMessages', 1+Team.TeamIndex*100);
GotoState('Guarding');
}
}
state Guarding
{
ignores Score, ScoreOrHome, Touch, UsedBy;
function BeginState()
{
local Controller C;
local ScrnPlayerController ScrnC;
local rotator r;
// log("State --> Guarding", class.name);
bActive = true;
Disable('Touch');
SetCollision(true, false);
bCollideWorld = true;
r = Rotation;
r.Pitch = 0;
r.Roll = 0;
SetRotation(r);
SetPhysics(PHYS_Rotating);
LightType = LT_Steady;
StunDamage = 0;
SameTeamCounter = default.SameTeamCounter;
SetTimer(1, true);
for ( C = Level.ControllerList; C != none; C = C.nextController ) {
if ( C.bIsPlayer && SameTeam(C) ) {
ScrnC = ScrnPlayerController(C);
if ( ScrnC != none && ScrnC.bShoppedThisWave )
ScrnPlayerController(C).ServerShowPathTo(1); // show path to base
}
}
}
function EndState()
{
// log("State <-- Guarding", class.name);
bActive = false;
SetTimer(0, false);
}
function SendHome()
{
if ( ShouldWipeOnBaseLost() )
{
TSCGame(Level.Game).WipeTeam(Team, WipeOnBaseLost);
}
global.SendHome();
}
function bool ShouldWipeOnBaseLost()
{
return WipeOnBaseLost != none && TSCGRI.MaxMonsters > 10;
}
function TakeDamage( int Damage, Pawn InstigatedBy, Vector Hitlocation, Vector Momentum, class<DamageType> damageType, optional int HitIndex)
{
local ScrnPlayerController PC;
if ( damageType == class'DamTypeFrag' )
Damage *= 5;
if ( InstigatedBy != none )
PC = ScrnPlayerController(InstigatedBy.Controller);
if ( PC != none ) {
PC.ClientPlayerDamaged(Damage, Hitlocation, 10);
}
StunDamage += Damage;
}
function Tick(float DeltaTime)
{
super.Tick(DeltaTime);
if ( StunDamage > 0 ) {
if ( StunDamage >= StunThreshold ) {
GotoState('Stunned');
}
else {
StunDamage -= StunFadeoutRate * DeltaTime;
if ( StunDamage < 0 )
StunDamage = 0;
}
}
}
function Timer()
{
local Controller C;
local ScrnPlayerController SC;
local bool bNobodyAtBase, bNobodyAlive;
bNobodyAtBase = true;
bNobodyAlive = true;
for ( C = Level.ControllerList; C != none; C = C.nextController ) {
if ( C.bIsPlayer && C.Pawn != none && C.Pawn.Health > 0
&& C.PlayerReplicationInfo != none )
{
if ( C.PlayerReplicationInfo.Team != Team && TSCGRI.AtBase(C.Pawn.Location, self) ) {
C.Pawn.TakeDamage(Damage, none, C.Pawn.Location, vect(0,0,0), class'DamTypeEnemyBase');
if ( PlayerController(C) != none && ScrnHumanPawn(C.Pawn) != none
&& ScrnHumanPawn(C.Pawn).NextEnemyBaseDamageMsg < Level.TimeSeconds )
{
PlayerController(C).ReceiveLocalizedMessage(class'TSCMessages', 312);
ScrnHumanPawn(C.Pawn).NextEnemyBaseDamageMsg = Level.TimeSeconds + 6.0;
}
}
else if ( bNobodyAtBase && C.PlayerReplicationInfo.Team == Team ) {
bNobodyAlive = false;
if ( TSCGRI.AtBase(C.Pawn.Location, self) )
bNobodyAtBase = false;
}
}
}
if ( bNobodyAtBase ) {
if ( bNobodyAlive || --SameTeamCounter <= 0 ) {
BroadcastLocalizedMessage(class'TSCMessages', 2+Team.TeamIndex*100);
SendHome();
}
else if ( SameTeamCounter % 3 == 0 ) {
for ( C = Level.ControllerList; C != none; C = C.nextController ) {
if ( C.bIsPlayer && C.PlayerReplicationInfo != none
&& C.Pawn != none && C.Pawn.Health > 0
&& C.PlayerReplicationInfo.Team == Team )
{
SC = ScrnPlayerController(C);
if ( SC != none ) {
SC.ServerShowPathTo(1); // show path to base
if ( ShouldWipeOnBaseLost() )
SC.ReceiveLocalizedMessage(class'TSCMessages', 311); // critical message
else
SC.ReceiveLocalizedMessage(class'TSCMessages', 211);
}
}
}
}
}
else {
SameTeamCounter = default.SameTeamCounter;
}
}
}
state Stunned
{
ignores Score, ScoreOrHome, Touch, UsedBy, TakeDamage;
function BeginState()
{
local rotator r;
// log("State --> Stunned", class.name);
r = Rotation;
r.Pitch = -16384;
r.Roll = 0;
SetRotation(r);
SetPhysics(PHYS_None);
LightType = LT_Flicker;
LightRadius = default.LightRadius * 0.3;
SetTimer(StunDuration, false);
NetUpdateTime = Level.TimeSeconds - 1; // replicate immediately
BroadcastLocalizedMessage(class'TSCMessages', 3+Team.TeamIndex*100);
}
function EndState()
{
// log("State <-- Stunned", class.name);
LightRadius = default.LightRadius;
}
function Timer()
{
GotoState('WakingUp');
}
}
state WakingUp
{
ignores Score, ScoreOrHome, Touch, UsedBy;
function BeginState()
{
local rotator r;
// log("State --> WakingUp", class.name);
r = Rotation;
r.Pitch = 0;
r.Roll = 0;
SetRotation(r);
SetPhysics(PHYS_Rotating);
bActive = true;
LightType = LT_Pulse;
SetTimer(5, false);
NetUpdateTime = Level.TimeSeconds - 1; // replicate immediately
BroadcastLocalizedMessage(class'TSCMessages', 4+Team.TeamIndex*100);
}
function EndState()
{
// log("State <-- WakingUp", class.name);
bActive = false;
}
function Timer()
{
GotoState('Guarding');
}
}
defaultproperties
{
Damage=5
SameTeamCounter=10
strUseMessage="Press USE to take the Base Guardian"
strUsedMessage="You took the Base Guardian"
strITookTheGnome="I took our Base Guardian"
StunThreshold=700
StunDuration=25
StunFadeoutRate=50.000000
MaxDropTime=0.000000
GameObjBone="CHR_Spine3"
GameObjOffset=(Y=15.000000)
GameObjRot=(Pitch=-16384)
LightType=LT_Steady
LightEffect=LE_QuadraticNonIncidence
LightSaturation=128
LightBrightness=150.000000
LightRadius=25.000000
LightPeriod=30
DrawType=DT_StaticMesh
StaticMesh=StaticMesh'HillbillyHorror_SM.GardenGnome'
bStatic=False
bHidden=True
bDynamicLight=True
bStasis=False
bAlwaysRelevant=True
NetPriority=3.000000
DrawScale=0.750000
PrePivot=(Z=23.000000)
SurfaceType=EST_Flesh
bUnlit=True
bCanBeDamaged=False
CollisionRadius=15.000000
CollisionHeight=15.000000
bCollideWorld=True
bProjTarget=True
bNetNotify=True
bFixedRotationDir=True
Mass=30.000000
Buoyancy=20.000000
RotationRate=(Yaw=25000)
MessageClass=Class'UnrealGame.CTFMessage'
}