676 lines
18 KiB
Ucode
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'
|
|
}
|