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; } 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 0 ) { let c = victim.Spawn("UFlameTrail",pos); c.scale *= max(.3,mult*0.5); c.vel = victim.vel*0.5+(cos(ang)*cos(pt),sin(ang)*cos(pt),-sin(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+(cos(ang)*cos(pt),sin(ang)*cos(pt),-sin(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 ) { 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*2; 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+60*int(0.4-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*5)); OnFire.Apply(t,master,amt); } } Default { RenderStyle "Add"; Speed 20; Radius 4; Height 4; Alpha 0.4; Scale 0.1; +NOBLOCKMAP; +NOGRAVITY; +NOFRICTION; +SLIDESONWALLS; +ACTIVATEPCROSS; +ACTIVATEIMPACT; +NOTELEPORT; +FORCEXYBILLBOARD; +DROPOFF; +NOBLOCKMONST; //+THRUIMPASSABLE; // gonna have to implement this } States { Spawn: SEXP AABBCCDDEEFFGGHHIIJJKKLLMMNNOOPPQQRRSSTT 1 Bright { A_Flame(); A_SetScale(scale.x*1.01+0.04); A_FadeOut(0.01); } 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 Tick() { Super.Tick(); if ( isFrozen() ) return; if ( !bNOGRAVITY ) { roll += rollvel; pitch += pitchvel; pitch += yawvel; if ( waterlevel > 0 ) { vel.xy *= 0.98; 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 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 (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_PlaySound("napalm/hit",CHAN_BODY,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; override void AlignSelf() { Super.AlignSelf(); if ( Scale.x > 1 ) numsplash = int(8*Scale.x)-1; } override void OnDestroy() { Vector3 ofs = (cos(angle)*cos(pitch),sin(angle)*cos(pitch),-sin(pitch)); while ( numsplash > 0 ) { 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 = (cos(d.angle)*cos(d.pitch),sin(d.angle)*cos(d.pitch),-sin(d.pitch))*d.speed*FRandom[FlameT](0.3,0.5)*scale.x; d.vel.z -= 2; } } } override void Tick() { Super.Tick(); if ( isFrozen() ) return; Vector3 ofs = (cos(angle)*cos(pitch),sin(angle)*cos(pitch),-sin(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 = (cos(d.angle)*cos(d.pitch),sin(d.angle)*cos(d.pitch),-sin(d.pitch))*d.speed*FRandom[FlameT](0.3,0.5)*scale.x; d.vel.z -= 2; } } } Class UNapalmSplash : UNapalm { override void AlignSelf() { Super.AlignSelf(); if ( hittype == HIT_CEILING ) hittype = HIT_FLOOR; } } Class UFlamethrower : UnrealWeapon { bool bCharging; double ChargeSize, Count; override int, int, bool, bool GetClipAmount() { return bCharging?min(5,int(chargesize+0.1)):-1, -1, false, false; } override void DetachFromOwner() { Owner.A_StopSound(CHAN_6); A_PlaySound("flamet/down",CHAN_6); Super.DetachFromOwner(); } override void OwnerDied() { if ( Owner.player && (Owner.player.ReadyWeapon == self) ) Owner.A_StopSound(CHAN_6); Super.OwnerDied(); } 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() { 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_CoordUtil.GetAxes(pitch,angle,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_CoordUtil.GetAxes(BulletSlope(),angle,roll); Vector3 dir = (x2+y2*cos(a)*s+z2*sin(a)*s).unit(); Actor p = Spawn("UFlame",origin); if ( p.waterlevel > 0 ) { p.Destroy(); s = FRandom[FlameT](0,.12); dir = (x2+y2*cos(a)*s+z2*sin(a)*s).unit(); p = Spawn("UNapalmSplash",origin); p.scale *= 0.2; p.angle = atan2(dir.y,dir.x); p.pitch = asin(-dir.z); p.vel = vel*.5+(cos(p.angle)*cos(p.pitch),sin(p.angle)*cos(p.pitch),-sin(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+(cos(p.angle)*cos(p.pitch),sin(p.angle)*cos(p.pitch),-sin(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_PlaySound("flamet/fire",CHAN_WEAPON,Dampener.Active(self)?.1:1.,true); A_Overlay(-9999,"Dummy2"); } action void A_BeginCharge() { let weap = Weapon(invoker); weap.DepleteAmmo(weap.bAltFire,true,1); invoker.count = invoker.chargesize = 0; A_PlaySound("flamet/charge",CHAN_WEAPON,Dampener.Active(self)?.1:1.); A_Overlay(-9999,"Dummy3"); } action void A_ChargeUp() { 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_PlaySound("flamet/altfire",CHAN_WEAPON,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_CoordUtil.GetAxes(pitch,angle,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); UTMainHandler.DoSwing(self,(FRandom[FlameT](-0.6,-1.3),FRandom[FlameT](-0.9,-0.2)),1+invoker.chargesize*0.3,-0.1,3,SWING_Spring,3,2); p.angle = angle; p.pitch = BulletSlope(); p.vel = (cos(p.angle)*cos(p.pitch),sin(p.angle)*cos(p.pitch),-sin(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_PlaySound("flamet/idle",CHAN_6,Dampener.Active(self)?.1:1.,true); 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_6,Dampener.Active(self)?.1:1.); FLMI A 0 A_Jump(20,"Twiddle"); Goto Idle+1; Twiddle: #### # 2 { A_SoundVolume(CHAN_6,Dampener.Active(self)?.1:1.); player.SetPSprite(-2,ResolveState("FlameTwiddle")); } FLMT ABCDEFGHIJKLMNOPQRSTUVWXYZ[\] 2 A_SoundVolume(CHAN_6,Dampener.Active(self)?.1:1.); FLT2 ABCDEF 2 A_SoundVolume(CHAN_6,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_6,Dampener.Active(self)?.02:.2); } Loop; Release: #### # 2 { A_Overlay(-9999,"Null"); player.SetPSprite(-2,ResolveState("FlameRelease")); A_PlaySound("flamet/fireend",CHAN_WEAPON,Dampener.Active(self)?.1:1.); A_SoundVolume(CHAN_6,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_PlaySound("flamet/down",CHAN_6,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; } }