stinger_m/zscript/miscitems.zsc
Marisa Kirisame 2d64db512f Various Sentry fixups and whatnot, plus a fancy explosion.
Sentry no longer causes a freeze if it runs out of ammo while still having a target.
Sentry now has to be recalled by using the item.
Hijacking a sentry now immediately assigns it to your inventory.
You can hijack rogue sentries (not owned by anyone).
Better placement check for forcefield and sentry spawning, no more "no room" messages when it can definitely fit.
Increased flak slug explosion sprite size, as it felt too small.
Eightball loads faster (slightly faster than vanilla but can't do anything about that).
ASMD combo no longer spawns amped explosions when it shouldn't.
Added a new flag to UnrealInventory, bDRAWSPECIAL, so the HUD displays the special1 as an amount. This is useful for items like the Sentry that use this variable to count ammo.
Increased rotation range for the Sentry so it's harder for enemies to sneak behind it from the sides.
Added debris to the explosions of flares and voice boxes.
2019-09-06 14:08:53 +02:00

2128 lines
49 KiB
Text

// Backpack that only gives ammo for valid weapons
Class UnrealBackpack : BackpackItem replaces Backpack
{
override Inventory CreateCopy( Actor other )
{
// Find every unique type of ammoitem. Give it to the player if
// he doesn't have it already, and double its maximum capacity.
for ( int i=0; i<AllActorClasses.Size(); i++ )
{
let type = (class<Ammo>)(AllActorClasses[i]);
if ( !type || (type.GetParentClass() != 'Ammo') ) continue;
// check that it's for a valid weapon
bool isvalid = false;
for ( int j=0; j<AllActorClasses.Size(); j++ )
{
let type2 = (class<Weapon>)(AllActorClasses[j]);
if ( !type2 ) continue;
let rep = GetReplacement(type2);
if ( (rep != type2) && !(rep is "DehackedPickup") ) continue;
readonly<Weapon> weap = GetDefaultByType(type2);
if ( !other.player || !other.player.weapons.LocateWeapon(type2) || weap.bCheatNotWeapon ) continue;
if ( (weap.AmmoType1 == type) || (weap.AmmoType2 == type) )
{
isvalid = true;
break;
}
}
if ( !isvalid ) continue;
let ammoitem = Ammo(other.FindInventory(type));
int amount = GetDefaultByType(type).BackpackAmount;
// extra ammo in baby mode and nightmare mode
if ( !bIgnoreSkill ) amount = int(amount*G_SkillPropertyFloat(SKILLP_AmmoFactor));
if ( amount < 0 ) amount = 0;
if ( !ammoitem )
{
// The player did not have the ammoitem. Add it.
ammoitem = Ammo(Spawn(type));
ammoitem.Amount = bDepleted?0:amount;
if ( ammoitem.BackpackMaxAmount > ammoitem.MaxAmount )
ammoitem.MaxAmount = ammoitem.BackpackMaxAmount;
if ( ammoitem.Amount > ammoitem.MaxAmount )
ammoitem.Amount = ammoitem.MaxAmount;
ammoitem.AttachToOwner(other);
}
else
{
// The player had the ammoitem. Give some more.
if ( ammoitem.MaxAmount < ammoitem.BackpackMaxAmount )
ammoitem.MaxAmount = ammoitem.BackpackMaxAmount;
if ( !bDepleted && (ammoitem.Amount < ammoitem.MaxAmount) )
{
ammoitem.Amount += amount;
if ( ammoitem.Amount > ammoitem.MaxAmount )
ammoitem.Amount = ammoitem.MaxAmount;
}
}
}
return Inventory.CreateCopy(other);
}
override bool HandlePickup( Inventory item )
{
// Since you already have a backpack, that means you already have every
// kind of ammo in your inventory, so we don't need to look at the
// entire PClass list to discover what kinds of ammo exist, and we don't
// have to alter the MaxAmount either.
if ( item is 'BackpackItem' )
{
for ( let probe = Owner.Inv; probe; probe = probe.Inv )
{
if ( probe.GetParentClass() != 'Ammo' ) continue;
if ( probe.Amount >= probe.MaxAmount && !sv_unlimited_pickup ) continue;
int amount = Ammo(probe).Default.BackpackAmount;
// extra ammo in baby mode and nightmare mode
if ( !bIgnoreSkill )
amount = int(amount*G_SkillPropertyFloat(SKILLP_AmmoFactor));
probe.Amount += amount;
if ( (probe.Amount > probe.MaxAmount) && !sv_unlimited_pickup )
probe.Amount = probe.MaxAmount;
}
// The pickup always succeeds, even if you didn't get anything
item.bPickupGood = true;
return true;
}
return false;
}
override void DoPickupSpecial( Actor toucher )
{
Super.DoPickupSpecial(toucher);
if ( gameinfo.gametype&GAME_DOOMCHEX )
{
static const Class<Inventory> xitems[] = {"Flare", "Seeds", "SentryItem", "VoiceBox", "ForceField", "Dampener", "Peacemaker"};
int xitemn[7];
xitemn[0] = max(0,Random[BackpackExtra](-1,3));
xitemn[1] = max(0,Random[BackpackExtra](-1,3));
xitemn[2] = max(0,Random[BackpackExtra](-2,1));
xitemn[3] = max(0,Random[BackpackExtra](-2,1));
xitemn[4] = max(0,Random[BackpackExtra](-2,1));
xitemn[5] = max(0,Random[BackpackExtra](-1,1));
xitemn[6] = max(0,Random[BackpackExtra](-2,1));
// random doubling
if ( !Random[BackpackExtra](0,4) ) xitemn[0] *= 2;
if ( !Random[BackpackExtra](0,4) ) xitemn[1] *= 2;
if ( !Random[BackpackExtra](0,9) ) xitemn[2] *= 2;
if ( !Random[BackpackExtra](0,7) ) xitemn[3] *= 2;
if ( !Random[BackpackExtra](0,6) ) xitemn[4] *= 2;
if ( !Random[BackpackExtra](0,5) ) xitemn[5] *= 2;
if ( !Random[BackpackExtra](0,9) ) xitemn[6] *= 2;
for ( int i=0; i<7; i++ )
{
if ( xitemn[i] <= 0 ) continue;
toucher.GiveInventory(xitems[i],xitemn[i]);
}
}
}
Default
{
Tag "$T_BACKPACK";
Inventory.PickupMessage "$I_BACKPACK";
Inventory.RespawnTics 2100;
}
States
{
Spawn:
BPAK A -1;
Stop;
}
}
Class UTranslator : UnrealInventory
{
bool bNewMessage, bNotNewMessage;
string NewMessage, Hint;
Array<String> OldMessages, OldHints;
override void Travelled()
{
Super.Travelled();
NewMessage = Hint = "";
OldMessages.Clear();
OldHints.Clear();
}
override bool Use( bool pickup )
{
if ( pickup ) return false;
if ( Owner.player == players[consoleplayer] )
Menu.SetMenu('TranslatorMenu');
bNewMessage = bNotNewmessage = false;
return false;
}
void AddMessage( String msg, String hnt )
{
int found = -1;
for ( int i=0; i<OldMessages.Size(); i++ )
{
if ( OldMessages[i] != msg ) continue;
found = i;
}
if ( found != -1 )
{
OldMessages.Delete(found);
OldHints.Delete(found);
}
if ( (NewMessage != msg) && (NewMessage.Length() > 0) )
{
OldMessages.Push(NewMessage);
OldHints.Push(Hint);
}
NewMessage = msg;
Hint = hnt;
}
Default
{
Tag "$T_TRANSLATOR";
Inventory.PickupMessage "$I_TRANSLATOR";
Inventory.Icon "I_Tran";
Inventory.MaxAmount 1;
}
States
{
Spawn:
TRNS A -1;
Stop;
}
}
// To be placed by mappers
// Uses two UDMF-settable user strings
// AMBUSH: only triggered by script activation, not touch
Class TranslatorEvent : Actor
{
String user_message, user_hint;
Array<Actor> Touchers;
Default
{
Radius 40;
Height 80;
+SPECIAL;
+NOGRAVITY;
+INVISIBLE;
}
virtual void TriggerMessage( Actor who )
{
if ( special1 > gametic ) return;
let Translator = UTranslator(who.FindInventory("UTranslator"));
if ( !Translator ) return;
special1 = gametic+8;
if ( who.CheckLocalView() )
{
if ( special2 ) Console.Printf(StringTable.Localize("$TR_MSG"));
else Console.Printf(StringTable.Localize("$TR_NEWMSG"));
S_Sound("translator/event",CHAN_VOICE|CHAN_UI);
}
Translator.AddMessage(user_message,user_hint);
if ( special2 ) Translator.bNotNewMessage = true;
else Translator.bNewMessage = true;
special2 = 1;
}
// GZDoom doesn't have Touch/UnTouch like UE1 so I have to improvise
override void Tick()
{
Super.Tick();
for ( int i=0; i<Touchers.Size(); i++ )
{
bool deletthis = false;
if ( Touchers[i].pos.x+Touchers[i].radius < pos.x-radius ) deletthis = true;
if ( Touchers[i].pos.x-Touchers[i].radius > pos.x+radius ) deletthis = true;
if ( Touchers[i].pos.y+Touchers[i].radius < pos.y-radius ) deletthis = true;
if ( Touchers[i].pos.y-Touchers[i].radius > pos.y+radius ) deletthis = true;
if ( Touchers[i].pos.z+Touchers[i].height < pos.z ) deletthis = true;
if ( Touchers[i].pos.z > pos.z+height ) deletthis = true;
if ( !deletthis ) continue;
Touchers.Delete(i);
i--;
}
}
override void Touch( Actor toucher )
{
if ( bAMBUSH || !toucher || !toucher.player ) return;
for ( int i=0; i<Touchers.Size(); i++ ) if ( Touchers[i] == toucher ) return;
Touchers.Push(toucher);
TriggerMessage(toucher);
}
override void Activate( Actor activator )
{
if ( !activator || !activator.player ) return;
TriggerMessage(activator);
}
States
{
Spawn:
SMSG A -1;
Stop;
}
}
Class VoiceBox : UnrealInventory
{
Actor box;
Default
{
Tag "$T_VOICEBOX";
Inventory.PickupMessage "$I_VOICEBOX";
Inventory.Icon "I_VoiceB";
Inventory.MaxAmount 3;
UnrealInventory.Charge 600;
}
override void DoEffect()
{
Super.DoEffect();
if ( !bActive ) return;
if ( box ) Charge = box.ReactionTime;
else
{
Charge = DefaultCharge;
bActive = false;
Amount--;
if ( Amount <= 0 ) DepleteOrDestroy();
}
}
override bool Use( bool pickup )
{
if ( pickup || bActive ) return false;
Vector3 x, y, z;
[x, y, z] = dt_CoordUtil.GetAxes(Owner.pitch,Owner.angle,Owner.roll);
Vector3 origin = Owner.Vec2OffsetZ(0,0,Owner.player.viewz);
origin = level.Vec3Offset(origin,x*20.-z*8.);
box = Spawn("VoiceBoxActive",origin);
box.ReactionTime = Charge;
box.vel = x*9.;
box.vel.z += 1.;
box.target = Owner;
box.angle = Owner.angle;
box.pitch = Owner.pitch;
bActive = true;
return false;
}
States
{
Spawn:
VBOX A -1;
Stop;
}
}
Class VoiceBoxHitbox : Actor
{
Default
{
Tag "$T_VOICEBOX";
Radius 6;
Height 9;
Health 60;
+SHOOTABLE;
+NOGRAVITY;
+NOCLIP;
+DONTSPLASH;
+NOBLOOD;
+NOTELEPORT;
}
override void Tick()
{
Super.Tick();
if ( !master )
{
Destroy();
return;
}
SetOrigin(master.pos,true);
}
override void Die( Actor source, Actor inflictor, int dmgflags, Name MeansOfDeath )
{
if ( master ) master.ReactionTime = 0;
Super.Die(source,inflictor,dmgflags,MeansOfDeath);
}
}
Class VoiceBoxActive : Actor
{
Actor b;
double desiredangle, anglevel, oldangle;
override void PostBeginPlay()
{
Super.PostBeginPlay();
b = Spawn("VoiceBoxHitbox",pos);
b.master = self;
desiredangle = FRandom[Junk](0,360);
anglevel = FRandom[Junk](5,15);
}
action void A_VoiceBoxPlay()
{
static const String BattleSounds[] =
{
"automag/shot",
"utrl/explode",
"automag/shot",
"automag/shot",
"flak/altfire",
"utrl/explode",
"flak/fire",
"shock/fire",
"stinger/hit2",
"automag/shot"
};
if ( invoker.b ) invoker.b.A_AlertMonsters(0,AMF_TARGETEMITTER);
for ( int i=0; i<10; i++ ) if ( !Random[Voicebox](0,30) ) A_PlaySound(BattleSounds[i],CHAN_AUTO,FRandom[Voicebox](0.5,1.0));
}
override void Tick()
{
oldangle = angle;
Super.Tick();
if ( deltaangle(angle,desiredangle) ~== 0 ) angle = desiredangle;
else angle += clamp(deltaangle(angle,desiredangle),-anglevel,anglevel);
}
Default
{
Radius 6;
Height 6;
PROJECTILE;
-NOGRAVITY;
+SKYEXPLODE;
+MOVEWITHSECTOR;
+CANBOUNCEWATER;
+BOUNCEAUTOOFF;
+BOUNCEAUTOOFFFLOORONLY;
+USEBOUNCESTATE;
+INTERPOLATEANGLES;
BounceType "Hexen";
BounceFactor 0.5;
BounceSound "transloc/bounce";
WallBounceFactor 0.5;
Gravity 0.35;
ReactionTime 200;
}
States
{
Spawn:
VBOX A -1;
Stop;
Bounce:
VBOX A 0 { angle = invoker.oldangle; }
Goto Spawn;
Death:
VBOX A 30
{
invoker.anglevel *= 0;
A_PlaySound("voice/activate");
}
VBOX ABCDEFGHIJ 1
{
A_VoiceBoxPlay();
ReactionTime--;
return A_JumpIf(ReactionTime<=0,"Death2");
}
Goto Death+1;
Death2:
VBOX A 0
{
if ( invoker.b ) invoker.b.Destroy();
A_PlaySound("flare/explode",CHAN_VOICE);
A_NoGravity();
A_Stop();
A_SetRenderStyle(1.,STYLE_Add);
SetZ(pos.z+16);
Spawn("FlareXLight",pos);
bMOVEWITHSECTOR = false;
bFORCEXYBILLBOARD = true;
A_SetScale(FRandomPick[ExploS](-1.5,1.5),FRandomPick[ExploS](-1.5,1.5));
int numpt = Random[ExploS](15,30);
for ( int i=0; i<numpt; i++ )
{
Vector3 pvel = (FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1)).unit()*FRandom[ExploS](1,3);
let s = Spawn("UTSmoke",pos);
s.vel = pvel;
}
numpt = Random[ExploS](9,18);
for ( int i=0; i<numpt; i++ )
{
Vector3 pvel = (FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1)).unit()*FRandom[ExploS](2,6);
let s = Spawn("UTSpark",pos);
s.vel = pvel;
}
numpt = Random[ExploS](18,28);
for ( int i=0; i<numpt; i++ )
{
Vector3 pvel = (FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1)).unit()*FRandom[ExploS](2,12);
let s = Spawn("UTChip",pos);
s.vel = pvel;
s.scale *= FRandom[ExploS](0.9,2.7);
}
return A_Jump(256,"Explo1","Explo2","Explo3","Explo4","Explo5");
}
Explo1:
EXP1 ABCDEFGH 3 Bright;
Stop;
Explo2:
EXP2 ABCDEFGH 3 Bright;
Stop;
Explo3:
EXP3 ABCDEFGH 3 Bright;
Stop;
Explo4:
EXP4 ABCDEFGH 3 Bright;
Stop;
Explo5:
EXP5 ABCDEFGH 3 Bright;
Stop;
}
}
Class Flare : UnrealInventory
{
Default
{
Tag "$T_FLARES";
Inventory.PickupMessage "$I_FLARES";
Inventory.Icon "I_Flare";
Inventory.MaxAmount 20;
}
override bool Use( bool pickup )
{
if ( pickup ) return false;
Vector3 x, y, z;
[x, y, z] = dt_CoordUtil.GetAxes(Owner.pitch,Owner.angle,Owner.roll);
Vector3 origin = level.Vec3Offset(Owner.Vec2OffsetZ(0,0,Owner.player.viewz),x*10.-z*8.);
let a = Spawn("FlareThrown",origin);
a.target = Owner;
a.angle = Owner.angle;
a.pitch = Owner.pitch;
a.vel += x*a.speed;
return true;
}
States
{
Spawn:
FLAR A -1;
Stop;
}
}
Class FlareThrown : Actor
{
double pitchvel, anglevel, rollvel;
double desiredangle;
bool rotatetodesired;
double lastpitch, lastangle, lastroll;
Actor b;
Default
{
Radius 6;
Height 6;
+NOBLOCKMAP;
+MISSILE;
+MOVEWITHSECTOR;
+THRUACTORS;
+USEBOUNCESTATE;
+INTERPOLATEANGLES;
+BOUNCEAUTOOFF;
+BOUNCEAUTOOFFFLOORONLY;
+NOTELEPORT;
+FORCERADIUSDMG;
+NODAMAGETHRUST;
DamageType "Exploded";
Speed 12;
VSpeed 2;
Mass 1;
Gravity 0.35;
BounceType "Hexen";
WallBounceFactor 0.6;
BounceFactor 0.6;
ReactionTime 350;
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
pitchvel = FRandom[Junk](5,15)*RandomPick[Junk](-1,1);
anglevel = FRandom[Junk](5,15)*RandomPick[Junk](-1,1);
rollvel = FRandom[Junk](5,15)*RandomPick[Junk](-1,1);
}
override void Tick()
{
lastpitch = pitch;
lastangle = angle;
lastroll = roll;
Super.Tick();
if ( rotatetodesired )
{
if ( deltaangle(pitch,0) ~== 0 ) pitch = 0;
else pitch += clamp(deltaangle(pitch,0),-pitchvel,pitchvel);
if ( deltaangle(angle,desiredangle) ~== 0 ) angle = desiredangle;
else angle += clamp(deltaangle(angle,desiredangle),-anglevel,anglevel);
if ( deltaangle(roll,0) ~== 0 ) roll = 0;
else roll += clamp(deltaangle(roll,0),-rollvel,rollvel);
}
else
{
angle += anglevel;
pitch += pitchvel;
roll += rollvel;
}
if ( ReactionTime <= 0 ) return;
if ( GetAge() < 9 ) return;
if ( waterlevel > 0 )
{
if ( tracer ) tracer.Destroy();
if ( b ) b.Destroy();
A_StopSound(CHAN_VOICE);
ReactionTime = 0;
return;
}
if ( !b )
{
A_PlaySound("flare/on");
A_PlaySound("flare/loop",CHAN_VOICE,.5,true);
tracer = Spawn("FlareThrownX",pos);
tracer.angle = angle;
tracer.pitch = pitch;
tracer.target = self;
b = Spawn("FlareHitbox",pos);
b.master = self;
}
b.A_AlertMonsters(0,AMF_TARGETEMITTER);
ReactionTime--;
}
States
{
Spawn:
FLAR A 1 A_JumpIf(ReactionTime<=0,"Death");
Wait;
Bounce:
FLAR A 0
{
pitch = lastpitch;
angle = lastangle;
roll = lastroll;
rotatetodesired = true;
desiredangle = FRandom[Junk](0,360);
pitchvel = abs(pitchvel)*0.75;
anglevel = abs(anglevel)*0.75;
rollvel = abs(rollvel)*0.75;
}
Goto Spawn;
Death:
FLAR A 0 { anglevel *= 0; }
FLAR A 1
{
if ( waterlevel > 0 )
{
if ( tracer ) tracer.Destroy();
if ( b ) b.Destroy();
A_StopSound(CHAN_VOICE);
return ResolveState("Fizz");
}
return A_JumpIf(ReactionTime<=0,1);
}
Wait;
FLAR A 0
{
if ( tracer ) tracer.Destroy();
if ( b ) b.Destroy();
if ( waterlevel > 0 )
{
return ResolveState("Fizz");
}
A_StopSound(CHAN_VOICE);
A_PlaySound("flare/explode");
A_Explode(50,50);
A_NoGravity();
A_Stop();
A_SetRenderStyle(1.,STYLE_Add);
SetZ(pos.z+9);
Spawn("FlareXLight",pos);
bMOVEWITHSECTOR = false;
bFORCEXYBILLBOARD = true;
A_SetScale(FRandomPick[ExploS](-1.5,1.5),FRandomPick[ExploS](-1.5,1.5));
int numpt = Random[ExploS](10,20);
for ( int i=0; i<numpt; i++ )
{
Vector3 pvel = (FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1)).unit()*FRandom[ExploS](1,3);
let s = Spawn("UTSmoke",pos);
s.vel = pvel;
}
numpt = Random[ExploS](6,15);
for ( int i=0; i<numpt; i++ )
{
Vector3 pvel = (FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1)).unit()*FRandom[ExploS](2,6);
let s = Spawn("UTSpark",pos);
s.vel = pvel;
}
numpt = Random[ExploS](15,20);
for ( int i=0; i<numpt; i++ )
{
Vector3 pvel = (FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1)).unit()*FRandom[ExploS](2,12);
let s = Spawn("UTChip",pos);
s.vel = pvel;
s.scale *= FRandom[ExploS](0.9,2.7);
}
return A_Jump(256,"Explo1","Explo2","Explo3","Explo4","Explo5");
}
Explo1:
EXP1 ABCDEFGH 3 Bright;
Stop;
Explo2:
EXP2 ABCDEFGH 3 Bright;
Stop;
Explo3:
EXP3 ABCDEFGH 3 Bright;
Stop;
Explo4:
EXP4 ABCDEFGH 3 Bright;
Stop;
Explo5:
EXP5 ABCDEFGH 3 Bright;
Stop;
Fizz:
FLAR A 1
{
special1++;
if ( special1 > 150 ) A_FadeOut();
}
Wait;
}
}
Class FlareXLight : PaletteLight
{
Default
{
ReactionTime 25;
Args 0,0,0,80;
}
}
Class FlareHitbox : VoiceBoxHitbox
{
Default
{
Tag "$T_FLARES";
Radius 4;
Height 6;
Health 1;
}
}
Class FlareThrownX : Actor
{
Default
{
RenderStyle "Add";
+NOGRAVITY;
+NOCLIP;
+DONTSPLASH;
+INTERPOLATEANGLES;
Radius 0.1;
Height 0;
}
override void Tick()
{
Super.Tick();
if ( !target )
{
Destroy();
return;
}
SetOrigin(target.pos,true);
angle = target.angle;
pitch = target.pitch;
}
States
{
Spawn:
FLAR A -1 Bright;
Stop;
}
}
Class BetaFlare : UnrealInventory
{
Class<Actor> ThrownClass;
Property ThrownClass : ThrownClass;
override bool TryPickup( in out Actor toucher )
{
if ( !sting_flares ) return false; // not allowed
return Super.TryPickup(toucher);
}
override void Tick()
{
Super.Tick();
if ( sting_flares ) return;
if ( Owner ) Owner.RemoveInventory(self);
Destroy();
}
override bool Use( bool pickup )
{
if ( pickup || bActive || (charge < defaultcharge) ) return false;
Vector3 x, y, z;
[x, y, z] = dt_CoordUtil.GetAxes(Owner.pitch,Owner.angle,Owner.roll);
Vector3 origin = level.Vec3Offset(Owner.Vec2OffsetZ(0,0,Owner.player.viewz),x*10.-z*8.);
let a = Spawn(ThrownClass,origin);
a.target = Owner;
a.angle = Owner.angle;
a.pitch = Owner.pitch;
a.vel += x*a.speed;
for ( Inventory i=Owner.Inv; i; i=i.Inv )
{
if ( !(i is 'BetaFlare') ) continue;
UnrealInventory(i).bActive = true;
UnrealInventory(i).Charge = 0;
i.tracer = a;
}
return false;
}
override void DoEffect()
{
Super.DoEffect();
if ( !bActive )
{
charge = min(defaultcharge,charge+1);
return;
}
else if ( !tracer ) bActive = false;
}
Default
{
Inventory.MaxAmount 1;
UnrealInventory.Charge 100;
+INVENTORY.UNDROPPABLE;
+INVENTORY.UNTOSSABLE;
}
}
Class LightFlare : BetaFlare
{
Default
{
Tag "$T_LFLARES";
Inventory.Icon "I_FlarBL";
BetaFlare.ThrownClass "LightFlareThrown";
}
}
Class DarkFlare : BetaFlare
{
Default
{
Tag "$T_DFLARES";
Inventory.Icon "I_FlarBD";
BetaFlare.ThrownClass "DarkFlareThrown";
}
}
Class BetaFlareThrown : Actor
{
double pitchvel, anglevel, rollvel;
double desiredangle;
bool rotatetodesired;
double lastpitch, lastangle, lastroll;
Actor b;
Default
{
Radius 6;
Height 6;
+NOBLOCKMAP;
+MISSILE;
+MOVEWITHSECTOR;
+THRUACTORS;
+USEBOUNCESTATE;
+INTERPOLATEANGLES;
+BOUNCEAUTOOFF;
+BOUNCEAUTOOFFFLOORONLY;
+NOTELEPORT;
+FORCERADIUSDMG;
+NODAMAGETHRUST;
DamageType "Exploded";
Speed 12;
VSpeed 2;
Mass 1;
Gravity 0.35;
BounceType "Hexen";
WallBounceFactor 0.6;
BounceFactor 0.6;
ReactionTime 350;
}
virtual void A_SpawnEffect()
{
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
pitchvel = FRandom[Junk](5,15)*RandomPick[Junk](-1,1);
anglevel = FRandom[Junk](5,15)*RandomPick[Junk](-1,1);
rollvel = FRandom[Junk](5,15)*RandomPick[Junk](-1,1);
}
override void Tick()
{
lastpitch = pitch;
lastangle = angle;
lastroll = roll;
Super.Tick();
if ( rotatetodesired )
{
if ( deltaangle(pitch,0) ~== 0 ) pitch = 0;
else pitch += clamp(deltaangle(pitch,0),-pitchvel,pitchvel);
if ( deltaangle(angle,desiredangle) ~== 0 ) angle = desiredangle;
else angle += clamp(deltaangle(angle,desiredangle),-anglevel,anglevel);
if ( deltaangle(roll,0) ~== 0 ) roll = 0;
else roll += clamp(deltaangle(roll,0),-rollvel,rollvel);
}
else
{
angle += anglevel;
pitch += pitchvel;
roll += rollvel;
}
if ( ReactionTime <= 0 ) return;
if ( GetAge() < 9 ) return;
if ( waterlevel > 0 )
{
if ( tracer ) tracer.Destroy();
if ( b ) b.Destroy();
A_StopSound(CHAN_VOICE);
ReactionTime = 0;
return;
}
if ( !b )
{
A_PlaySound("flare/on");
A_PlaySound("flare/loop",CHAN_VOICE,.5,true);
b = Spawn("FlareHitbox",pos);
b.master = self;
}
b.A_AlertMonsters(0,AMF_TARGETEMITTER);
ReactionTime--;
}
States
{
Spawn:
FLAR A 1
{
if ( GetAge() >= 9 ) A_SpawnEffect();
return A_JumpIf(ReactionTime<=0,"Death");
}
Wait;
Bounce:
FLAR A 0
{
pitch = lastpitch;
angle = lastangle;
roll = lastroll;
rotatetodesired = true;
desiredangle = FRandom[Junk](0,360);
pitchvel = abs(pitchvel)*0.75;
anglevel = abs(anglevel)*0.75;
rollvel = abs(rollvel)*0.75;
}
Goto Spawn;
Death:
FLAR A 0 { anglevel *= 0; }
FLAR A 1
{
if ( waterlevel > 0 )
{
if ( b ) b.Destroy();
A_StopSound(CHAN_VOICE);
return ResolveState("Fizz");
}
A_SpawnEffect();
return A_JumpIf(ReactionTime<=0,1);
}
Wait;
FLAR A 0
{
if ( b ) b.Destroy();
if ( waterlevel > 0 ) return ResolveState("Fizz");
A_RemoveLight('PLight');
A_StopSound(CHAN_VOICE);
A_PlaySound("flare/explode");
A_Explode(50,50);
A_NoGravity();
A_Stop();
A_SetRenderStyle(1.,STYLE_Add);
SetZ(pos.z+9);
Spawn("FlareXLight",pos);
bMOVEWITHSECTOR = false;
bFORCEXYBILLBOARD = true;
A_SetScale(FRandomPick[ExploS](-1.5,1.5),FRandomPick[ExploS](-1.5,1.5));
int numpt = Random[ExploS](10,20);
for ( int i=0; i<numpt; i++ )
{
Vector3 pvel = (FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1)).unit()*FRandom[ExploS](1,3);
let s = Spawn("UTSmoke",pos);
s.vel = pvel;
}
numpt = Random[ExploS](6,15);
for ( int i=0; i<numpt; i++ )
{
Vector3 pvel = (FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1)).unit()*FRandom[ExploS](2,6);
let s = Spawn("UTSpark",pos);
s.vel = pvel;
}
numpt = Random[ExploS](15,20);
for ( int i=0; i<numpt; i++ )
{
Vector3 pvel = (FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1)).unit()*FRandom[ExploS](2,12);
let s = Spawn("UTChip",pos);
s.vel = pvel;
s.scale *= FRandom[ExploS](0.9,2.7);
}
return A_Jump(256,"Explo1","Explo2","Explo3","Explo4","Explo5");
}
Explo1:
EXP1 ABCDEFGH 3 Bright;
Stop;
Explo2:
EXP2 ABCDEFGH 3 Bright;
Stop;
Explo3:
EXP3 ABCDEFGH 3 Bright;
Stop;
Explo4:
EXP4 ABCDEFGH 3 Bright;
Stop;
Explo5:
EXP5 ABCDEFGH 3 Bright;
Stop;
Fizz:
FLAR A 0 A_RemoveLight('PLight');
FLAR A 1
{
special1++;
if ( special1 > 150 ) A_FadeOut();
}
Wait;
}
}
Class LightFlareThrown : BetaFlareThrown
{
override void A_SpawnEffect()
{
Vector3 x, y, z;
double a, s;
[x, y, z] = dt_CoordUtil.GetAxes(pitch,angle,roll);
for ( int i=0; i<4; i++ )
{
a = FRandom[BFlare](0,360);
s = FRandom[BFlare](0,0.15);
let t = Spawn("LightFlareParticle",pos+x*4+z);
t.vel = (x+y*cos(a)*s+z*sin(a)*s).unit()*FRandom[BFlare](1,4);
}
}
override void Tick()
{
Super.Tick();
if ( GetAge() == 9 ) A_AttachLightDef('PLight','LIGHTFLARE');
}
}
Class DarkFlareThrown : BetaFlareThrown
{
override void A_SpawnEffect()
{
Vector3 x, y, z;
double a, s;
[x, y, z] = dt_CoordUtil.GetAxes(pitch,angle,roll);
for ( int i=0; i<3; i++ )
{
a = FRandom[BFlare](0,360);
s = FRandom[BFlare](0,0.5);
let t = Spawn("DarkFlareParticle",pos+x*4+z);
t.vel = (x+y*cos(a)*s+z*sin(a)*s).unit()*FRandom[BFlare](4,8);
}
}
override void Tick()
{
Super.Tick();
if ( GetAge() == 9 ) A_AttachLightDef('PLight','DARKFLARE');
}
}
Class LightFlareParticle : UTSmoke
{
Default
{
StencilColor "FFFFFF";
Scale 0.2;
}
}
Class DarkFlareParticle : UTSmoke
{
Default
{
StencilColor "000000";
Scale 2.2;
}
}
Class Dampener : UnrealInventory
{
static bool Active( Actor Owner )
{
let d = Dampener(Owner.FindInventory("Dampener"));
if ( d && d.bActive ) return true;
return false;
}
override bool Use( bool pickup )
{
if ( pickup ) return false;
bActive = !bActive;
Owner.A_PlaySound(bActive?"dampener/on":"dampener/off",CHAN_ITEM);
return false;
}
override void DoEffect()
{
Super.DoEffect();
if ( !bActive ) return;
if ( DrainCharge(1) )
{
Owner.A_PlaySound("dampener/off",CHAN_ITEM);
if ( Owner.CheckLocalView() ) Console.Printf(StringTable.Localize("$D_DAMPENER"));
if ( Amount <= 0 ) DepleteOrDestroy();
}
}
Default
{
Tag "$T_DAMPENER";
Inventory.PickupMessage "$I_DAMPENER";
Inventory.Icon "I_Dampen";
Inventory.MaxAmount 3;
UnrealInventory.Charge 1000;
}
States
{
Spawn:
DAMP A 8 A_CheckProximity(1,"PlayerPawn",80,1,CPXF_ANCESTOR|CPXF_CHECKSIGHT);
Wait;
DAMP ABC 8;
DAMP DEFGHIJ 3;
DAMP D 0 A_CheckProximity(1,"PlayerPawn",80,0,CPXF_ANCESTOR|CPXF_CHECKSIGHT|CPXF_EXACT);
Goto Spawn+4;
DAMP CBA 8;
Goto Spawn;
}
}
Class Forcefield : UnrealInventory
{
Default
{
Tag "$T_FORCEFIELD";
Inventory.PickupMessage "$I_FORCEFIELD";
Inventory.Icon "I_ForceF";
Inventory.MaxAmount 5;
}
override bool Use( bool pickup )
{
if ( pickup ) return false;
Vector3 origin = Owner.Vec2OffsetZ(0,0,Owner.player.viewz);
FLineTraceData d;
Owner.LineTrace(Owner.angle,90,Owner.pitch,TRF_ABSPOSITION,origin.z,origin.x,origin.y,data:d);
if ( d.HitType != TRACE_HitNone ) origin = d.HitLocation-d.HitDir*(GetDefaultByType("ForceFieldEffect").radius+8);
else origin = d.HitLocation;
Owner.LineTrace(0,GetDefaultByType("ForceFieldEffect").height/2,90,TRF_ABSPOSITION,origin.z,origin.x,origin.y,data:d);
origin = d.HitLocation;
let a = Spawn("ForceFieldEffect",origin);
if ( !a.TestMobjLocation() )
{
if ( Owner.CheckLocalView() ) Console.Printf(StringTable.Localize("$M_FFNOROOM"));
a.Destroy();
return false;
}
a.target = Owner;
a.angle = Owner.angle;
return true;
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
tracer = Spawn("ForcefieldX",pos);
tracer.angle = angle;
tracer.target = self;
}
States
{
Spawn:
FFPK A -1;
Stop;
}
}
Class ForcefieldX : AsmdAmmoX
{
States
{
Spawn:
FFPK A -1 Bright;
Stop;
}
}
Class ForceFieldLight : DynamicLight
{
double cdown;
Default
{
DynamicLight.Type "Point";
+DYNAMICLIGHT.ATTENUATE;
Args 0,0,0,100;
}
override void Tick()
{
Super.Tick();
if ( !target )
{
Destroy();
return;
}
SetOrigin(target.Vec3Offset(0,0,target.height/2.),true);
if ( isFrozen() ) return;
args[LIGHT_RED] = int(255*cdown);
args[LIGHT_BLUE] = int(255*cdown);
if ( target.bSOLID )
{
cdown = min(1.,cdown+1./27);
return;
}
cdown = max(0.,cdown-1./35);
if ( cdown <= 0. ) Destroy();
}
}
Class ForcefieldEffect : Actor
{
double nvol;
int lct;
Default
{
Tag "$T_FORCEFIELD";
RenderStyle "Add";
+NOGRAVITY;
+NOTELEPORT;
+DONTSPLASH;
+CANNOTPUSH;
+SHOOTABLE;
+SOLID;
+NODAMAGE;
+NOBLOOD;
Mass int.max;
Health int.max;
Radius 15;
Height 45;
}
override int DamageMobj( Actor inflictor, Actor source, int damage, Name mod, int flags, double angle )
{
A_PlaySound("ffield/hit",CHAN_BODY);
return Super.DamageMobj(inflictor,source,damage,mod,flags,angle);
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
A_PlaySound("ffield/on",CHAN_ITEM);
A_PlaySound("ffield/active",CHAN_VOICE,0.6,true);
let tracer = Spawn("ForceFieldLight",pos);
tracer.target = self;
lct = 24;
}
States
{
Spawn:
FFLD ABCDEFGHIJ 3 Bright;
#### # 700 Bright;
#### # 35 Bright
{
A_UnsetShootable();
A_UnsetSolid();
}
FFLD A 1 Bright A_PlaySound("ffield/hit",CHAN_VOICE);
Stop;
}
}
Class UFlashLight1 : DynamicLight
{
int basecolor[3];
Default
{
DynamicLight.Type "Point";
+DynamicLight.SPOT;
+DynamicLight.ATTENUATE;
+DynamicLight.DONTLIGHTSELF;
args 0,0,0,560;
DynamicLight.SpotInnerAngle 3;
DynamicLight.SpotOuterAngle 10;
}
override void Tick()
{
Super.Tick();
if ( !target || !UnrealInventory(master) )
{
Destroy();
return;
}
if ( target.player ) SetOrigin((target.pos.x,target.pos.y,target.player.viewz),true);
else SetOrigin(target.vec3Offset(0,0,target.height*0.75),true);
A_SetAngle(target.angle,SPF_INTERPOLATE);
A_SetPitch(target.pitch,SPF_INTERPOLATE);
args[LIGHT_RED] = int(basecolor[0]*clamp(UnrealInventory(master).charge/1400.,0.,1.));
args[LIGHT_GREEN] = int(basecolor[1]*clamp(UnrealInventory(master).charge/1400.,0.,1.));
args[LIGHT_BLUE] = int(basecolor[2]*clamp(UnrealInventory(master).charge/1400.,0.,1.));
bDORMANT = (target.health <= 0);
if ( Inventory(target) && target.bInvisible ) bDORMANT = true;
// alert monsters hit by the light
if ( GetClass() != "UFlashLight1" ) return;
if ( !bDORMANT && target.player && (target.health > 0) )
{
BlockThingsIterator bt = BlockThingsIterator.Create(target,args[LIGHT_INTENSITY]);
while ( bt.Next() )
{
if ( !bt.Thing || (Distance3D(bt.Thing) > args[LIGHT_INTENSITY]) ) continue;
Vector3 aimdir = (cos(angle)*cos(pitch),sin(angle)*cos(pitch),-sin(pitch));
Vector3 reldir = Vec3To(bt.Thing).unit();
if ( (acos(aimdir dot reldir) < SpotOuterAngle+5) && bt.Thing.CheckSight(target) ) bt.Thing.LastHeard = target;
}
}
}
}
Class UFlashLight2 : UFlashLight1
{
Default
{
args 0,0,0,600;
DynamicLight.SpotInnerAngle 0;
DynamicLight.SpotOuterAngle 20;
}
}
Class UFlashlight : UnrealInventory
{
UFlashLight1 lt[2];
Default
{
Tag "$T_FLASHLIGHT";
Inventory.Icon "I_Flashl";
Inventory.MaxAmount 3;
Inventory.PickupMessage "$I_FLASHLIGHT";
UnrealInventory.Charge 2800;
}
virtual void SetupLights()
{
lt[0].basecolor[0] = 255;
lt[0].basecolor[1] = 240;
lt[0].basecolor[2] = 224;
lt[1].basecolor[0] = 128;
lt[1].basecolor[1] = 120;
lt[1].basecolor[2] = 112;
}
override bool Use( bool pickup )
{
if ( pickup ) return false;
bActive = !bActive;
Owner.A_PlaySound(bActive?"lite/pickup":"lite/off",CHAN_ITEM);
if ( bActive )
{
if ( !lt[0] ) lt[0] = UFlashLight1(Spawn("UFlashLight1",owner.pos));
lt[0].target = owner;
lt[0].master = self;
if ( !lt[1] ) lt[1] = UFlashLight1(Spawn("UFlashLight2",owner.pos));
lt[1].target = owner;
lt[1].master = self;
SetupLights();
}
else
{
if ( lt[0] ) lt[0].Destroy();
if ( lt[1] ) lt[1].Destroy();
}
return false;
}
override void DoEffect()
{
Super.DoEffect();
if ( !bActive ) return;
if ( DrainCharge(1) )
{
Owner.A_PlaySound("lite/off",CHAN_ITEM);
if ( Owner.CheckLocalView() ) Console.Printf(StringTable.Localize("$D_FLASHLIGHT"));
if ( Amount <= 0 ) DepleteOrDestroy();
}
}
override void DetachFromOwner()
{
Super.DetachFromOwner();
if ( lt[0] ) lt[0].Destroy();
if ( lt[1] ) lt[1].Destroy();
}
override void Travelled()
{
Super.Travelled();
if ( !bActive ) return;
if ( !lt[0] ) lt[0] = UFlashLight1(Spawn("UFlashLight1",owner.pos));
lt[0].target = owner;
lt[0].master = self;
if ( !lt[1] ) lt[1] = UFlashLight1(Spawn("UFlashLight2",owner.pos));
lt[1].target = owner;
lt[1].master = self;
SetupLights();
}
States
{
Spawn:
SLIT A -1;
Stop;
}
}
Class USearchlight : UFlashlight
{
Default
{
Tag "$T_SEARCHLIGHT";
Inventory.Icon "I_BigFl";
Inventory.MaxAmount 1;
Inventory.PickupMessage "$I_SEARCHLIGHT";
Inventory.RespawnTics 1050;
UnrealInventory.Charge 70000;
}
override void DoEffect()
{
Super.DoEffect();
if ( !bActive ) return;
if ( DrainCharge(1) )
{
Owner.A_PlaySound("lite/off",CHAN_ITEM);
if ( Owner.CheckLocalView() ) Console.Printf(StringTable.Localize("$D_SEARCHLIGHT"));
if ( Amount <= 0 ) DepleteOrDestroy();
}
}
override void SetupLights()
{
lt[0].basecolor[0] = 255;
lt[0].basecolor[1] = 224;
lt[0].basecolor[2] = 192;
lt[0].SpotInnerAngle = 20;
lt[0].SpotOuterAngle = 35;
lt[0].args[3] = 750;
lt[1].basecolor[0] = 128;
lt[1].basecolor[1] = 112;
lt[1].basecolor[2] = 96;
lt[1].SpotOuterAngle = 50;
lt[1].args[3] = 780;
}
}
Class SentryItem : UnrealInventory
{
Default
{
Tag "$T_SENTRY";
Inventory.Icon "I_Sentry";
Inventory.MaxAmount 1;
Inventory.PickupMessage "$I_SENTRY";
Inventory.RespawnTics 1050;
UnrealInventory.Charge MinigunSentryBase.sentryhealth;
+UNREALINVENTORY.DRAWSPECIAL;
}
static void TransferOwnership( Actor newowner, Actor sentry )
{
if ( sentry.master ) sentry.master.TakeInventory("SentryItem",200);
sentry.master = newowner;
sentry.SetTag(String.Format(StringTable.Localize("$T_OWNEDSENTRY"),newowner.player.GetUserName()));
sentry.tracer.A_ClearTarget();
sentry.tracer.SetFriendPlayer(newowner.player);
let si = SentryItem(newowner.FindInventory("SentryItem"));
if ( si )
{
si.bActive = true;
si.tracer = sentry;
}
else
{
newowner.GiveInventory("SentryItem",1);
let si = SentryItem(newowner.FindInventory("SentryItem"));
si.bActive = true;
si.tracer = sentry;
}
}
override void AttachToOwner( Actor other )
{
special1 = MinigunSentryBase.sentryammo;
Super.AttachToOwner(other);
}
override bool Use( bool pickup )
{
if ( pickup ) return false;
if ( bActive )
{
if ( Owner.Distance3D(tracer) > 80 )
{
if ( Owner.CheckLocalView() ) Console.Printf(StringTable.Localize("$M_NSTOOFAR"));
return false;
}
tracer.SetStateLabel("PackUp");
return false;
}
Vector3 origin = Owner.Vec2OffsetZ(0,0,Owner.player.viewz);
FLineTraceData d;
Owner.LineTrace(Owner.angle,90,Owner.pitch,TRF_ABSPOSITION,origin.z,origin.x,origin.y,data:d);
if ( d.HitType != TRACE_HitNone ) origin = d.HitLocation-d.HitDir*20;
else origin = d.HitLocation;
Owner.LineTrace(0,56,90,TRF_ABSPOSITION,origin.z,origin.x,origin.y,data:d);
origin = d.HitLocation;
let a = Spawn("MinigunSentryBase",origin);
if ( !a.TestMobjLocation() )
{
if ( Owner.CheckLocalView() ) Console.Printf(StringTable.Localize("$M_MSNOROOM"));
a.Destroy();
return false;
}
bActive = true;
bUNTOSSABLE = true;
bUNDROPPABLE = true;
tracer = a;
a.Health = Charge;
a.special1 = special1;
a.master = Owner;
a.angle = Owner.angle;
a.pitch = 0;
a.roll = 0;
return false;
}
override void DoEffect()
{
Super.DoEffect();
if ( !bActive ) return;
if ( !tracer )
{
bActive = false;
bUNTOSSABLE = false;
bUNDROPPABLE = false;
return;
}
Charge = tracer.Health;
special1 = tracer.special1;
if ( Charge <= 0 ) Destroy();
}
override void Travelled()
{
Super.Travelled();
if ( bActive && !tracer )
{
bUNTOSSABLE = false;
bUNDROPPABLE = false;
Destroy();
}
}
States
{
Spawn:
SENT A -1;
Stop;
}
}
// overlay for muzzle flash
Class MinigunSentryX : Actor
{
Default
{
RenderStyle "Add";
+NOGRAVITY;
+NOBLOCKMAP;
+INTERPOLATEANGLES;
}
override void Tick()
{
Super.Tick();
if ( !tracer || !tracer.InStateSequence(tracer.CurState,tracer.FindState("MissileLoop")) )
{
Destroy();
return;
}
SetOrigin(tracer.pos,true);
angle = tracer.angle;
pitch = tracer.pitch;
roll = tracer.roll;
}
States
{
Spawn:
SENF A 1 Bright;
TNT1 A 2;
SENF D 1 Bright;
TNT1 A 2;
SENF G 1 Bright;
TNT1 A 2;
SENF J 1 Bright;
TNT1 A 2;
SENF M 1 Bright;
TNT1 A 2;
SENF P 1 Bright;
TNT1 A 2;
Stop;
}
}
// The "head" of the sentry, attaches to the body
Class MinigunSentry : Actor
{
const maxangle = 80;
const maxpitch = 40;
Default
{
+NOGRAVITY;
+NOBLOCKMAP;
+INTERPOLATEANGLES;
+FRIENDLY;
}
override void Tick()
{
Super.Tick();
if ( !master )
{
Destroy();
return;
}
Vector3 x, y, z;
[x, y, z] = dt_CoordUtil.GetAxes(master.pitch,master.angle,master.roll);
SetOrigin(level.Vec3Offset(master.pos,z*38),true);
}
double _PitchTo( Actor other )
{
if ( !other ) return 0;
Vector3 otherpos = level.Vec3Diff(pos,other.Vec3Offset(0,0,other.height/2));
double dist = otherpos.length();
if ( dist > 0 ) return -asin(otherpos.z/dist);
return 0;
}
bool TargetVisible()
{
if ( !target || (target.Health <= 0) || !CheckSight(target) ) return false;
double angledelta = DeltaAngle(master.angle,AngleTo(target));
double pitchdelta = DeltaAngle(master.pitch,_PitchTo(target));
if ( (abs(angledelta) > maxangle) || (abs(pitchdelta) > maxpitch) ) return false;
return true;
}
void A_SentryFaceTarget()
{
if ( !TargetVisible() ) return;
double angledelta = DeltaAngle(angle,AngleTo(target));
double pitchdelta = DeltaAngle(pitch,_PitchTo(target));
double angleturn = clamp(abs(angledelta)*0.1,1,5);
double pitchturn = clamp(abs(pitchdelta)*0.1,1,5);
angle = clamp(angle+clamp(angledelta,-angleturn,angleturn),master.angle-maxangle,master.angle+maxangle);
pitch = clamp(pitch+clamp(pitchdelta,-pitchturn,pitchturn),master.pitch-maxpitch,master.pitch+maxpitch);
}
void A_SentryFaceDir( int dest, statelabel next )
{
double angledelta = DeltaAngle(angle,master.angle+dest);
double pitchdelta = DeltaAngle(pitch,master.pitch);
if ( max(abs(angledelta),abs(pitchdelta)) < 2 )
{
angle = master.angle+dest;
pitch = master.pitch;
SetStateLabel(next);
return;
}
angle = clamp(angle+clamp(angledelta,-2,2),master.angle-maxangle,master.angle+maxangle);
pitch = clamp(pitch+clamp(pitchdelta,-2,2),master.pitch-maxpitch,master.pitch+maxpitch);
}
void A_SentryAttack()
{
master.special1 = special1 = max(0,special1-1);
if ( (special1 <= 0) && master.master && master.master.CheckLocalView() ) Console.Printf(StringTable.Localize("$M_SENTRYDRY"));
A_SentryFaceTarget();
master.A_AlertMonsters(0,AMF_TARGETEMITTER);
A_PlaySound("sentry/fire",CHAN_WEAPON);
Vector3 x, y, z, origin;
[x, y, z] = dt_CoordUtil.GetAxes(pitch,angle,roll);
origin = level.Vec3Offset(pos,x*24+z*8);
double a = FRandom[Sentry](0,360), s = FRandom[Sentry](0,0.04);
Vector3 dir = (x+y*cos(a)*s+z*sin(a)*s).unit();
FLineTraceData d;
master.LineTrace(atan2(dir.y,dir.x),10000,asin(-dir.z),TRF_ABSPOSITION,origin.z,origin.x,origin.y,d);
if ( d.HitType == TRACE_HitActor )
{
int dmg = 17;
dmg = d.HitActor.DamageMobj(self,master,dmg,'shot',DMG_USEANGLE|DMG_THRUSTLESS,atan2(d.HitDir.y,d.HitDir.x));
double mm = 3000;
if ( FRandom[Sentry](0,1) < 0.2 ) mm *= 5;
UTMainHandler.DoKnockback(d.HitActor,d.HitDir,mm);
if ( d.HitActor.bNOBLOOD )
{
let p = Spawn("BulletImpact",d.HitLocation);
p.angle = atan2(d.HitDir.y,d.HitDir.x)+180;
p.pitch = asin(d.HitDir.z);
}
else
{
d.HitActor.TraceBleed(dmg,self);
d.HitActor.SpawnBlood(d.HitLocation,atan2(d.HitDir.y,d.HitDir.x)+180,dmg);
}
}
else if ( d.HitType != TRACE_HitNone )
{
Vector3 hitnormal = -d.HitDir;
if ( d.HitType == TRACE_HitFloor ) hitnormal = d.HitSector.floorplane.Normal;
else if ( d.HitType == TRACE_HitCeiling ) hitnormal = d.HitSector.ceilingplane.Normal;
else if ( d.HitType == TRACE_HitWall )
{
hitnormal = (-d.HitLine.delta.y,d.HitLine.delta.x,0).unit();
if ( !d.LineSide ) hitnormal *= -1;
}
let p = Spawn("BulletImpact",d.HitLocation+hitnormal*0.01);
p.angle = atan2(hitnormal.y,hitnormal.x);
p.pitch = asin(-hitnormal.z);
if ( d.HitLine ) d.HitLine.RemoteActivate(self,d.LineSide,SPAC_Impact,d.HitLocation);
}
for ( int i=0; i<3; i++ )
{
let s = Spawn("UTSmoke",origin);
s.alpha *= 0.5;
}
let c = Spawn("UCasing",level.Vec3Offset(pos,-x*3+y*2+z*1.5));
c.vel = x*FRandom[Junk](-1.5,1.5)+y*FRandom[Junk](2,4)+z*FRandom[Junk](2,3);
}
States
{
Spawn:
SENT A 15;
SENT A 0
{
A_PlaySound("sentry/raise");
master.A_AlertMonsters(0,AMF_TARGETEMITTER);
}
SENR ABCDEFGHIJKLMNO 3;
Goto Idle;
Idle:
SENI A 1 A_SentryFaceDir(0,1);
Wait;
SENI A 1
{
A_LookEx(LOF_NOSOUNDCHECK|LOF_NOJUMP);
A_SentryFaceDir(-maxangle,1);
return A_JumpIf(TargetVisible(),"See");
}
Wait;
SENI AAAAAA 1
{
A_LookEx(LOF_NOSOUNDCHECK|LOF_NOJUMP);
return A_JumpIf(TargetVisible(),"See");
}
SENI A 1
{
A_LookEx(LOF_NOSOUNDCHECK|LOF_NOJUMP);
A_SentryFaceDir(maxangle,1);
return A_JumpIf(TargetVisible(),"See");
}
Wait;
SENI AAAAAA 1
{
A_LookEx(LOF_NOSOUNDCHECK|LOF_NOJUMP);
return A_JumpIf(TargetVisible(),"See");
}
Goto Idle+1;
See:
SENI A 1
{
if ( !TargetVisible() )
{
A_ClearTarget();
return ResolveState("Idle");
}
if ( special1 > 0 ) A_Chase(null,"Missile",flags:CHF_DONTMOVE|CHF_NODIRECTIONTURN|CHF_DONTTURN);
A_SentryFaceTarget();
return ResolveState(null);
}
Wait;
Missile:
SENW A 0
{
A_PlaySound("sentry/wind",looping:true);
master.A_AlertMonsters(0,AMF_TARGETEMITTER);
master.SetStateLabel("Missile");
}
SENW ABCDEFGHIJKLMNOPQR 1 A_SentryFaceTarget();
Goto MissileLoop;
MissileLoop:
SENF A 0
{
let f = Spawn("MinigunSentryX",pos);
f.angle = angle;
f.pitch = pitch;
f.roll = roll;
f.tracer = self;
}
SENF A 0 A_JumpIf(!TargetVisible()||(special1<=0),"MissileEnd");
SENF A 1 A_SentryAttack();
SENF BC 1 A_SentryFaceTarget();
SENF D 0 A_JumpIf(!TargetVisible()||(special1<=0),"MissileEnd");
SENF D 1 A_SentryAttack();
SENF EF 1 A_SentryFaceTarget();
SENF G 0 A_JumpIf(!TargetVisible()||(special1<=0),"MissileEnd");
SENF G 1 A_SentryAttack();
SENF HI 1 A_SentryFaceTarget();
SENF J 0 A_JumpIf(!TargetVisible()||(special1<=0),"MissileEnd");
SENF J 1 A_SentryAttack();
SENF KL 1 A_SentryFaceTarget();
SENF M 0 A_JumpIf(!TargetVisible()||(special1<=0),"MissileEnd");
SENF M 1 A_SentryAttack();
SENF NO 1 A_SentryFaceTarget();
SENF P 0 A_JumpIf(!TargetVisible()||(special1<=0),"MissileEnd");
SENF P 1 A_SentryAttack();
SENF QR 1 A_SentryFaceTarget();
Loop;
MissileEnd:
SENU A 0
{
A_LookEx(LOF_NOSOUNDCHECK|LOF_NOJUMP);
if ( TargetVisible() && (special1>0) ) return ResolveState("MissileLoop");
A_PlaySound("sentry/unwind");
master.SetStateLabel("MissileEnd");
return ResolveState(null);
}
SENU ABCDEFGHIJKLMNOPQR 1 A_SentryFaceTarget();
SENI A 0 A_JumpIf(TargetVisible(),"See");
Goto Idle;
PackUp:
SENI A 1 A_SentryFaceDir(0,1);
Wait;
SENI A 0
{
A_PlaySound("sentry/raise");
master.A_AlertMonsters(0,AMF_TARGETEMITTER);
master.SetStateLabel("DoPackUp");
}
SENR ONMLKJIHGFEDCBA 3;
Stop;
}
}
Class SentryFragment : Actor
{
int deadtimer;
double rollvel, anglevel, pitchvel;
double heat;
Default
{
Radius 2;
Height 2;
+NOBLOCKMAP;
+MISSILE;
+MOVEWITHSECTOR;
+THRUACTORS;
+NOTELEPORT;
+DONTSPLASH;
+INTERPOLATEANGLES;
+USEBOUNCESTATE;
BounceType "Doom";
BounceFactor 0.3;
Gravity 0.35;
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
deadtimer = 0;
anglevel = FRandom[Junk](10,30)*RandomPick[Junk](-1,1);
pitchvel = FRandom[Junk](10,30)*RandomPick[Junk](-1,1);
rollvel = FRandom[Junk](10,30)*RandomPick[Junk](-1,1);
frame = Random[Junk](0,5);
scale *= Frandom[Junk](0.8,1.2);
heat = 1.5;
}
override void Tick()
{
Super.Tick();
if ( isFrozen() ) return;
if ( heat > 0 )
{
heat -= FRandom[Junk](0.003,0.006);
let s = Spawn("UTSmoke",pos);
s.alpha *= min(1.,heat)*0.6*alpha;
}
if ( InStateSequence(CurState,ResolveState("Death")) )
{
deadtimer++;
if ( deadtimer > 300 ) A_FadeOut(0.05);
return;
}
}
States
{
Spawn:
CHIP # 1
{
angle += anglevel;
pitch += pitchvel;
roll += rollvel;
}
Loop;
Bounce:
CHIP # 0
{
anglevel = FRandom[Junk](10,30)*RandomPick[Junk](-1,1);
pitchvel = FRandom[Junk](10,30)*RandomPick[Junk](-1,1);
rollvel = FRandom[Junk](10,30)*RandomPick[Junk](-1,1);
vel = (vel.unit()+(FRandom[Junk](-.2,.2),FRandom[Junk](-.2,.2),FRandom[Junk](-.2,.2))).unit()*vel.length();
A_PlaySound("vfrag/bounce",CHAN_BODY,min(1.,vel.length()*0.1),pitch:FRandom[Junk](0.6,1.4));
}
Goto Spawn;
Death:
CHIP # -1
{
pitch = int(pitch/180)*180;
roll = int(roll/180)*180;
}
Stop;
Dummy:
CHIP ABCDEF -1;
Stop;
}
}
Class SentryXLight : PaletteLight
{
Default
{
ReactionTime 30;
Args 0,0,0,120;
}
}
Class SentryBoom : Actor
{
Default
{
RenderStyle "Add";
Scale 3.2;
+NOGRAVITY;
+NOBLOCKMAP;
+NODAMAGETHRUST;
+FORCERADIUSDMG;
+FORCEXYBILLBOARD;
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
A_Explode(80,250);
A_PlaySound("sentry/explode");
UTMainHandler.DoBlast(self,250,70000);
double ang, pt;
for ( int i=0; i<16; i++ )
{
let f = Spawn("SentryFragment",Vec3Offset(FRandom[EFrag](-8,8),FRandom[EFrag](-8,8),FRandom[EFrag](4,40)));
ang = FRandom[EFrag](0,360);
pt = FRandom[EFrag](-90,90);
f.vel = (cos(ang)*cos(pt),sin(ang)*cos(pt),-sin(pt))*FRandom[EFrag](8,15);
}
Scale.x *= RandomPick[EFrag](-1,1);
Scale.y *= RandomPick[EFrag](-1,1);
int numpt = Random[ExploS](20,25);
for ( int i=0; i<numpt; i++ )
{
Vector3 pvel = (FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1)).unit()*FRandom[ExploS](1,3);
let s = Spawn("UTSmoke",pos);
s.vel = pvel;
}
numpt = Random[ExploS](10,20);
for ( int i=0; i<numpt; i++ )
{
Vector3 pvel = (FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1)).unit()*FRandom[ExploS](2,6);
let s = Spawn("UTSpark",pos);
s.vel = pvel;
}
numpt = Random[ExploS](20,30);
for ( int i=0; i<numpt; i++ )
{
Vector3 pvel = (FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1)).unit()*FRandom[ExploS](2,12);
let s = Spawn("UTChip",pos);
s.vel = pvel;
s.scale *= FRandom[ExploS](0.9,2.7);
}
Spawn("FlareXLight",pos);
}
States
{
Spawn:
TNT1 A 0 NoDelay A_Jump(256,"Explo1","Explo2","Explo3","Explo4","Explo5");
Explo1:
EXP1 ABCDEFGH 3 Bright;
Stop;
Explo2:
EXP2 ABCDEFGH 3 Bright;
Stop;
Explo3:
EXP3 ABCDEFGH 3 Bright;
Stop;
Explo4:
EXP4 ABCDEFGH 3 Bright;
Stop;
Explo5:
EXP5 ABCDEFGH 3 Bright;
Stop;
}
}
// The body of the sentry
Class MinigunSentryBase : Actor
{
const sentryammo = 200;
const sentryhealth = 500;
int rememberedplayer;
Default
{
Health sentryhealth;
Mass int.max;
Radius 12;
Height 48;
+SOLID;
+SHOOTABLE;
+NOBLOOD;
+DONTTHRUST;
+SPECIAL;
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
if ( master && master.player )
{
SetTag(String.Format(StringTable.Localize("$T_OWNEDSENTRY"),master.player.GetUserName()));
rememberedplayer = master.playernumber();
}
else
{
SetTag(StringTable.Localize("$T_SENTRY"));
rememberedplayer = -1;
}
tracer = Spawn("MinigunSentry",pos);
tracer.special1 = special1;
tracer.master = self;
tracer.angle = angle;
tracer.pitch = pitch;
tracer.roll = roll;
Vector3 x, y, z;
[x, y, z] = dt_CoordUtil.GetAxes(pitch,angle,roll);
tracer.SetOrigin(level.Vec3Offset(pos,z*38),false);
if ( !deathmatch ) return;
if ( master && master.player ) tracer.SetFriendPlayer(master.player);
else tracer.bFRIENDLY = false;
}
override void Tick()
{
Super.Tick();
// hub return support
if ( !master && (rememberedplayer != -1) && playeringame[rememberedplayer] )
{
master = players[rememberedplayer].mo;
let si = SentryItem(master.FindInventory("SentryItem"));
if ( si )
{
si.bActive = true;
si.tracer = self;
}
else
{
master.GiveInventory("SentryItem",1);
si = SentryItem(master.FindInventory("SentryItem"));
si.bActive = true;
si.tracer = self;
}
}
}
override string GetObituary( Actor victim, Actor inflictor, Name mod, bool playerattack )
{
if ( victim == master ) return String.Format(StringTable.Localize("$O_OWNSENTRY"),GetTag());
return String.Format(StringTable.Localize("$O_SENTRY"),GetTag());
}
override void Touch( Actor toucher )
{
if ( !toucher.player || !bSPECIAL ) return;
if ( deathmatch )
{
if ( master && (toucher != master) && master.CheckLocalView() )
Console.Printf(StringTable.Localize("$M_SENTRYHIJACK"));
SentryItem.TransferOwnership(toucher,self);
}
let amo = toucher.FindInventory("UMiniAmmo");
if ( !amo || (amo.Amount <= 0) || (tracer.special1 >= sentryammo) ) return;
A_PlaySound("misc/i_pkup",CHAN_ITEM);
int xammo = min(sentryammo-tracer.special1,amo.Amount);
special1 = tracer.special1 += xammo;
amo.Amount -= xammo;
}
override int DamageMobj( Actor inflictor, Actor source, int damage, Name mod, int flags, double angle )
{
if ( Health-damage <= 0 )
{
if ( master && master.CheckLocalView() )
Console.Printf(StringTable.Localize("$M_SENTRYDOWN"));
if ( master ) master.TakeInventory("SentryItem",1);
if ( tracer ) tracer.Destroy();
}
int dmg = Super.DamageMobj(inflictor,source,damage,mod,flags,angle);
if ( tracer && !tracer.target ) tracer.target = target;
return dmg;
}
States
{
Spawn:
SENT A 15;
SENR ABCDEFGHIJKLMNO 3;
Goto Idle;
Idle:
SENI A 1;
Wait;
Missile:
SENW ABCDEFGHIJKLMNOPQR 1;
Goto MissileLoop;
MissileLoop:
SENF ABCDEFGHIJKLMNOPQR 1;
Loop;
MissileEnd:
SENU ABCDEFGHIJKLMNOPQR 1;
Goto Idle;
PackUp:
SENI A -1
{
bSPECIAL = false;
tracer.SetStateLabel("PackUp");
}
Stop;
DoPackUp:
SENR ONMLKJIHGFEDCBA 3;
TNT1 A 1
{
if ( !master ) return ResolveState(null);
let si = SentryItem(master.FindInventory("SentryItem"));
if ( si )
{
si.charge = Health;
si.special1 = special1;
}
else
{
master.GiveInventory("SentryItem",1);
let si = SentryItem(master.FindInventory("SentryItem"));
si.charge = Health;
si.special1 = special1;
}
return ResolveState(null);
}
Stop;
Death:
TNT1 A 1 Spawn("SentryBoom",Vec3Offset(0,0,height/2));
Stop;
}
}