stinger_m/zscript/napalm.zsc
Marisa the Magician 1c84fc4e88 4.10 support:
- CORRECTPIXELSTRETCH where needed on models.
 - Replace CoordUtil.GetAxes with quaternion version.
2022-12-05 16:56:52 +01:00

1178 lines
30 KiB
Text

Class FlameAmmo : Ammo
{
Default
{
Tag "$T_FLAMEAMMO";
Inventory.Icon "I_Napalm";
Inventory.PickupMessage "$I_FLAMEAMMO";
Inventory.Amount 60;
Inventory.MaxAmount 450;
Ammo.BackpackAmount 30;
Ammo.BackpackMaxAmount 900;
Ammo.DropAmount 30;
}
override bool TryPickup( in out Actor toucher )
{
if ( !sting_proto ) return false; // not allowed
return Super.TryPickup(toucher);
}
override void Tick()
{
Super.Tick();
if ( sting_proto ) return;
if ( !Owner )
{
let r = Spawn("RocketAmmo",pos,ALLOW_REPLACE);
r.spawnangle = spawnangle;
r.spawnpoint = spawnpoint;
r.angle = angle;
r.pitch = pitch;
r.roll = roll;
r.special = special;
r.args[0] = args[0];
r.args[1] = args[1];
r.args[2] = args[2];
r.args[3] = args[3];
r.args[4] = args[4];
r.ChangeTid(tid);
r.SpawnFlags = SpawnFlags&~MTF_SECRET;
r.HandleSpawnFlags();
r.SpawnFlags = SpawnFlags;
r.bCountSecret = SpawnFlags&MTF_SECRET;
r.vel = vel;
r.master = master;
r.target = target;
r.tracer = tracer;
r.bDropped = bDropped;
r.bNeverRespawn = bNeverRespawn;
}
else Owner.RemoveInventory(self);
Destroy();
}
States
{
Spawn:
FLMA A -1;
Stop;
}
}
Class OnFireLight : DynamicLight
{
OnFire of;
override void Tick()
{
Super.Tick();
if ( !of || !of.victim )
{
Destroy();
return;
}
Args[0] = clamp(of.Amount*4,0,255);
Args[1] = clamp(of.Amount*2,0,128);
Args[3] = int(max(of.victim.radius,of.victim.height)*2.+20+clamp(of.amount/5,0,80));
SetOrigin(of.Victim.Vec3Offset(0,0,of.Victim.Height/2),true);
}
}
Class OnFire : Thinker
{
Actor victim, instigator, lite;
int amount, cnt, delay;
bool forcespread;
double oangle;
override void Tick()
{
if ( !victim )
{
Destroy();
return;
}
if ( victim.waterlevel > 0 )
{
if ( lite ) lite.Destroy();
amount -= int(victim.waterlevel**2);
}
if ( victim.Health <= 0 ) amount = min(amount,100);
if ( !(victim is 'UNapalm') )
{
if ( !(level.maptime%3) )
amount--;
if ( victim.player ) amount -= int(abs(actor.deltaangle(victim.angle,oangle))/30);
oangle = victim.angle;
}
if ( (victim is 'UNapalm') && victim.InStateSequence(victim.CurState,victim.FindState("XDeath")) )
amount = min(amount-3,100);
if ( amount < -30 )
{
Destroy();
return;
}
if ( cnt > 0 ) cnt--;
else
{
cnt = 10;
if ( victim.bSHOOTABLE && (victim.Health > 0) && (amount > 0) )
victim.DamageMobj(instigator.FindInventory("UFlamethrower"),instigator,clamp(int(amount*(victim.bBOSS?0.05:0.15)),1,20),'Fire',DMG_THRUSTLESS);
if ( !victim )
{
Destroy();
return;
}
}
if ( delay > 0 ) delay--;
if ( level.maptime%5 ) return;
int numpt = clamp(int(Random[FlameT](2,4)*amount*0.02),1,4);
double mult = max(victim.radius,victim.height)/30.;
numpt = int(clamp(numpt*mult**.5,1,5));
for ( int i=0; i<numpt; i++ )
{
Vector3 pos = victim.Vec3Offset(FRandom[FlameT](-victim.radius,victim.radius)*0.8,FRandom[FlameT](-victim.radius,victim.radius)*0.8,FRandom[FlameT](victim.height*0.2,victim.height*0.8));
double ang = FRandom[FlameT](0,360);
double pt = FRandom[FlameT](-90,90);
if ( amount > 0 )
{
let c = victim.Spawn("UFlameTrail",pos);
c.scale *= max(.3,mult*0.5);
c.vel = victim.vel*0.5+dt_Utility.Vec3FromAngle(ang,pt)*FRandom[FlameT](.5,2.)*c.scale.x;
}
let s = victim.Spawn("UTSmoke",pos);
s.scale *= max(1.,1.6*mult);
s.alpha *= min(amount+30,100)*0.02;
s.vel = victim.vel*0.5+dt_Utility.Vec3FromAngle(ang,pt)*FRandom[FlameT](.2,.6)*s.scale.x;
}
if ( (!sting_flametspread && !forcespread) || (amount <= 0) ) return;
// spread to nearby actors
let bt = BlockThingsIterator.Create(victim);
while ( bt.Next() )
{
let t = bt.Thing;
if ( !t || !t.bSHOOTABLE || (t.Health <= 0) || (t == victim) || ((t == instigator) && (delay > 0)) || (victim.Distance3D(t) > victim.radius+t.radius+20) || !victim.CheckSight(t) ) continue;
int amt = max(1,amount/10);
if ( IsOnFire(t) ) amt = min(5,amt);
Apply(t,instigator,amt);
}
}
static OnFire Apply( Actor victim, Actor instigator, int amount, bool forcespread = false, int delay = 0 )
{
if ( amount <= 0 ) return null;
if ( victim is 'ShredCorpseHitbox' ) return null;
let ti = ThinkerIterator.Create("OnFire",STAT_USER);
OnFire t;
while ( t = OnFire(ti.Next()) )
{
if ( t.victim != victim ) continue;
if ( instigator ) t.instigator = instigator;
t.amount = min(500,t.amount+amount);
t.cnt = min(t.cnt,5);
return t;
}
t = new("ONFire");
t.ChangeStatNum(STAT_USER);
t.victim = victim;
t.instigator = instigator;
t.amount = min(500,amount);
t.cnt = 1;
// for napalm gel
t.forcespread = forcespread;
t.delay = delay;
t.lite = Actor.Spawn("OnFireLight",victim.pos);
OnFireLight(t.lite).of = t;
t.oangle = victim.angle;
return t;
}
static bool IsOnFire( Actor victim )
{
let ti = ThinkerIterator.Create("OnFire",STAT_USER);
OnFire t;
while ( t = OnFire(ti.Next()) )
{
if ( t.victim != victim ) continue;
return (t.amount>0);
}
return false;
}
}
Class UFlameLight : PaletteLight
{
Default
{
Tag "Ampd";
Args 0,0,0,40;
ReactionTime 40;
}
override void Tick()
{
Super.Tick();
Args[0] /= 10;
Args[1] /= 10;
Args[2] /= 10;
Args[3] += 3;
if ( !target || (target.waterlevel > 0) )
{
Destroy();
return;
}
SetOrigin(target.pos,true);
}
}
Class UFlame : Actor
{
override void PostBeginPlay()
{
Super.PostBeginPlay();
if ( (waterlevel <= 0) && Random[FlameT](0,1) )
{
let l = Spawn("UFlameLight",pos);
l.target = self;
}
Scale.x *= RandomPick[ExploS](-1,1);
Scale.y *= RandomPick[ExploS](-1,1);
}
action void A_Flame()
{
if ( waterlevel > 0 )
vel *= 0.9;
else
{
vel *= 0.98;
vel.z += 0.2*abs(scale.x);
}
if ( waterlevel > 0 )
{
let s = Spawn("UTSmoke",pos);
s.vel = (FRandom[FlameT](-0.2,0.2),FRandom[FlameT](-0.2,0.2),FRandom[FlameT](-0.2,0.2));
s.vel += vel*0.3;
s.alpha *= alpha*4;
s.scale *= 0.5+abs(scale.x)*(.5+GetAge()/6.);
Destroy();
return;
}
if ( !Random[FlameT](0,int(40*(default.alpha-alpha))) )
{
let s = Spawn("UTSmoke",pos);
s.vel = (FRandom[FlameT](-0.2,0.2),FRandom[FlameT](-0.2,0.2),FRandom[FlameT](-0.2,0.2));
s.vel += vel*0.3;
s.alpha *= alpha*4;
s.scale *= 0.5+abs(scale.x)*(.5+GetAge()/6.);
}
if ( bAMBUSH ) return;
if ( Random[FlameT](0,int(20*((default.alpha+0.1)-alpha))) ) return;
double rad = 60+120*int(0.2-alpha);
let bt = BlockThingsIterator.Create(self,rad);
while ( bt.Next() )
{
let t = bt.Thing;
if ( !t || !t.bSHOOTABLE || (t.Health <= 0) || (t == tracer) || ((t == master) && (GetAge() < 6)) || (Distance3D(t) > rad+t.radius) ) continue;
int amt = max(1,int(alpha*10));
OnFire.Apply(t,master,amt);
}
}
override bool CanCollideWith( Actor Other, bool passive )
{
return false;
}
Default
{
RenderStyle "Add";
Speed 20;
Radius 4;
Height 4;
Alpha 0.2;
Scale 0.1;
+NOBLOCKMAP;
+NOGRAVITY;
+NOFRICTION;
+SLIDESONWALLS;
+ACTIVATEPCROSS;
+ACTIVATEIMPACT;
+NOTELEPORT;
+FORCEXYBILLBOARD;
+DROPOFF;
+NOBLOCKMONST;
+DONTSPLASH;
+MBFBOUNCER;
}
States
{
Spawn:
SEXP AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTT 1 Bright
{
A_Flame();
A_SetScale(scale.x*1.01+0.04);
A_FadeOut(0.005);
}
Stop;
}
}
Class UFlameTrail : UFlame
{
override void PostBeginPlay()
{
Actor.PostBeginPlay();
Scale.x *= RandomPick[ExploS](-1,1);
Scale.y *= RandomPick[ExploS](-1,1);
}
Default
{
Speed 2;
Alpha 0.3;
Scale 0.6;
+AMBUSH;
}
States
{
Spawn:
SEXP AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTT 1 Bright
{
A_Flame();
A_SetScale(scale.x*0.98);
A_FadeOut(0.01);
vel.z += 0.1;
}
Stop;
}
}
Class UNapalm : Actor
{
enum EHitType
{
HIT_NONE,
HIT_WALL,
HIT_CEILING,
HIT_FLOOR
};
int hittype;
int deadtimer, dttimer;
Line atline;
int atside;
int atpart;
int atplane;
Sector atsector;
double atz;
double rollvel, pitchvel, yawvel;
Vector3 normal;
Actor atbridge;
bool onbridge;
Vector3 atbridgeofs;
OnFire myfire;
Actor lasthit;
override void PostBeginPlay()
{
Super.PostBeginPlay();
vel.z += 3;
deadtimer = 105;
rollvel = FRandom[FlameT](10,30)*RandomPick[FlameT](-1,1);
pitchvel = FRandom[FlameT](10,30)*RandomPick[FlameT](-1,1);
yawvel = FRandom[FlameT](10,30)*RandomPick[FlameT](-1,1);
if ( waterlevel <= 0 ) myfire = OnFire.Apply(self,target,int(120*scale.x),true,6);
}
override void FallAndSink( double grav, double oldfloorz )
{
if ( bNOGRAVITY || (waterlevel < 1) )
{
Super.FallAndSink(grav,oldfloorz);
return;
}
vel *= .99;
if ( pos.z > floorz ) vel.z += grav*.01; // floats in water
}
override void Tick()
{
Super.Tick();
if ( isFrozen() ) return;
if ( !bNOGRAVITY )
{
roll += rollvel;
pitch += pitchvel;
pitch += yawvel;
if ( waterlevel > 0 )
{
rollvel *= 0.98;
pitchvel *= 0.98;
yawvel *= 0.98;
}
}
if ( !Random[FlameT](0,2) )
{
Vector3 pvel = (FRandom[FlameT](-1,1),FRandom[FlameT](-1,1),FRandom[FlameT](-1,1)).unit()*FRandom[FlameT](0.2,0.4);
let s = Spawn("UTSmoke",pos+normal);
s.vel = pvel+vel*0.25+normal*0.5;
s.scale *= scale.x;
s.alpha *= scale.x;
if ( InStateSequence(CurState,FindState("XDeath")) )
s.scale *= (12-frame)/12.;
}
if ( onbridge ) // attempt to follow the movement of the bridge (if it's moving)
{
if ( atbridge )
{
if ( !Warp(atbridge,atbridgeofs.x,atbridgeofs.y,atbridgeofs.z,0,WARPF_ABSOLUTEOFFSET|WARPF_USECALLERANGLE|WARPF_COPYINTERPOLATION) )
deadtimer = min(deadtimer,0);
}
else deadtimer = 0;
}
if ( atline ) // attempt to follow the movement of the line
{
if ( atpart == 1 )
{
if ( atline.flags&Line.ML_DONTPEGTOP ) SetOrigin(Vec2OffsetZ(0,0,atz+atline.sidedef[atside].sector.GetPlaneTexZ(1)),true);
else SetOrigin(Vec2OffsetZ(0,0,atz+atline.sidedef[atside?0:1].sector.GetPlaneTexZ(1)),true);
}
else if ( atpart == -1 )
{
if ( atline.flags&Line.ML_DONTPEGBOTTOM ) SetOrigin(Vec2OffsetZ(0,0,atz+atline.sidedef[atside].sector.GetPlaneTexZ(0)),true);
else SetOrigin(Vec2OffsetZ(0,0,atz+atline.sidedef[atside?0:1].sector.GetPlaneTexZ(0)),true);
}
else if ( atline.flags&Line.ML_DONTPEGBOTTOM ) SetOrigin(Vec2OffsetZ(0,0,atz+atline.sidedef[atside].sector.GetPlaneTexZ(0)),true);
else SetOrigin(Vec2OffsetZ(0,0,atz+atline.sidedef[atside].sector.GetPlaneTexZ(1)),true);
if ( (pos.z > ceilingz) || (pos.z < floorz) ) deadtimer = min(deadtimer,0);
}
else if ( atsector ) // attempt to follow the movement of the plane
{
SetOrigin(Vec2OffsetZ(0,0,atz+atsector.GetPlaneTexZ(atplane)),true);
if ( ceilingz-floorz <= 2 ) deadtimer = min(deadtimer,0);
}
if ( (deadtimer-- <= 0) && !InStateSequence(CurState,FindState("XDeath")) )
SetStateLabel("XDeath");
}
// align self to what surface was hit
// TODO handle plane collision within the very border between two
// sectors (most noticeable with moving 3d floors)
virtual void AlignSelf()
{
F3DFloor ff;
bINTERPOLATEANGLES = false;
bHITOWNER = true;
A_NoGravity();
A_Stop();
A_SetSize(0.1,0);
if ( tracer && tracer.bSHOOTABLE ) OnFire.Apply(tracer,target,myfire?myfire.Amount:10);
if ( tracer && tracer.bACTLIKEBRIDGE )
{
atbridge = tracer;
onbridge = true;
if ( (pos.x+radius) <= (atbridge.pos.x-atbridge.radius) )
{
// west side
normal = (-1,0,0);
SetOrigin((atbridge.pos.x-atbridge.radius,pos.y,pos.z),false);
atbridgeofs = pos-atbridge.pos;
angle = 180;
pitch = 0;
roll = 180; // otherwise it slides upwards (UT changes roll like this too)
if ( waterlevel > 0 ) hittype = HIT_FLOOR;
else hittype = HIT_WALL;
}
else if ( (pos.x-radius) >= (atbridge.pos.x+atbridge.radius) )
{
// east side
normal = (1,0,0);
SetOrigin((atbridge.pos.x+atbridge.radius,pos.y,pos.z),false);
atbridgeofs = pos-atbridge.pos;
angle = 0;
pitch = 0;
roll = 180; // otherwise it slides upwards (UT changes roll like this too)
if ( waterlevel > 0 ) hittype = HIT_FLOOR;
else hittype = HIT_WALL;
}
else if ( (pos.y+radius) <= (atbridge.pos.y-atbridge.radius) )
{
// north side
normal = (0,-1,0);
SetOrigin((pos.x,atbridge.pos.y-atbridge.radius,pos.z),false);
atbridgeofs = pos-atbridge.pos;
angle = 270;
pitch = 0;
roll = 180; // otherwise it slides upwards (UT changes roll like this too)
if ( waterlevel > 0 ) hittype = HIT_FLOOR;
else hittype = HIT_WALL;
}
else if ( (pos.y-radius) >= (atbridge.pos.y+atbridge.radius) )
{
// south side
normal = (0,1,0);
SetOrigin((pos.x,atbridge.pos.y+atbridge.radius,pos.z),false);
atbridgeofs = pos-atbridge.pos;
angle = 90;
pitch = 0;
roll = 180; // otherwise it slides upwards (UT changes roll like this too)
if ( waterlevel > 0 ) hittype = HIT_FLOOR;
else hittype = HIT_WALL;
}
else if ( pos.z >= (atbridge.pos.z+atbridge.height) )
{
// top of actor
normal = (0,0,1);
SetOrigin((pos.x,pos.y,atbridge.pos.z+atbridge.height),false);
atbridgeofs = pos-atbridge.pos;
pitch = -90;
angle = 0;
roll = FRandom[FlameT](0,360);
hittype = HIT_FLOOR;
}
else if ( (pos.z+height) <= atbridge.pos.z )
{
// bottom of actor
normal = (0,0,-1);
SetOrigin((pos.x,pos.y,atbridge.pos.z),false);
pitch = 90;
angle = 0;
roll = FRandom[FlameT](0,360);
if ( waterlevel > 0 ) hittype = HIT_FLOOR;
else hittype = HIT_CEILING;
}
else
{
// inside of actor, just shove to the top or bottom based on our Z velocity
if ( vel.z <= 0 )
{
normal = (0,0,1);
SetOrigin((pos.x,pos.y,atbridge.pos.z+atbridge.height),false);
atbridgeofs = pos-atbridge.pos;
pitch = -90;
angle = 0;
roll = FRandom[FlameT](0,360);
hittype = HIT_FLOOR;
}
else
{
normal = (0,0,-1);
SetOrigin((pos.x,pos.y,atbridge.pos.z),false);
pitch = 90;
angle = 0;
roll = FRandom[FlameT](0,360);
if ( waterlevel > 0 ) hittype = HIT_FLOOR;
else hittype = HIT_CEILING;
}
}
}
else if ( BlockingFloor )
{
// find closest 3d floor for its normal
for ( int i=0; i<BlockingFloor.Get3DFloorCount(); i++ )
{
if ( !(BlockingFloor.Get3DFloor(i).top.ZAtPoint(pos.xy) ~== floorz) ) continue;
ff = BlockingFloor.Get3DFLoor(i);
break;
}
if ( ff )
{
normal = -ff.top.Normal;
atsector = ff.model;
atplane = 1;
}
else
{
normal = BlockingFloor.floorplane.Normal;
atsector = BlockingFloor;
atplane = 0;
}
pitch = asin(-normal.z);
angle = atan2(normal.y,normal.x);
roll = FRandom[FlameT](0,360);
SetOrigin((pos.x,pos.y,floorz)+normal*0.5,false);
atz = pos.z-atsector.GetPlaneTexZ(atplane);
hittype = HIT_FLOOR;
}
else if ( BlockingCeiling )
{
// find closest 3d floor for its normal
for ( int i=0; i<BlockingCeiling.Get3DFloorCount(); i++ )
{
if ( !(BlockingCeiling.Get3DFloor(i).bottom.ZAtPoint(pos.xy) ~== ceilingz) ) continue;
ff = BlockingCeiling.Get3DFloor(i);
break;
}
if ( ff )
{
normal = -ff.bottom.Normal;
atsector = ff.model;
atplane = 0;
}
else
{
normal = BlockingCeiling.ceilingplane.Normal;
atsector = BlockingCeiling;
atplane = 1;
}
pitch = asin(-normal.z);
angle = atan2(normal.y,normal.x);
roll = FRandom[FlameT](0,360);
SetOrigin((pos.x,pos.y,ceilingz)+normal*0.5,false);
atz = pos.z-atsector.GetPlaneTexZ(atplane);
if ( waterlevel > 0 ) hittype = HIT_FLOOR;
else if ( normal dot (0,0,-1) > 0.7 )
hittype = HIT_CEILING;
else hittype = HIT_FLOOR;
}
else if ( BlockingLine )
{
atline = BlockingLine;
normal = (-BlockingLine.delta.y,BlockingLine.delta.x,0).unit();
atside = 1;
if ( !BlockingLine.sidedef[1] || (CurSector == BlockingLine.frontsector) )
{
atside = 0;
normal *= -1;
}
Vector3 orig = (BlockingLine.v1.p.x,BlockingLine.v1.p.y,0);
Vector3 onwall = pos-(normal dot (pos-orig))*normal;
SetOrigin(onwall+normal*0.5,false);
// attempt to guess line part (upper/mid/lower)
if ( !atline.sidedef[1] ) atpart = 0; // mid
else if ( atline.sidedef[atside?0:1].sector.ceilingplane.ZAtPoint(pos.xy) < pos.z ) atpart = 1; // upper
else if ( atline.sidedef[atside?0:1].sector.floorplane.ZAtPoint(pos.xy) > (pos.z+height) ) atpart = -1; // lower
else
{
atpart = 0;
// check if we're touching a 3d floor line
Sector backsector = atline.sidedef[atside?0:1].sector;
for ( int i=0; i<backsector.Get3DFloorCount(); i++ )
{
if ( backsector.Get3DFloor(i).bottom.ZAtPoint(pos.xy) > (pos.z+height) ) continue;
if ( backsector.Get3DFloor(i).top.ZAtPoint(pos.xy) < pos.z ) continue;
ff = backsector.Get3DFloor(i);
break;
}
// attach to it
if ( ff )
{
atline = ff.master;
atside = 0;
}
}
if ( atpart == 1 )
{
if ( atline.flags&Line.ML_DONTPEGTOP ) atz = pos.z-atline.sidedef[atside].sector.GetPlaneTexZ(1);
else atz = pos.z-atline.sidedef[atside?0:1].sector.GetPlaneTexZ(1);
}
else if ( atpart == -1 )
{
if ( atline.flags&Line.ML_DONTPEGBOTTOM ) atz = pos.z-atline.sidedef[atside].sector.GetPlaneTexZ(0);
else atz = pos.z-atline.sidedef[atside?0:1].sector.GetPlaneTexZ(0);
}
else if ( atline.flags&Line.ML_DONTPEGBOTTOM ) atz = pos.z-atline.sidedef[atside].sector.GetPlaneTexZ(0);
else atz = pos.z-atline.sidedef[atside].sector.GetPlaneTexZ(1);
angle = atan2(normal.y,normal.x);
pitch = 0;
roll = 180; // otherwise it slides upwards (UT changes roll like this too)
if ( waterlevel > 0 ) hittype = HIT_FLOOR;
else hittype = HIT_WALL;
}
A_StartSound("napalm/hit",volume:min(1.,scale.x));
}
action void A_DropDrip()
{
let d = Spawn("UNapalmSplash",pos+invoker.normal*2*scale.x);
d.target = target;
d.angle = angle;
d.pitch = pitch;
d.roll = roll;
d.master = self;
d.scale = scale*0.5;
d.vel.z -= 6;
}
override int DoSpecialDamage( Actor target, int damage, Name damagetype )
{
if ( target != lasthit )
{
OnFire.Apply(target,self.target,myfire?myfire.Amount:1);
lasthit = target;
}
return damage;
}
Default
{
Obituary "$O_FLAMETHROWER";
DamageFunction 1;
DamageType 'Fire';
Radius 4;
Height 4;
Speed 15;
Gravity 0.35;
PROJECTILE;
-NOGRAVITY;
+SKYEXPLODE;
+EXPLODEONWATER;
+FORCERADIUSDMG;
+FORCEXYBILLBOARD;
+MOVEWITHSECTOR;
+NODAMAGETHRUST;
+HITTRACER;
+INTERPOLATEANGLES;
+NOFRICTION;
+RIPPER;
+BLOODLESSIMPACT;
}
States
{
Spawn:
GELF ABCDEFGHIJKLM 1;
Loop;
Death:
GELH A 1 AlignSelf();
GELH BCDEFGHIJ 1;
GELH J 1 A_SetTics(Random[FlameT](10,30));
GELH J -1
{
invoker.deadtimer = Random[FlameT](250,300);
if ( invoker.hittype == HIT_WALL ) return ResolveState("Slide");
else if ( invoker.hittype == HIT_CEILING ) return ResolveState("Drip");
return ResolveState(null);
}
Stop;
Drip:
GELD ABCDEFGH 4;
GELD I 4 A_DropDrip();
GELD JKLM 4;
GELH J -1;
Stop;
Slide:
GELS ABCDEF 3;
GELS G -1;
Stop;
XDeath:
GELX ABCDEFGHIJKL 4;
Stop;
}
}
Class UNapalmGlob : UNapalm
{
int numsplash;
void SpawnSplash()
{
Vector3 ofs = dt_Utility.Vec3FromAngle(angle,pitch);
for ( int i=0; i<2; i++ )
{
if ( numsplash-- <= 0 ) return;
Vector3 dir = (ofs+(FRandom[FlameT](-.8,.8),FRandom[FlameT](-.8,.8),FRandom[FlameT](-.8,.8))).unit();
A_SetScale(scale.x-0.05);
let d = Spawn("UNapalmSplash",pos+ofs*4);
d.target = target;
d.master = self;
d.scale *= FRandom[FlameT](0.5,0.7);
d.angle = atan2(dir.y,dir.x);
d.pitch = -asin(dir.z);
d.vel = dt_Utility.Vec3FromAngle(d.angle,d.pitch)*d.speed*FRandom[FlameT](0.3,0.5)*scale.x;
d.vel.z -= 2;
}
}
override void AlignSelf()
{
Super.AlignSelf();
if ( Scale.x > 1 ) numsplash = int(8*Scale.x)-1;
SpawnSplash();
}
override void OnDestroy()
{
Vector3 ofs = dt_Utility.Vec3FromAngle(angle,pitch);
while ( numsplash > 0 ) SpawnSplash();
}
override void Tick()
{
Super.Tick();
if ( isFrozen() ) return;
SpawnSplash();
}
}
Class UNapalmSplash : UNapalm
{
override void AlignSelf()
{
Super.AlignSelf();
if ( hittype == HIT_CEILING ) hittype = HIT_FLOOR;
}
}
// MODELDEF fuckery
Class UFlamethrowerAlt : Actor abstract {}
Class UFlamethrower : UnrealWeapon
{
bool bCharging;
double ChargeSize, Count;
bool bOldSkin;
override bool TryPickup( in out Actor toucher )
{
if ( !sting_proto ) return false; // not allowed
return Super.TryPickup(toucher);
}
override void Tick()
{
Super.Tick();
if ( sting_altflamet )
{
if ( !bOldSkin ) A_ChangeModel("UFlamethrowerAlt");
bOldSkin = true;
}
else
{
if ( bOldSkin ) A_ChangeModel("UFlamethrower");
bOldSkin = false;
}
if ( sting_proto ) return;
if ( !Owner )
{
let r = Spawn("PlasmaRifle",pos,ALLOW_REPLACE);
r.spawnangle = spawnangle;
r.spawnpoint = spawnpoint;
r.angle = angle;
r.pitch = pitch;
r.roll = roll;
r.special = special;
r.args[0] = args[0];
r.args[1] = args[1];
r.args[2] = args[2];
r.args[3] = args[3];
r.args[4] = args[4];
r.ChangeTid(tid);
r.SpawnFlags = SpawnFlags&~MTF_SECRET;
r.HandleSpawnFlags();
r.SpawnFlags = SpawnFlags;
r.bCountSecret = SpawnFlags&MTF_SECRET;
r.vel = vel;
r.master = master;
r.target = target;
r.tracer = tracer;
r.bDropped = bDropped;
r.bNeverRespawn = bNeverRespawn;
}
else Owner.RemoveInventory(self);
Destroy();
}
override int, int, bool, bool GetClipAmount()
{
return bCharging?min(5,int(chargesize+0.1)):-1, -1, false, false;
}
override void DetachFromOwner()
{
A_StartSound("flamet/down",CHAN_WEAPONMISC);
Super.DetachFromOwner();
}
override void DoEffect()
{
Super.DoEffect();
if ( Owner.player.ReadyWeapon != self ) return;
let psp = Owner.player.FindPSprite(-2);
if ( psp ) psp.alpha = (Owner.waterlevel>2)?.0:1.;
}
action void A_FireFlame()
{
if ( Health <= 0 )
{
player.SetPSprite(-9999,ResolveState("Null"));
return;
}
let weap = Weapon(invoker);
if ( (weap.Ammo1.Amount <= 0) || !(player.cmd.buttons&BT_ATTACK) )
{
player.SetPSprite(PSP_WEAPON,invoker.FindState("Release"));
return;
}
invoker.count += 10./TICRATE;
while ( invoker.count > 1. )
{
weap.DepleteAmmo(weap.bAltFire,true,1);
invoker.count -= 1.;
}
invoker.FireEffect();
A_AlertMonsters();
UTMainHandler.DoFlash(self,Color(32,255,128,0),1);
Vector3 x, y, z, x2, y2, z2;
[x, y, z] = dt_Utility.GetAxes(angle,pitch,roll);
Vector3 origin = level.Vec3Offset(Vec2OffsetZ(0,0,player.viewz),15*x-2.3*y-2.7*z);
for ( int i=0; i<2; i++ )
{
double a = FRandom[FlameT](0,360), s = FRandom[FlameT](0,.05);
[x2, y2, z2] = dt_Utility.GetAxes(angle,BulletSlope(),roll);
Vector3 dir = dt_Utility.ConeSpread(x2,y2,z2,a,s);
Actor p = Spawn("UFlame",origin);
if ( p.waterlevel > 0 )
{
p.Destroy();
s = FRandom[FlameT](0,.12);
dir = dt_Utility.ConeSpread(x2,y2,z2,a,s);
p = Spawn("UNapalmSplash",origin);
p.scale *= 0.2;
p.angle = atan2(dir.y,dir.x);
p.pitch = asin(-dir.z);
p.vel = vel*.5+dt_Utility.Vec3FromAngle(p.angle,p.pitch)*p.speed*FRandom[FlameT](0.3,0.6);
p.vel.z -= 3;
p.target = self;
continue;
}
p.angle = atan2(dir.y,dir.x);
p.pitch = asin(-dir.z);
p.vel = vel*.1+dt_Utility.Vec3FromAngle(p.angle,p.pitch)*p.speed*FRandom[FlameT](0.8,1.4);
p.master = self;
}
}
action void A_BeginFlame()
{
let weap = Weapon(invoker);
weap.DepleteAmmo(weap.bAltFire,true,1);
invoker.count = 0;
invoker.special1 = 0;
A_StartSound("flamet/fire",CHAN_WEAPON,CHANF_LOOPING,Dampener.Active(self)?.1:1.);
A_Overlay(-9999,"Dummy2");
}
action void A_BeginCharge()
{
let weap = Weapon(invoker);
weap.DepleteAmmo(weap.bAltFire,true,1);
invoker.count = invoker.chargesize = 0;
A_StartSound("flamet/charge",CHAN_WEAPON,volume:Dampener.Active(self)?.1:1.);
A_Overlay(-9999,"Dummy3");
}
action void A_ChargeUp()
{
if ( Health <= 0 )
{
invoker.bCharging = false;
player.SetPSprite(-9999,ResolveState("Null"));
return;
}
let weap = Weapon(invoker);
if ( (invoker.chargesize < 4.9) && (weap.Ammo1.Amount > 0) )
{
invoker.count += 40./TICRATE;
while ( invoker.count > 1. )
{
invoker.chargesize += 0.05;
weap.DepleteAmmo(weap.bAltFire,true,1);
invoker.count -= 1.;
}
}
double ox = FRandom[Flamet](-1,1)*invoker.chargesize*0.3;
double oy = FRandom[Flamet](-1,1)*invoker.chargesize*0.3;
A_WeaponOffset(ox,32+oy);
A_OverlayOffset(-2,-ox,-oy);
A_SoundVolume(CHAN_WEAPON,Dampener.Active(self)?.1:1.);
if ( (weap.Ammo1.Amount <= 0) || (invoker.chargesize >= 4.9) || !(player.cmd.buttons&BT_ALTATTACK) )
player.SetPSPrite(PSP_WEAPON,invoker.FindState("AltRelease"));
}
action void A_FireNapalm()
{
Weapon weap = Weapon(invoker);
if ( !weap ) return;
invoker.FireEffect();
A_AlertMonsters();
UTMainHandler.DoFlash(self,Color(32,255,128,0),1);
A_Overlay(-9999,"Null");
A_WeaponOffset(0,32);
A_OverlayOffset(-2,0,0);
invoker.bCharging = false;
A_StartSound("flamet/altfire",CHAN_WEAPON,volume:Dampener.Active(self)?.1:1.,pitch:max(.5,1.2-invoker.chargesize/10.));
if ( self is 'UTPlayer' ) UTPlayer(self).PlayAttacking3();
A_QuakeEx(1+int(0.5*invoker.chargesize),1+int(0.5*invoker.chargesize),1+int(0.5*invoker.chargesize),5+int(1.2*invoker.chargesize),0,64,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:0.05+0.01*invoker.chargesize);
Vector3 x, y, z;
[x, y, z] = dt_Utility.GetAxes(angle,pitch,roll);
Vector3 origin = level.Vec3Offset(Vec2OffsetZ(0,0,player.viewz),10*x-2.3*y-2.7*z);
Actor p = Spawn("UNapalmGlob",origin);
p.A_SetScale(0.5+invoker.chargesize/3.5);
p.angle = angle;
p.pitch = BulletSlope();
p.vel = dt_Utility.Vec3FromAngle(p.angle,p.pitch)*p.speed;
p.target = self;
for ( int i=0; i<12; i++ )
{
let s = Spawn("UTViewSpark",origin);
UTViewSpark(s).ofs = (10,-2.3,-2.7);
s.target = self;
UTViewSpark(s).vvel += (FRandom[FlameT](0.8,1.6),FRandom[FlameT](-0.5,0.5),FRandom[FlameT](-0.5,0.5));
}
invoker.chargesize = 0;
}
Default
{
Obituary "$O_FLAMETHROWER";
Tag "$T_FLAMETHROWER";
Inventory.PickupMessage "$I_FLAMETHROWER";
Weapon.UpSound "flamet/select";
Weapon.SlotNumber 6;
Weapon.SelectionOrder 300;
Weapon.SlotPriority 0.9;
Weapon.AmmoType "FlameAmmo";
Weapon.AmmoUse 1;
Weapon.AmmoType2 "FlameAmmo";
Weapon.AmmoUse2 1;
Weapon.AmmoGive 100;
UTWeapon.DropAmmo 50;
+NOEXTREMEDEATH;
}
States
{
Spawn:
FLMP A -1;
Stop;
FLMP B -1;
Stop;
Select:
FLMS A 1 A_Raise(int.max);
Wait;
Ready:
FLMS ABCDEF 2 A_WeaponReady(WRF_NOFIRE);
FLMS G 0
{
A_StartSound("flamet/idle",CHAN_WEAPONMISC,CHANF_LOOPING,Dampener.Active(self)?.1:1.);
A_Overlay(-2,"FlameReady");
A_OverlayFlags(-2,PSPF_RENDERSTYLE|PSPF_FORCESTYLE|PSPF_ALPHA|PSPF_FORCEALPHA,true);
A_OverlayRenderStyle(-2,STYLE_Add);
}
FLMS GHIJ 2 A_WeaponReady(WRF_NOFIRE);
Goto Idle;
Dummy:
TNT1 A 1
{
A_CheckReload();
A_WeaponReady();
}
Wait;
Idle:
FLMI A 0 A_Overlay(-9999,"Dummy");
FLMI ABCDEFG 12 A_SoundVolume(CHAN_WEAPONMISC,Dampener.Active(self)?.1:1.);
FLMI A 0 A_Jump(20,"Twiddle");
Goto Idle+1;
Twiddle:
#### # 2
{
A_SoundVolume(CHAN_WEAPONMISC,Dampener.Active(self)?.1:1.);
player.SetPSprite(-2,ResolveState("FlameTwiddle"));
}
FLMT ABCDEFGHIJKLMNOPQRSTUVWXYZ[\] 2 A_SoundVolume(CHAN_WEAPONMISC,Dampener.Active(self)?.1:1.);
FLT2 ABCDEF 2 A_SoundVolume(CHAN_WEAPONMISC,Dampener.Active(self)?.1:1.);
Goto Idle+1;
Fire:
#### # 2
{
A_Overlay(-9999,"Null");
player.SetPSprite(-2,ResolveState("FlameFire"));
}
FLMF ABCDE 2;
FLMF F 0 A_BeginFlame();
Goto Hold;
Dummy2:
TNT1 A 1 A_FireFlame();
Wait;
Hold:
FLMF FGHIJK 2
{
A_SoundVolume(CHAN_WEAPON,Dampener.Active(self)?.1:1.);
A_SoundVolume(CHAN_WEAPONMISC,Dampener.Active(self)?.02:.2);
}
Loop;
Release:
#### # 2
{
A_Overlay(-9999,"Null");
player.SetPSprite(-2,ResolveState("FlameRelease"));
A_StartSound("flamet/fireend",CHAN_WEAPON,volume:Dampener.Active(self)?.1:1.);
A_SoundVolume(CHAN_WEAPONMISC,Dampener.Active(self)?.1:1.);
A_ClearRefire();
}
FLMF MNOP 2;
Goto Idle;
AltFire:
#### # 2
{
A_Overlay(-9999,"Null");
player.SetPSprite(-2,ResolveState("FlameAltFire"));
invoker.bCharging = true;
}
FLMA ABCDE 2;
FLMA F 0 A_BeginCharge();
Goto AltHold;
Dummy3:
TNT1 A 1 A_ChargeUp();
Wait;
AltHold:
FLMA FIGHLKGJHLGFHJGILHFKGFKHIGKGHL 10;
Loop;
AltRelease:
FLMA M 0
{
A_FireNapalm();
player.SetPSprite(-2,ResolveState("FlameAltRelease"));
}
FLMA MNOPQRSTUVWX 2;
FLMA Y 0
{
if ( invoker.CheckAmmo(1,false,true) )
A_Refire("GotoAltHold");
}
FLMA Y 0 A_ClearRefire();
FLMA YZ[\] 2;
FLA2 ABCD 2;
Goto Idle;
GotoAltHold:
FLMA Y 0
{
invoker.bCharging = true;
A_BeginCharge();
player.SetPSprite(-2,ResolveState("FlameAltHold"));
}
Goto AltHold;
Deselect:
#### # 1
{
A_Overlay(-9999,"Null");
A_StartSound("flamet/down",CHAN_WEAPONMISC,volume:Dampener.Active(self)?.1:1.);
player.SetPSprite(-2,ResolveState("FlameDeselect"));
}
FLMD ABCDEFHIJ 1;
FLMD J 1 A_Lower(int.max);
Wait;
FlameReady:
FLFS ABCD 2 Bright;
Goto FlameIdle;
FlameIdle:
FLFI ABCDEFG 12 Bright;
Loop;
FlameTwiddle:
#### # 2 Bright;
FLFT ABCDEFGHIJKLMNOPQRSTUVWXYZ[\] 2 Bright;
FFT2 ABCDEF 2 Bright;
Goto FlameIdle;
FlameFire:
#### # 1 Bright;
FLFF ABCDE 2 Bright;
Goto FlameHold;
FlameHold:
FLFF FGHIJK 2 Bright;
Loop;
FlameRelease:
#### # 2 Bright;
FLFF MNOP 2 Bright;
Goto FlameIdle;
FlameAltFire:
#### # 2 Bright;
FLFA ABCDE 2 Bright;
Goto FlameAltHold;
FlameAltHold:
FLFA FIGHLKGJHLGFHJGILHFKGFKHIGKGHL 10 Bright;
Loop;
FlameAltRelease:
FLFA MNOPQRSTUVWXYZ[\] 2 Bright;
FFA2 ABCD 2 Bright;
Goto FlameIdle;
FlameDeselect:
#### # 1 Bright;
FLFD ABCD 2 Bright;
Stop;
}
}