2575 lines
60 KiB
Text
2575 lines
60 KiB
Text
// Backpack that only gives ammo for valid weapons
|
|
Class UnrealBackpack : UTBackpack
|
|
{
|
|
override void DoPickupSpecial( Actor toucher )
|
|
{
|
|
Super.DoPickupSpecial(toucher);
|
|
if ( gameinfo.gametype&GAME_DOOMCHEX )
|
|
{
|
|
static const Class<Inventory> xitems[] = {"Flare", "Seeds", "VoiceBox", "ForceField", "Dampener", "Peacemaker", "SentryGunItem"};
|
|
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](-1,1));
|
|
xitemn[5] = max(0,Random[BackpackExtra](-2,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,7) ) xitemn[2] *= 2;
|
|
if ( !Random[BackpackExtra](0,6) ) xitemn[3] *= 2;
|
|
if ( !Random[BackpackExtra](0,5) ) xitemn[4] *= 2;
|
|
if ( !Random[BackpackExtra](0,9) ) xitemn[5] *= 2;
|
|
if ( !Random[BackpackExtra](0,9) ) xitemn[6] *= 2;
|
|
for ( int i=0; i<(sting_proto?7:5); i++ )
|
|
{
|
|
if ( xitemn[i] <= 0 ) continue;
|
|
toucher.GiveInventory(xitems[i],xitemn[i]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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_StartSound("translator/event",CHAN_VOICE,CHANF_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 2;
|
|
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 = level.Vec3Offset(Owner.Vec2OffsetZ(0,0,Owner.player.viewz),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
|
|
{
|
|
Mixin UMissileFix;
|
|
|
|
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",
|
|
"eightball/explode",
|
|
"automag/shot",
|
|
"automag/shot",
|
|
"uflak/altfire",
|
|
"eightball/explode",
|
|
"uflak/fire",
|
|
"asmd/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_StartSound(BattleSounds[i],CHAN_VOICE,CHANF_OVERLAP,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_StartSound("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_StartSound("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;
|
|
+UNREALINVENTORY.UNLIMITEDCOPIES;
|
|
}
|
|
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
|
|
{
|
|
Mixin UMissileFix;
|
|
|
|
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_StartSound("flare/on");
|
|
A_StartSound("flare/loop",CHAN_VOICE,CHANF_LOOPING,.5);
|
|
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_StartSound("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_proto || !sting_flares ) return false; // not allowed
|
|
return Super.TryPickup(toucher);
|
|
}
|
|
override void Tick()
|
|
{
|
|
Super.Tick();
|
|
if ( sting_proto && 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
|
|
{
|
|
Mixin UMissileFix;
|
|
|
|
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_StartSound("flare/on");
|
|
A_StartSound("flare/loop",CHAN_VOICE,CHANF_LOOPING,.5);
|
|
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_StartSound("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 = dt_Utility.ConeSpread(x,y,z,a,s)*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 = dt_Utility.ConeSpread(x,y,z,a,s)*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_StartSound(bActive?"dampener/on":"dampener/off",CHAN_ITEM);
|
|
return false;
|
|
}
|
|
override void DoEffect()
|
|
{
|
|
Super.DoEffect();
|
|
if ( !bActive ) return;
|
|
if ( DrainCharge(1) )
|
|
{
|
|
Owner.A_StartSound("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 3;
|
|
}
|
|
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 = level.Vec3Offset(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;
|
|
+DONTGIB;
|
|
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_StartSound("ffield/hit",flags:CHANF_OVERLAP);
|
|
return Super.DamageMobj(inflictor,source,damage,mod,flags,angle);
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
A_StartSound("ffield/on",CHAN_ITEM);
|
|
A_StartSound("ffield/active",CHAN_VOICE,CHANF_LOOPING,.6);
|
|
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_StartSound("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 || !master )
|
|
{
|
|
Destroy();
|
|
return;
|
|
}
|
|
if ( target.player ) SetOrigin(target.Vec2OffsetZ(0,0,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);
|
|
if ( UnrealInventory(master) )
|
|
{
|
|
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.));
|
|
}
|
|
else
|
|
{
|
|
args[LIGHT_RED] = basecolor[0];
|
|
args[LIGHT_GREEN] = basecolor[1];
|
|
args[LIGHT_BLUE] = basecolor[2];
|
|
}
|
|
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 = dt_Utility.Vec3FromAngle(angle,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_StartSound(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_StartSound("lite/off",CHAN_ITEM);
|
|
if ( Owner.CheckLocalView() ) Console.Printf(StringTable.Localize("$D_FLASHLIGHT"));
|
|
if ( Amount <= 0 ) DepleteOrDestroy();
|
|
else
|
|
{
|
|
if ( lt[0] ) lt[0].Destroy();
|
|
if ( lt[1] ) lt[1].Destroy();
|
|
}
|
|
}
|
|
}
|
|
override void DetachFromOwner()
|
|
{
|
|
if ( lt[0] ) lt[0].Destroy();
|
|
if ( lt[1] ) lt[1].Destroy();
|
|
Super.DetachFromOwner();
|
|
}
|
|
override void OwnerDied()
|
|
{
|
|
if ( lt[0] ) lt[0].Destroy();
|
|
if ( lt[1] ) lt[1].Destroy();
|
|
Super.OwnerDied();
|
|
}
|
|
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_StartSound("lite/off",CHAN_ITEM);
|
|
if ( Owner.CheckLocalView() ) Console.Printf(StringTable.Localize("$D_SEARCHLIGHT"));
|
|
if ( Amount <= 0 ) DepleteOrDestroy();
|
|
else
|
|
{
|
|
if ( lt[0] ) lt[0].Destroy();
|
|
if ( lt[1] ) lt[1].Destroy();
|
|
}
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
override bool TryPickup( in out Actor toucher )
|
|
{
|
|
if ( !sting_msentry ) return false; // not allowed
|
|
return Super.TryPickup(toucher);
|
|
}
|
|
override void Tick()
|
|
{
|
|
Super.Tick();
|
|
if ( sting_msentry ) return;
|
|
if ( Owner ) Owner.RemoveInventory(self);
|
|
else
|
|
{
|
|
let r = Spawn("Berserk",pos,ALLOW_REPLACE);
|
|
r.spawnangle = spawnangle;
|
|
r.spawnpoint = spawnpoint;
|
|
r.angle = angle;
|
|
r.pitch = pitch;
|
|
r.roll = roll;
|
|
r.special = special;
|
|
r.args[0] = args[0];
|
|
r.args[1] = args[1];
|
|
r.args[2] = args[2];
|
|
r.args[3] = args[3];
|
|
r.args[4] = args[4];
|
|
r.ChangeTid(tid);
|
|
r.SpawnFlags = SpawnFlags&~MTF_SECRET;
|
|
r.HandleSpawnFlags();
|
|
r.SpawnFlags = SpawnFlags;
|
|
r.bCountSecret = SpawnFlags&MTF_SECRET;
|
|
r.vel = vel;
|
|
r.master = master;
|
|
r.target = target;
|
|
r.tracer = tracer;
|
|
r.bDropped = bDropped;
|
|
r.bNeverRespawn = bNeverRespawn;
|
|
}
|
|
Destroy();
|
|
}
|
|
override bool HandlePickup( Inventory item )
|
|
{
|
|
if ( item.GetClass() == GetClass() ) return true; // can never get more than one
|
|
return Super.HandlePickup(item);
|
|
}
|
|
static bool TransferOwnership( Actor newowner, Actor sentry )
|
|
{
|
|
if ( sentry.master == newowner ) return false;
|
|
if ( sentry.master ) sentry.master.TakeInventory("SentryItem",1);
|
|
sentry.master = newowner;
|
|
sentry.SetTag(String.Format(StringTable.Localize("$T_OWNEDSENTRY"),newowner.player.GetUserName()));
|
|
sentry.tracer.A_ClearTarget();
|
|
sentry.tracer.bFRIENDLY = true;
|
|
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;
|
|
}
|
|
return true;
|
|
}
|
|
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,80,Owner.pitch,TRF_ABSPOSITION,origin.z,origin.x,origin.y,data:d);
|
|
if ( d.HitType != TRACE_HitNone )
|
|
{
|
|
Vector3 normal = -d.HitDir;
|
|
if ( d.HitType == TRACE_HitFloor )
|
|
{
|
|
if ( d.Hit3DFloor ) normal = -d.Hit3DFloor.top.Normal;
|
|
else normal = d.HitSector.floorplane.Normal;
|
|
}
|
|
else if ( d.HitType == TRACE_HitCeiling )
|
|
{
|
|
if ( d.Hit3DFloor ) normal = -d.Hit3DFloor.bottom.Normal;
|
|
else normal = d.HitSector.ceilingplane.Normal;
|
|
}
|
|
else if ( d.HitType == TRACE_HitWall )
|
|
{
|
|
normal = (-d.HitLine.delta.y,d.HitLine.delta.x,0).unit();
|
|
if ( !d.LineSide ) normal *= -1;
|
|
}
|
|
origin = level.Vec3Offset(d.HitLocation,normal*20);
|
|
}
|
|
else origin = level.Vec3Offset(d.HitLocation,-d.HitDir*20);
|
|
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;
|
|
}
|
|
if ( a.pos.z-a.floorz > 50 )
|
|
{
|
|
if ( Owner.CheckLocalView() ) Console.Printf(StringTable.Localize("$M_MSNOFLOOR"));
|
|
a.Destroy();
|
|
return false;
|
|
}
|
|
F3DFloor ff = null;
|
|
for ( int i=0; i<a.floorsector.Get3DFloorCount(); i++ )
|
|
{
|
|
if ( a.floorsector.Get3DFloor(i).top.ZAtPoint(pos.xy) > floorz ) continue;
|
|
ff = a.floorsector.Get3DFLoor(i);
|
|
break;
|
|
}
|
|
Vector3 normal;
|
|
if ( ff ) normal = -ff.top.Normal;
|
|
else normal = a.floorsector.floorplane.Normal;
|
|
if ( normal.z < 0.9 )
|
|
{
|
|
if ( Owner.CheckLocalView() ) Console.Printf(StringTable.Localize("$M_MSNOFLAT"));
|
|
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 )
|
|
{
|
|
if ( !(level.maptime%10) ) Charge = min(DefaultCharge,Charge+1);
|
|
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*32),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 ( !master || !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( double dest, statelabel next )
|
|
{
|
|
if ( !master ) return;
|
|
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()
|
|
{
|
|
if ( !master ) return;
|
|
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_StartSound("sentry/fire",CHAN_WEAPON,CHANF_OVERLAP);
|
|
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 = dt_Utility.ConeSpread(x,y,z,a,s);
|
|
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_StartSound("sentry/raise");
|
|
if ( master ) master.A_AlertMonsters(0,AMF_TARGETEMITTER);
|
|
}
|
|
SENR ABCDEFGHIJKLMNO 3;
|
|
Goto Idle;
|
|
Idle:
|
|
SENI A 0
|
|
{
|
|
A_StartSound("sentry/move",flags:CHANF_LOOPING,.4,pitch:.8);
|
|
if ( specialf1 > 0 ) specialf1 = -maxangle;
|
|
else specialf1 = maxangle;
|
|
special2 = 0;
|
|
}
|
|
SENI A 1
|
|
{
|
|
A_LookEx(LOF_NOSOUNDCHECK|LOF_NOJUMP);
|
|
A_SentryFaceDir(specialf1,"IdleStop");
|
|
return A_JumpIf(TargetVisible(),"See");
|
|
}
|
|
Wait;
|
|
IdleStop:
|
|
SENI A 0 A_StartSound("sentry/movestop",volume:.4,pitch:.8);
|
|
SENI A 1
|
|
{
|
|
A_LookEx(LOF_NOSOUNDCHECK|LOF_NOJUMP);
|
|
if ( special2++ > 20 ) return ResolveState("Idle");
|
|
return A_JumpIf(TargetVisible(),"See");
|
|
}
|
|
Wait;
|
|
See:
|
|
SENI A 0 A_StartSound("sentry/movestop",volume:.4,pitch:.8);
|
|
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_StartSound("sentry/wind",flags:CHANF_LOOPING);
|
|
if ( master )
|
|
{
|
|
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_StartSound("sentry/unwind");
|
|
if ( master ) 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_StartSound("sentry/raise");
|
|
if ( master )
|
|
{
|
|
master.A_AlertMonsters(0,AMF_TARGETEMITTER);
|
|
master.SetStateLabel("DoPackUp");
|
|
}
|
|
}
|
|
SENR ONMLKJIHGFEDCBA 3;
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
Class SentryFragment : Actor
|
|
{
|
|
Mixin UMissileFix;
|
|
|
|
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_StartSound("vfrag/bounce",volume: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_StartSound("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 = dt_Utility.Vec3FromAngle(ang,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
|
|
{
|
|
Tag "$T_SENTRY";
|
|
Health sentryhealth;
|
|
Mass int.max;
|
|
Radius 8;
|
|
Height 35;
|
|
+SOLID;
|
|
+SHOOTABLE;
|
|
+NOBLOOD;
|
|
+DONTTHRUST;
|
|
+NOICEDEATH;
|
|
+DONTGIB;
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
if ( master && master.player )
|
|
{
|
|
SetTag(String.Format(StringTable.Localize("$T_OWNEDSENTRY"),master.player.GetUserName()));
|
|
rememberedplayer = master.playernumber();
|
|
}
|
|
else
|
|
{
|
|
rememberedplayer = -1;
|
|
special1 = sentryammo; // rogue sentries need ammo assigned
|
|
}
|
|
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 )
|
|
{
|
|
if ( !master || !master.player ) tracer.bFRIENDLY = bFRIENDLY;
|
|
bFRIENDLY = false;
|
|
return;
|
|
}
|
|
if ( master && master.player ) tracer.SetFriendPlayer(master.player);
|
|
else tracer.bFRIENDLY = bFRIENDLY;
|
|
bFRIENDLY = false;
|
|
}
|
|
override void Tick()
|
|
{
|
|
Super.Tick();
|
|
if ( !sting_msentry )
|
|
{
|
|
if ( tracer ) tracer.Destroy();
|
|
Destroy();
|
|
return;
|
|
}
|
|
// 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 StringTable.Localize("$O_OWNSENTRY");
|
|
else if ( master && master.player ) return String.Format(StringTable.Localize("$O_SENTRY"),GetTag());
|
|
return StringTable.Localize("$O_ROGUESENTRY");
|
|
}
|
|
override bool Used( Actor user )
|
|
{
|
|
if ( !user.player || !InStateSequence(CurState,FindState("Idle")) ) return false;
|
|
if ( abs(DeltaAngle(angle,AngleTo(user))) < 120 ) return false;
|
|
if ( !master || ((user != master) && master.IsHostile(user)) )
|
|
{
|
|
if ( master && master.CheckLocalView() )
|
|
Console.Printf(StringTable.Localize("$M_SENTRYHIJACK"));
|
|
if ( SentryItem.TransferOwnership(user,self) )
|
|
return false; // on first touch only transfer ownership
|
|
}
|
|
let amo = user.FindInventory("UMiniAmmo");
|
|
if ( !amo || (amo.Amount <= 0) || (tracer.special1 >= sentryammo) ) return false;
|
|
A_StartSound("misc/i_pkup",CHAN_ITEM);
|
|
int xammo = min(sentryammo-tracer.special1,amo.Amount);
|
|
special1 = tracer.special1 += xammo;
|
|
amo.Amount -= xammo;
|
|
return false;
|
|
}
|
|
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
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
|
|
// original fun-size stationary version
|
|
Class SentryGunItem : UnrealInventory
|
|
{
|
|
Default
|
|
{
|
|
Tag "$T_OSENTRY";
|
|
Inventory.Icon "I_OSntry";
|
|
Inventory.MaxAmount 2;
|
|
Inventory.PickupMessage "$I_OSENTRY";
|
|
Inventory.RespawnTics 1050;
|
|
}
|
|
override bool TryPickup( in out Actor toucher )
|
|
{
|
|
if ( !sting_proto ) return false; // not allowed
|
|
return Super.TryPickup(toucher);
|
|
}
|
|
override void Tick()
|
|
{
|
|
Super.Tick();
|
|
if ( sting_proto ) return;
|
|
if ( Owner ) Owner.RemoveInventory(self);
|
|
Destroy();
|
|
}
|
|
override bool Use( bool pickup )
|
|
{
|
|
if ( pickup ) return false;
|
|
Vector3 origin = Owner.Vec2OffsetZ(0,0,Owner.player.viewz);
|
|
FLineTraceData d;
|
|
Owner.LineTrace(Owner.angle,60,Owner.pitch,TRF_ABSPOSITION,origin.z,origin.x,origin.y,data:d);
|
|
if ( d.HitType != TRACE_HitNone )
|
|
{
|
|
Vector3 normal = -d.HitDir;
|
|
if ( d.HitType == TRACE_HitFloor )
|
|
{
|
|
if ( d.Hit3DFloor ) normal = -d.Hit3DFloor.top.Normal;
|
|
else normal = d.HitSector.floorplane.Normal;
|
|
}
|
|
else if ( d.HitType == TRACE_HitCeiling )
|
|
{
|
|
if ( d.Hit3DFloor ) normal = -d.Hit3DFloor.bottom.Normal;
|
|
else normal = d.HitSector.ceilingplane.Normal;
|
|
}
|
|
else if ( d.HitType == TRACE_HitWall )
|
|
{
|
|
normal = (-d.HitLine.delta.y,d.HitLine.delta.x,0).unit();
|
|
if ( !d.LineSide ) normal *= -1;
|
|
}
|
|
origin = level.Vec3Offset(d.HitLocation,normal*20);
|
|
}
|
|
else origin = level.Vec3Offset(d.HitLocation,-d.HitDir*20);
|
|
Owner.LineTrace(0,56,90,TRF_ABSPOSITION,origin.z,origin.x,origin.y,data:d);
|
|
origin = d.HitLocation;
|
|
let a = Spawn("SentryGun",origin);
|
|
if ( !a.TestMobjLocation() )
|
|
{
|
|
if ( Owner.CheckLocalView() ) Console.Printf(StringTable.Localize("$M_MSNOROOM"));
|
|
a.Destroy();
|
|
return false;
|
|
}
|
|
if ( a.pos.z-a.floorz > 50 )
|
|
{
|
|
if ( Owner.CheckLocalView() ) Console.Printf(StringTable.Localize("$M_MSNOFLOOR"));
|
|
a.Destroy();
|
|
return false;
|
|
}
|
|
F3DFloor ff = null;
|
|
for ( int i=0; i<a.floorsector.Get3DFloorCount(); i++ )
|
|
{
|
|
if ( a.floorsector.Get3DFloor(i).top.ZAtPoint(pos.xy) > floorz ) continue;
|
|
ff = a.floorsector.Get3DFLoor(i);
|
|
break;
|
|
}
|
|
Vector3 normal;
|
|
if ( ff ) normal = -ff.top.Normal;
|
|
else normal = a.floorsector.floorplane.Normal;
|
|
if ( normal.z < 0.9 )
|
|
{
|
|
if ( Owner.CheckLocalView() ) Console.Printf(StringTable.Localize("$M_MSNOFLAT"));
|
|
a.Destroy();
|
|
return false;
|
|
}
|
|
tracer = a;
|
|
a.master = Owner;
|
|
a.angle = Owner.angle;
|
|
a.pitch = 0;
|
|
a.roll = 0;
|
|
return true;
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
SENT A -1;
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
Class SentryGunX : Actor
|
|
{
|
|
Default
|
|
{
|
|
RenderStyle "Add";
|
|
+NOGRAVITY;
|
|
+NOBLOCKMAP;
|
|
+INTERPOLATEANGLES;
|
|
}
|
|
override void Tick()
|
|
{
|
|
Super.Tick();
|
|
if ( !tracer || !tracer.InStateSequence(tracer.CurState,tracer.FindState("Fire")) )
|
|
{
|
|
Destroy();
|
|
return;
|
|
}
|
|
SetOrigin(tracer.pos,true);
|
|
angle = tracer.angle;
|
|
pitch = tracer.pitch;
|
|
roll = tracer.roll;
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
SENF A 2 Bright;
|
|
TNT1 A 2;
|
|
SENF C 2 Bright;
|
|
TNT1 A 2;
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
Class SentryGun : Actor
|
|
{
|
|
int rememberedplayer;
|
|
Default
|
|
{
|
|
Tag "$T_OSENTRY";
|
|
Health 50;
|
|
Mass int.max;
|
|
Radius 6;
|
|
Height 15;
|
|
+SOLID;
|
|
+SHOOTABLE;
|
|
+NOBLOOD;
|
|
+DONTTHRUST;
|
|
+FRIENDLY;
|
|
+NOICEDEATH;
|
|
+DONTGIB;
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
if ( master && master.player )
|
|
{
|
|
SetTag(String.Format(StringTable.Localize("$T_OWNEDOSENTRY"),master.player.GetUserName()));
|
|
rememberedplayer = master.playernumber();
|
|
}
|
|
else rememberedplayer = -1;
|
|
if ( !deathmatch )
|
|
{
|
|
if ( !master || !master.player ) bFRIENDLY = false;
|
|
return;
|
|
}
|
|
if ( master && master.player ) SetFriendPlayer(master.player);
|
|
else bFRIENDLY = false;
|
|
}
|
|
override string GetObituary( Actor victim, Actor inflictor, Name mod, bool playerattack )
|
|
{
|
|
if ( victim == master ) return StringTable.Localize("$O_OWNOSENTRY");
|
|
else if ( master && master.player ) return String.Format(StringTable.Localize("$O_OSENTRY"),GetTag());
|
|
return StringTable.Localize("$O_ROGUEOSENTRY");
|
|
}
|
|
override void Tick()
|
|
{
|
|
Super.Tick();
|
|
if ( !sting_proto )
|
|
{
|
|
Destroy();
|
|
return;
|
|
}
|
|
if ( !master && (rememberedplayer != -1) && playeringame[rememberedplayer] )
|
|
master = players[rememberedplayer].mo;
|
|
}
|
|
bool IsEnemy( Actor a )
|
|
{
|
|
if ( !a || !a.bSHOOTABLE || !a.bISMONSTER || (a.Health <= 0) ) return false;
|
|
return IsHostile(a);
|
|
}
|
|
bool HasTarget()
|
|
{
|
|
// check for targets in range
|
|
let ti = ThinkerIterator.Create("Actor");
|
|
Actor a;
|
|
while ( a = Actor(ti.Next()) )
|
|
{
|
|
if ( !IsEnemy(a) ) continue;
|
|
Vector3 x = dt_Utility.Vec3FromAngle(angle,pitch);
|
|
Vector3 vecto = level.Vec3Diff(Vec3Offset(0,0,16),a.Vec3Offset(0,0,a.height/2));
|
|
double distto = vecto.length();
|
|
Vector3 dirto = vecto/distto;
|
|
double angleto = atan2(dirto.y,dirto.x);
|
|
double pitchto = asin(-dirto.z);
|
|
if ( (distto < 6000) && (dirto dot x > 0.95) && !LineTrace(angleto,distto,pitchto,TRF_THRUACTORS,16) )
|
|
{
|
|
target = a;
|
|
return true;
|
|
}
|
|
}
|
|
// check for targets in a straight line
|
|
FLineTraceData d;
|
|
LineTrace(angle,200,pitch,0,16,data:d);
|
|
if ( (d.HitType == TRACE_HitActor) && IsEnemy(d.HitActor) )
|
|
{
|
|
target = d.HitActor;
|
|
return true;
|
|
}
|
|
target = null;
|
|
return false;
|
|
}
|
|
// if there's a target and we have ammo, jump to first state (if set)
|
|
// if there's no target but we still have ammo, jump to second state (if set)
|
|
// if we have no ammo, jump to third state (if set)
|
|
void A_SentryGunCheck( statelabel actstate = null, statelabel idlestate = null, statelabel failstate = null )
|
|
{
|
|
if ( !(GetAge()%35) ) special1++;
|
|
if ( (special1 >= 200) && failstate ) SetStateLabel(failstate);
|
|
else if ( HasTarget() && actstate ) SetStateLabel(actstate);
|
|
else if ( !HasTarget() && idlestate ) SetStateLabel(idlestate);
|
|
}
|
|
void A_SentryGunAttack( statelabel failstate = null )
|
|
{
|
|
special1++;
|
|
if ( ((special1 >= 200) || !HasTarget()) && failstate )
|
|
{
|
|
A_StopSound(CHAN_BODY);
|
|
SetStateLabel(failstate);
|
|
return;
|
|
}
|
|
A_AlertMonsters(0,AMF_TARGETEMITTER);
|
|
A_StartSound("sentry/fire",CHAN_WEAPON,CHANF_OVERLAP,pitch:1.6);
|
|
Vector3 x, y, z, origin;
|
|
[x, y, z] = dt_CoordUtil.GetAxes(pitch,angle,roll);
|
|
origin = level.Vec3Offset(pos,x*12+z*16);
|
|
double a = FRandom[Sentry](0,360), s = FRandom[Sentry](0,0.05);
|
|
Vector3 nx = target?Vec3To(target).unit():x;
|
|
Vector3 dir = dt_Utility.ConeSpread(nx,y,z,a,s);
|
|
FLineTraceData d;
|
|
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 = Random[OSentry](6,17);
|
|
dmg = d.HitActor.DamageMobj(self,self,dmg,'shot',DMG_USEANGLE|DMG_THRUSTLESS,atan2(d.HitDir.y,d.HitDir.x));
|
|
double mm = 3000;
|
|
if ( FRandom[OSentry](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;
|
|
s.scale *= 0.7;
|
|
s.vel += x*2;
|
|
}
|
|
let c = Spawn("UCasing",level.Vec3Offset(pos,-x*8+y*0.6+z*16));
|
|
c.scale *= 0.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;
|
|
Goto StartUp;
|
|
StartUp:
|
|
SENR A 0
|
|
{
|
|
A_StartSound("sentry/raise",pitch:1.6);
|
|
A_AlertMonsters(0,AMF_TARGETEMITTER);
|
|
}
|
|
SENR ABCDE 4;
|
|
Goto Idle;
|
|
Idle:
|
|
SENI A 1 A_SentryGunCheck("WindUp",null,"ShutDown");
|
|
Wait;
|
|
WindUp:
|
|
SENW A 0
|
|
{
|
|
A_StartSound("sentry/wind",flags:CHANF_LOOPING,pitch:1.6);
|
|
A_AlertMonsters(0,AMF_TARGETEMITTER);
|
|
}
|
|
SENW ABCDEFGHIJKLMN 1;
|
|
Fire:
|
|
SENF A 0
|
|
{
|
|
let f = Spawn("SentryGunX",pos);
|
|
f.angle = angle;
|
|
f.pitch = pitch;
|
|
f.roll = roll;
|
|
f.tracer = self;
|
|
}
|
|
SENF A 2 A_SentryGunAttack("Unwind");
|
|
SENF B 2;
|
|
SENF C 2 A_SentryGunAttack("Unwind");
|
|
SENF D 2;
|
|
SENU A 0; // tweening hack
|
|
Loop;
|
|
Unwind:
|
|
SENU A 0 A_StartSound("sentry/unwind",pitch:1.6);
|
|
SENU ABCDEFGHIJKLMN 1;
|
|
Goto Idle;
|
|
ShutDown:
|
|
SEND A 0 A_StartSound("sentry/raise",pitch:1.6);
|
|
SEND ABCDE 4;
|
|
SEND E 20;
|
|
SEND E -1 A_Die();
|
|
Stop;
|
|
Death:
|
|
TNT1 A 0
|
|
{
|
|
A_StopSound(CHAN_BODY);
|
|
A_StartSound("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;
|
|
}
|
|
}
|