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 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, 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' }