Addition to HUD for showing clip counts (kinda cheap-looking, but still better than what Oldskool did). Charge for some weapons and loaded rockets for eightball also use the clipcount display, like in Doom Tournament. Small fixups for dual wielding logic.
2615 lines
62 KiB
Text
2615 lines
62 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", "SentryGunItem", "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 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 = 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 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 = 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 || !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);
|
|
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 = (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;
|
|
}
|
|
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;
|
|
}
|
|
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 = d.HitLocation+normal*20;
|
|
}
|
|
else origin = 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*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( double 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 0
|
|
{
|
|
A_PlaySound("sentry/move",CHAN_BODY,0.4,true,pitch:0.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_PlaySound("sentry/movestop",CHAN_BODY,0.4,pitch:0.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_PlaySound("sentry/movestop",CHAN_BODY,0.4,pitch:0.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_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
|
|
{
|
|
Tag "$T_SENTRY";
|
|
Health sentryhealth;
|
|
Mass int.max;
|
|
Radius 8;
|
|
Height 46;
|
|
+SOLID;
|
|
+SHOOTABLE;
|
|
+NOBLOOD;
|
|
+DONTTHRUST;
|
|
}
|
|
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 ( deathmatch || !master )
|
|
{
|
|
if ( master && (user != 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_PlaySound("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 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 = d.HitLocation+normal*20;
|
|
}
|
|
else origin = 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 18;
|
|
+SOLID;
|
|
+SHOOTABLE;
|
|
+NOBLOOD;
|
|
+DONTTHRUST;
|
|
+FRIENDLY;
|
|
}
|
|
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 ( !master && (rememberedplayer != -1) && playeringame[rememberedplayer] )
|
|
master = players[rememberedplayer].mo;
|
|
}
|
|
bool IsEnemy( Actor a )
|
|
{
|
|
if ( !a || !a.bSHOOTABLE || !a.bISMONSTER || (a.Health <= 0) ) return false;
|
|
if ( deathmatch ) return ((a != master) && (a.master != master));
|
|
return (!bFRIENDLY || (!a.bFRIENDLY && !a.player));
|
|
}
|
|
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 = (cos(angle)*cos(pitch),sin(angle)*cos(pitch),-sin(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_PlaySound("sentry/fire",CHAN_WEAPON,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 = (nx+y*cos(a)*s+z*sin(a)*s).unit();
|
|
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_PlaySound("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_PlaySound("sentry/wind",looping:true,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_PlaySound("sentry/unwind",pitch:1.6);
|
|
SENU ABCDEFGHIJKLMN 1;
|
|
Goto Idle;
|
|
ShutDown:
|
|
SEND A 0 A_PlaySound("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_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;
|
|
}
|
|
}
|