stinger_m/zscript/miscitems.zsc
Marisa Kirisame b12c4a4112 Flashlight/Searchlight implemented.
Proper handling of charge redistribution on multi-copy items (fixes infinitely replenishable charge exploit).
More preparation code for other items.
2019-09-05 16:31:05 +02:00

1181 lines
25 KiB
Text

// Backpack that only gives ammo for valid weapons
Class UnrealBackpack : BackpackItem replaces Backpack
{
override Inventory CreateCopy( Actor other )
{
// Find every unique type of ammoitem. Give it to the player if
// he doesn't have it already, and double its maximum capacity.
for ( int i=0; i<AllActorClasses.Size(); i++ )
{
let type = (class<Ammo>)(AllActorClasses[i]);
if ( !type || (type.GetParentClass() != 'Ammo') ) continue;
// check that it's for a valid weapon
bool isvalid = false;
for ( int j=0; j<AllActorClasses.Size(); j++ )
{
let type2 = (class<Weapon>)(AllActorClasses[j]);
if ( !type2 ) continue;
let rep = GetReplacement(type2);
if ( (rep != type2) && !(rep is "DehackedPickup") ) continue;
readonly<Weapon> weap = GetDefaultByType(type2);
if ( !other.player || !other.player.weapons.LocateWeapon(type2) || weap.bCheatNotWeapon ) continue;
if ( (weap.AmmoType1 == type) || (weap.AmmoType2 == type) )
{
isvalid = true;
break;
}
}
if ( !isvalid ) continue;
let ammoitem = Ammo(other.FindInventory(type));
int amount = GetDefaultByType(type).BackpackAmount;
// extra ammo in baby mode and nightmare mode
if ( !bIgnoreSkill ) amount = int(amount*G_SkillPropertyFloat(SKILLP_AmmoFactor));
if ( amount < 0 ) amount = 0;
if ( !ammoitem )
{
// The player did not have the ammoitem. Add it.
ammoitem = Ammo(Spawn(type));
ammoitem.Amount = bDepleted?0:amount;
if ( ammoitem.BackpackMaxAmount > ammoitem.MaxAmount )
ammoitem.MaxAmount = ammoitem.BackpackMaxAmount;
if ( ammoitem.Amount > ammoitem.MaxAmount )
ammoitem.Amount = ammoitem.MaxAmount;
ammoitem.AttachToOwner(other);
}
else
{
// The player had the ammoitem. Give some more.
if ( ammoitem.MaxAmount < ammoitem.BackpackMaxAmount )
ammoitem.MaxAmount = ammoitem.BackpackMaxAmount;
if ( !bDepleted && (ammoitem.Amount < ammoitem.MaxAmount) )
{
ammoitem.Amount += amount;
if ( ammoitem.Amount > ammoitem.MaxAmount )
ammoitem.Amount = ammoitem.MaxAmount;
}
}
}
return Inventory.CreateCopy(other);
}
override bool HandlePickup( Inventory item )
{
// Since you already have a backpack, that means you already have every
// kind of ammo in your inventory, so we don't need to look at the
// entire PClass list to discover what kinds of ammo exist, and we don't
// have to alter the MaxAmount either.
if ( item is 'BackpackItem' )
{
for ( let probe = Owner.Inv; probe; probe = probe.Inv )
{
if ( probe.GetParentClass() != 'Ammo' ) continue;
if ( probe.Amount >= probe.MaxAmount && !sv_unlimited_pickup ) continue;
int amount = Ammo(probe).Default.BackpackAmount;
// extra ammo in baby mode and nightmare mode
if ( !bIgnoreSkill )
amount = int(amount*G_SkillPropertyFloat(SKILLP_AmmoFactor));
probe.Amount += amount;
if ( (probe.Amount > probe.MaxAmount) && !sv_unlimited_pickup )
probe.Amount = probe.MaxAmount;
}
// The pickup always succeeds, even if you didn't get anything
item.bPickupGood = true;
return true;
}
return false;
}
override void DoPickupSpecial( Actor toucher )
{
Super.DoPickupSpecial(toucher);
if ( gameinfo.gametype&GAME_DOOMCHEX )
{
static const Class<Inventory> xitems[] = {"Flare", "Seeds", "SentryItem", "VoiceBox", "ForceField", "Dampener", "Peacemaker"};
int xitemn[7];
xitemn[0] = max(0,Random[BackpackExtra](-1,3));
xitemn[1] = max(0,Random[BackpackExtra](-1,3));
xitemn[2] = max(0,Random[BackpackExtra](-2,1));
xitemn[3] = max(0,Random[BackpackExtra](-2,1));
xitemn[4] = max(0,Random[BackpackExtra](-2,1));
xitemn[5] = max(0,Random[BackpackExtra](-1,1));
xitemn[6] = max(0,Random[BackpackExtra](-2,1));
// random doubling
if ( !Random[BackpackExtra](0,4) ) xitemn[0] *= 2;
if ( !Random[BackpackExtra](0,4) ) xitemn[1] *= 2;
if ( !Random[BackpackExtra](0,9) ) xitemn[2] *= 2;
if ( !Random[BackpackExtra](0,7) ) xitemn[3] *= 2;
if ( !Random[BackpackExtra](0,6) ) xitemn[4] *= 2;
if ( !Random[BackpackExtra](0,5) ) xitemn[5] *= 2;
if ( !Random[BackpackExtra](0,9) ) xitemn[6] *= 2;
for ( int i=0; i<7; i++ )
{
if ( xitemn[i] <= 0 ) continue;
toucher.GiveInventory(xitems[i],xitemn[i]);
}
}
}
Default
{
Tag "$T_BACKPACK";
Inventory.PickupMessage "$I_BACKPACK";
Inventory.RespawnTics 2100;
}
States
{
Spawn:
BPAK A -1;
Stop;
}
}
Class UTranslator : UnrealInventory
{
bool bNewMessage, bNotNewMessage;
string NewMessage, Hint;
Array<String> OldMessages, OldHints;
override void Travelled()
{
Super.Travelled();
NewMessage = Hint = "";
OldMessages.Clear();
OldHints.Clear();
}
override bool Use( bool pickup )
{
if ( pickup ) return false;
if ( Owner.player == players[consoleplayer] )
Menu.SetMenu('TranslatorMenu');
bNewMessage = bNotNewmessage = false;
return false;
}
void AddMessage( String msg, String hnt )
{
int found = -1;
for ( int i=0; i<OldMessages.Size(); i++ )
{
if ( OldMessages[i] != msg ) continue;
found = i;
}
if ( found != -1 )
{
OldMessages.Delete(found);
OldHints.Delete(found);
}
if ( (NewMessage != msg) && (NewMessage.Length() > 0) )
{
OldMessages.Push(NewMessage);
OldHints.Push(Hint);
}
NewMessage = msg;
Hint = hnt;
}
Default
{
Tag "$T_TRANSLATOR";
Inventory.PickupMessage "$I_TRANSLATOR";
Inventory.Icon "I_Tran";
Inventory.MaxAmount 1;
}
States
{
Spawn:
TRNS A -1;
Stop;
}
}
// To be placed by mappers
// Uses two UDMF-settable user strings
// AMBUSH: only triggered by script activation, not touch
Class TranslatorEvent : Actor
{
String user_message, user_hint;
Array<Actor> Touchers;
Default
{
Radius 40;
Height 80;
+SPECIAL;
+NOGRAVITY;
+INVISIBLE;
}
virtual void TriggerMessage( Actor who )
{
if ( special1 > gametic ) return;
let Translator = UTranslator(who.FindInventory("UTranslator"));
if ( !Translator ) return;
special1 = gametic+8;
if ( who.CheckLocalView() )
{
if ( special2 ) Console.Printf(StringTable.Localize("$TR_MSG"));
else Console.Printf(StringTable.Localize("$TR_NEWMSG"));
S_Sound("translator/event",CHAN_VOICE|CHAN_UI);
}
Translator.AddMessage(user_message,user_hint);
if ( special2 ) Translator.bNotNewMessage = true;
else Translator.bNewMessage = true;
special2 = 1;
}
// GZDoom doesn't have Touch/UnTouch like UE1 so I have to improvise
override void Tick()
{
Super.Tick();
for ( int i=0; i<Touchers.Size(); i++ )
{
bool deletthis = false;
if ( Touchers[i].pos.x+Touchers[i].radius < pos.x-radius ) deletthis = true;
if ( Touchers[i].pos.x-Touchers[i].radius > pos.x+radius ) deletthis = true;
if ( Touchers[i].pos.y+Touchers[i].radius < pos.y-radius ) deletthis = true;
if ( Touchers[i].pos.y-Touchers[i].radius > pos.y+radius ) deletthis = true;
if ( Touchers[i].pos.z+Touchers[i].height < pos.z ) deletthis = true;
if ( Touchers[i].pos.z > pos.z+height ) deletthis = true;
if ( !deletthis ) continue;
Touchers.Delete(i);
i--;
}
}
override void Touch( Actor toucher )
{
if ( bAMBUSH || !toucher || !toucher.player ) return;
for ( int i=0; i<Touchers.Size(); i++ ) if ( Touchers[i] == toucher ) return;
Touchers.Push(toucher);
TriggerMessage(toucher);
}
override void Activate( Actor activator )
{
if ( !activator || !activator.player ) return;
TriggerMessage(activator);
}
States
{
Spawn:
SMSG A -1;
Stop;
}
}
Class VoiceBox : UnrealInventory
{
Actor box;
Default
{
Tag "$T_VOICEBOX";
Inventory.PickupMessage "$I_VOICEBOX";
Inventory.Icon "I_VoiceB";
Inventory.MaxAmount 3;
UnrealInventory.Charge 600;
}
override void DoEffect()
{
Super.DoEffect();
if ( !bActive ) return;
if ( box ) Charge = box.ReactionTime;
else
{
Charge = DefaultCharge;
bActive = false;
Amount--;
if ( Amount <= 0 ) DepleteOrDestroy();
}
}
override bool Use( bool pickup )
{
if ( pickup || bActive ) return false;
Vector3 x, y, z;
[x, y, z] = dt_CoordUtil.GetAxes(Owner.pitch,Owner.angle,Owner.roll);
Vector3 origin = Owner.Vec2OffsetZ(0,0,Owner.player.viewz);
origin = level.Vec3Offset(origin,x*20.-z*8.);
box = Spawn("VoiceBoxActive",origin);
box.ReactionTime = Charge;
box.vel = x*9.;
box.vel.z += 1.;
box.target = Owner;
box.angle = Owner.angle;
box.pitch = Owner.pitch;
bActive = true;
return false;
}
States
{
Spawn:
VBOX A -1;
Stop;
}
}
Class VoiceBoxHitbox : Actor
{
Default
{
Tag "$T_VOICEBOX";
Radius 6;
Height 9;
Health 60;
+SHOOTABLE;
+NOGRAVITY;
+NOCLIP;
+DONTSPLASH;
+NOBLOOD;
+NOTELEPORT;
}
override void Tick()
{
Super.Tick();
if ( !master )
{
Destroy();
return;
}
SetOrigin(master.pos,true);
}
override void Die( Actor source, Actor inflictor, int dmgflags, Name MeansOfDeath )
{
if ( master ) master.ReactionTime = 0;
Super.Die(source,inflictor,dmgflags,MeansOfDeath);
}
}
Class VoiceBoxActive : Actor
{
Actor b;
double desiredangle, anglevel, oldangle;
override void PostBeginPlay()
{
Super.PostBeginPlay();
b = Spawn("VoiceBoxHitbox",pos);
b.master = self;
desiredangle = FRandom[Junk](0,360);
anglevel = FRandom[Junk](5,15);
}
action void A_VoiceBoxPlay()
{
static const String BattleSounds[] =
{
"automag/shot",
"utrl/explode",
"automag/shot",
"automag/shot",
"flak/altfire",
"utrl/explode",
"flak/fire",
"shock/fire",
"stinger/hit2",
"automag/shot"
};
if ( invoker.b ) invoker.b.A_AlertMonsters(0,AMF_TARGETEMITTER);
for ( int i=0; i<10; i++ ) if ( !Random[Voicebox](0,30) ) A_PlaySound(BattleSounds[i],CHAN_AUTO,FRandom[Voicebox](0.5,1.0));
}
override void Tick()
{
oldangle = angle;
Super.Tick();
if ( deltaangle(angle,desiredangle) ~== 0 ) angle = desiredangle;
else angle += clamp(deltaangle(angle,desiredangle),-anglevel,anglevel);
}
Default
{
Radius 6;
Height 6;
PROJECTILE;
-NOGRAVITY;
+SKYEXPLODE;
+MOVEWITHSECTOR;
+CANBOUNCEWATER;
+BOUNCEAUTOOFF;
+BOUNCEAUTOOFFFLOORONLY;
+USEBOUNCESTATE;
+INTERPOLATEANGLES;
BounceType "Hexen";
BounceFactor 0.5;
BounceSound "transloc/bounce";
WallBounceFactor 0.5;
Gravity 0.35;
ReactionTime 200;
}
States
{
Spawn:
VBOX A -1;
Stop;
Bounce:
VBOX A 0 { angle = invoker.oldangle; }
Goto Spawn;
Death:
VBOX A 30
{
invoker.anglevel *= 0;
A_PlaySound("voice/activate");
}
VBOX ABCDEFGHIJ 1
{
A_VoiceBoxPlay();
ReactionTime--;
return A_JumpIf(ReactionTime<=0,"Death2");
}
Goto Death+1;
Death2:
VBOX A 0
{
if ( invoker.b ) invoker.b.Destroy();
A_PlaySound("flare/explode",CHAN_VOICE);
A_NoGravity();
A_Stop();
A_SetRenderStyle(1.,STYLE_Add);
SetZ(pos.z+16);
Spawn("FlareXLight",pos);
bMOVEWITHSECTOR = false;
bFORCEXYBILLBOARD = true;
A_SetScale(FRandomPick[ExploS](-1.5,1.5),FRandomPick[ExploS](-1.5,1.5));
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));
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();
}
Default
{
Inventory.MaxAmount 1;
UnrealInventory.Charge 100;
+INVENTORY.UNDROPPABLE;
+INVENTORY.UNTOSSABLE;
}
}
Class LightFlare : BetaFlare
{
Default
{
Tag "$T_LFLARES";
Inventory.Icon "I_FlarBL";
}
}
Class DarkFlare : BetaFlare
{
Default
{
Tag "$T_DFLARES";
Inventory.Icon "I_FlarBD";
}
}
Class BetaFlareThrown
{
}
Class LightFlareThrown : BetaFlareThrown
{
}
Class DarkFlareThrown : BetaFlareThrown
{
}
Class Dampener : UnrealInventory
{
static bool Active( Actor Owner )
{
let d = Dampener(Owner.FindInventory("Dampener"));
if ( d && d.bActive ) return true;
return false;
}
override bool Use( bool pickup )
{
if ( pickup ) return false;
bActive = !bActive;
Owner.A_PlaySound(bActive?"dampener/on":"dampener/off",CHAN_ITEM);
return false;
}
override void DoEffect()
{
Super.DoEffect();
if ( !bActive ) return;
if ( DrainCharge(1) )
{
Owner.A_PlaySound("dampener/off",CHAN_ITEM);
if ( Owner.CheckLocalView() ) Console.Printf(StringTable.Localize("$D_DAMPENER"));
if ( Amount <= 0 ) DepleteOrDestroy();
}
}
Default
{
Tag "$T_DAMPENER";
Inventory.PickupMessage "$I_DAMPENER";
Inventory.Icon "I_Dampen";
Inventory.MaxAmount 3;
UnrealInventory.Charge 1000;
}
States
{
Spawn:
DAMP A 8 A_CheckProximity(1,"PlayerPawn",80,1,CPXF_ANCESTOR|CPXF_CHECKSIGHT);
Wait;
DAMP ABC 8;
DAMP DEFGHIJ 3;
DAMP D 0 A_CheckProximity(1,"PlayerPawn",80,0,CPXF_ANCESTOR|CPXF_CHECKSIGHT|CPXF_EXACT);
Goto Spawn+4;
DAMP CBA 8;
Goto Spawn;
}
}
Class Forcefield : UnrealInventory
{
Default
{
Tag "$T_FORCEFIELD";
Inventory.PickupMessage "$I_FORCEFIELD";
Inventory.Icon "I_ForceF";
Inventory.MaxAmount 5;
}
override bool Use( bool pickup )
{
if ( pickup ) return false;
Vector3 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*90.);
origin = level.Vec3Offset(origin,(0,0,-GetDefaultByType("ForceFieldEffect").Height*.5));
if ( !level.IsPointInLevel(origin) )
{
if ( Owner.CheckLocalView() ) Console.Printf(StringTable.Localize("$M_FFNOROOM"));
return false;
}
let a = Spawn("ForceFieldEffect",origin);
if ( !a.TestMobjLocation() )
{
if ( Owner.CheckLocalView() ) Console.Printf(StringTable.Localize("$M_FFNOROOM"));
a.Destroy();
return false;
}
a.target = Owner;
a.angle = Owner.angle;
return true;
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
tracer = Spawn("ForcefieldX",pos);
tracer.angle = angle;
tracer.target = self;
}
States
{
Spawn:
FFPK A -1;
Stop;
}
}
Class ForcefieldX : AsmdAmmoX
{
States
{
Spawn:
FFPK A -1 Bright;
Stop;
}
}
Class ForceFieldLight : DynamicLight
{
double cdown;
Default
{
DynamicLight.Type "Point";
+DYNAMICLIGHT.ATTENUATE;
Args 0,0,0,100;
}
override void Tick()
{
Super.Tick();
if ( !target )
{
Destroy();
return;
}
SetOrigin(target.Vec3Offset(0,0,target.height/2.),true);
if ( isFrozen() ) return;
args[LIGHT_RED] = int(255*cdown);
args[LIGHT_BLUE] = int(255*cdown);
if ( target.bSOLID )
{
cdown = min(1.,cdown+1./27);
return;
}
cdown = max(0.,cdown-1./35);
if ( cdown <= 0. ) Destroy();
}
}
Class ForcefieldEffect : Actor
{
double nvol;
int lct;
Default
{
Tag "$T_FORCEFIELD";
RenderStyle "Add";
+NOGRAVITY;
+NOTELEPORT;
+DONTSPLASH;
+CANNOTPUSH;
+SHOOTABLE;
+SOLID;
+NODAMAGE;
+NOBLOOD;
Mass int.max;
Health int.max;
Radius 15;
Height 45;
}
override int DamageMobj( Actor inflictor, Actor source, int damage, Name mod, int flags, double angle )
{
A_PlaySound("ffield/hit",CHAN_BODY);
return Super.DamageMobj(inflictor,source,damage,mod,flags,angle);
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
A_PlaySound("ffield/on",CHAN_ITEM);
A_PlaySound("ffield/active",CHAN_VOICE,0.6,true);
let tracer = Spawn("ForceFieldLight",pos);
tracer.target = self;
lct = 24;
}
States
{
Spawn:
FFLD ABCDEFGHIJ 3 Bright;
#### # 700 Bright;
#### # 35 Bright
{
A_UnsetShootable();
A_UnsetSolid();
}
FFLD A 1 Bright A_PlaySound("ffield/hit",CHAN_VOICE);
Stop;
}
}
Class UFlashLight1 : DynamicLight
{
int basecolor[3];
Default
{
DynamicLight.Type "Point";
+DynamicLight.SPOT;
+DynamicLight.ATTENUATE;
+DynamicLight.DONTLIGHTSELF;
args 0,0,0,560;
DynamicLight.SpotInnerAngle 3;
DynamicLight.SpotOuterAngle 10;
}
override void Tick()
{
Super.Tick();
if ( !target || !UnrealInventory(master) )
{
Destroy();
return;
}
if ( target.player ) SetOrigin((target.pos.x,target.pos.y,target.player.viewz),true);
else SetOrigin(target.vec3Offset(0,0,target.height*0.75),true);
A_SetAngle(target.angle,SPF_INTERPOLATE);
A_SetPitch(target.pitch,SPF_INTERPOLATE);
args[LIGHT_RED] = int(basecolor[0]*clamp(UnrealInventory(master).charge/1400.,0.,1.));
args[LIGHT_GREEN] = int(basecolor[1]*clamp(UnrealInventory(master).charge/1400.,0.,1.));
args[LIGHT_BLUE] = int(basecolor[2]*clamp(UnrealInventory(master).charge/1400.,0.,1.));
bDORMANT = (target.health <= 0);
if ( Inventory(target) && target.bInvisible ) bDORMANT = true;
// alert monsters hit by the light
if ( GetClass() != "UFlashLight1" ) return;
if ( !bDORMANT && target.player && (target.health > 0) )
{
BlockThingsIterator bt = BlockThingsIterator.Create(target,args[LIGHT_INTENSITY]);
while ( bt.Next() )
{
if ( !bt.Thing || (Distance3D(bt.Thing) > args[LIGHT_INTENSITY]) ) continue;
Vector3 aimdir = (cos(angle)*cos(pitch),sin(angle)*cos(pitch),-sin(pitch));
Vector3 reldir = Vec3To(bt.Thing).unit();
if ( (acos(aimdir dot reldir) < SpotOuterAngle+5) && bt.Thing.CheckSight(target) ) bt.Thing.LastHeard = target;
}
}
}
}
Class UFlashLight2 : UFlashLight1
{
Default
{
args 0,0,0,600;
DynamicLight.SpotInnerAngle 0;
DynamicLight.SpotOuterAngle 20;
}
}
Class UFlashlight : UnrealInventory
{
UFlashLight1 lt[2];
Default
{
Tag "$T_FLASHLIGHT";
Inventory.Icon "I_Flashl";
Inventory.MaxAmount 3;
Inventory.PickupMessage "$I_FLASHLIGHT";
UnrealInventory.Charge 2800;
}
virtual void SetupLights()
{
lt[0].basecolor[0] = 255;
lt[0].basecolor[1] = 240;
lt[0].basecolor[2] = 224;
lt[1].basecolor[0] = 128;
lt[1].basecolor[1] = 120;
lt[1].basecolor[2] = 112;
}
override bool Use( bool pickup )
{
if ( pickup ) return false;
bActive = !bActive;
Owner.A_PlaySound(bActive?"lite/pickup":"lite/off",CHAN_ITEM);
if ( bActive )
{
if ( !lt[0] ) lt[0] = UFlashLight1(Spawn("UFlashLight1",owner.pos));
lt[0].target = owner;
lt[0].master = self;
if ( !lt[1] ) lt[1] = UFlashLight1(Spawn("UFlashLight2",owner.pos));
lt[1].target = owner;
lt[1].master = self;
SetupLights();
}
else
{
if ( lt[0] ) lt[0].Destroy();
if ( lt[1] ) lt[1].Destroy();
}
return false;
}
override void DoEffect()
{
Super.DoEffect();
if ( !bActive ) return;
if ( DrainCharge(1) )
{
Owner.A_PlaySound("lite/off",CHAN_ITEM);
if ( Owner.CheckLocalView() ) Console.Printf(StringTable.Localize("$D_FLASHLIGHT"));
if ( Amount <= 0 ) DepleteOrDestroy();
}
}
override void DetachFromOwner()
{
Super.DetachFromOwner();
if ( lt[0] ) lt[0].Destroy();
if ( lt[1] ) lt[1].Destroy();
}
override void Travelled()
{
Super.Travelled();
if ( !bActive ) return;
if ( !lt[0] ) lt[0] = UFlashLight1(Spawn("UFlashLight1",owner.pos));
lt[0].target = owner;
lt[0].master = self;
if ( !lt[1] ) lt[1] = UFlashLight1(Spawn("UFlashLight2",owner.pos));
lt[1].target = owner;
lt[1].master = self;
SetupLights();
}
States
{
Spawn:
SLIT A -1;
Stop;
}
}
Class USearchlight : UFlashlight
{
Default
{
Tag "$T_SEARCHLIGHT";
Inventory.Icon "I_BigFl";
Inventory.MaxAmount 1;
Inventory.PickupMessage "$I_SEARCHLIGHT";
Inventory.RespawnTics 1050;
UnrealInventory.Charge 70000;
}
override void DoEffect()
{
Super.DoEffect();
if ( !bActive ) return;
if ( DrainCharge(1) )
{
Owner.A_PlaySound("lite/off",CHAN_ITEM);
if ( Owner.CheckLocalView() ) Console.Printf(StringTable.Localize("$D_SEARCHLIGHT"));
if ( Amount <= 0 ) DepleteOrDestroy();
}
}
override void SetupLights()
{
lt[0].basecolor[0] = 255;
lt[0].basecolor[1] = 224;
lt[0].basecolor[2] = 192;
lt[0].SpotInnerAngle = 20;
lt[0].SpotOuterAngle = 35;
lt[0].args[3] = 750;
lt[1].basecolor[0] = 128;
lt[1].basecolor[1] = 112;
lt[1].basecolor[2] = 96;
lt[1].SpotOuterAngle = 50;
lt[1].args[3] = 780;
}
}
Class SentryItem : UnrealInventory
{
Default
{
Tag "$T_SENTRY";
Inventory.Icon "I_Sentry";
Inventory.MaxAmount 1;
Inventory.PickupMessage "$I_SENTRY";
Inventory.RespawnTics 1050;
UnrealInventory.Charge 300;
}
States
{
Spawn:
SENT A -1;
Stop;
}
}
// The "head" of the sentry, attaches to the body
Class MinigunSentry : Actor
{
States
{
Spawn:
SENT A 15;
SENT A 0 A_PlaySound("sentry/raise");
SENR ABCDEFGHIJKLMNO 3;
Goto Idle;
Idle:
SENI A 1;
Wait;
Fire:
SENW ABCDEFGHIJKLMNOPQR 1;
Goto FireLoop;
FireLoop:
SENF ABCDEFGHIJKLMNOPQR 1;
Loop;
FireEnd:
SENU ABCDEFGHIJKLMNOPQR 1;
Goto Idle;
}
}
// The body of the sentry
Class MinigunSentryBase : Actor
{
States
{
Spawn:
SENT A 15;
SENR ABCDEFGHIJKLMNO 3;
Goto Idle;
Idle:
SENI A 1;
Wait;
Fire:
SENW ABCDEFGHIJKLMNOPQR 1;
Goto FireLoop;
FireLoop:
SENF ABCDEFGHIJKLMNOPQR 1;
Loop;
FireEnd:
SENU ABCDEFGHIJKLMNOPQR 1;
Goto Idle;
}
}