Class FlakAmmo : Ammo { Default { Tag "$T_FLAKAMMO"; Inventory.PickupMessage ""; Inventory.Amount 10; Inventory.MaxAmount 50; Ammo.BackpackAmount 5; Ammo.BackpackMaxAmount 100; Ammo.DropAmount 5; } override String PickupMessage() { if ( PickupMsg.Length() > 0 ) return Super.PickupMessage(); return String.Format("%s%d%s",StringTable.Localize("$I_FLAKAMMOL"),Amount,StringTable.Localize("$I_FLAKAMMOR")); } States { Spawn: FAMO A -1; Stop; } } Class FlakAmmo2 : FlakAmmo { Default { Tag "$T_FLAKAMMO2"; Inventory.PickupMessage "$I_FLAKAMMO2"; Inventory.Amount 1; Ammo.DropAmount 1; +INVENTORY.IGNORESKILL; } States { Spawn: FSLG A -1; Stop; } } Class ChunkLight : DynamicLight { Default { DynamicLight.Type "Point"; Args 255,224,128,8; } override void Tick() { Super.Tick(); if ( !target ) { Destroy(); return; } SetOrigin(target.pos,true); if ( isFrozen() ) return; args[LIGHT_RED] = int(255*(10-target.frame)*0.1); args[LIGHT_GREEN] = int(224*(10-target.frame)*0.1); args[LIGHT_BLUE] = int(128*(10-target.frame)*0.1); } } Class ChunkTrail : Actor { Default { RenderStyle "Add"; Radius 0.1; Height 0; +NOBLOCKMAP; +NOGRAVITY; +DONTSPLASH; +FORCEXYBILLBOARD; +NOTELEPORT; Scale 0.2; } override void PostBeginPlay() { Super.PostBeginPlay(); let l = Spawn("ChunkLight",pos); l.target = self; } override void Tick() { Super.Tick(); if ( isFrozen() ) return; if ( InStateSequence(CurState,FindState("Death")) ) return; if ( !target ) { int dist = FindState("Spawn").DistanceTo(CurState); SetState(FindState("Death")+dist); return; } SetOrigin(target.Vec3Offset(0,0,speed),true); } States { Spawn: FGLO ABCDEFGHIJK 3 Bright; Stop; Death: FGLO ABCDEFGHIJK 1 Bright; Stop; } } Class FlakAccumulator : Thinker { Actor victim, inflictor, source; Array amounts; int total; Name type; override void Tick() { Super.Tick(); // so many damn safeguards in this if ( !victim ) { Destroy(); return; } int gibhealth = victim.GetGibHealth(); // おまえはもう死んでいる if ( victim.health-total <= gibhealth ) { // safeguard for inflictors that have somehow ceased to exist, which apparently STILL CAN HAPPEN if ( inflictor ) inflictor.bEXTREMEDEATH = true; else type = 'Extreme'; } // make sure accumulation isn't reentrant if ( inflictor && (inflictor is 'FlakChunk') ) inflictor.bAMBUSH = true; // 何? for ( int i=0; i 0 ) { rollvel *= 0.98; pitchvel *= 0.98; yawvel *= 0.98; if ( trail ) trail.Destroy(); } lifetics++; if ( lifetics > 3 ) { lifetics = 0; if ( frame < 11 ) frame++; } lifetime += lifespeed; if ( (waterlevel <= 0) && (frame < 10) && !(lifetics%2) ) { let s = Spawn("UTSmoke",pos); s.vel = (FRandom[Flak](-0.1,0.1),FRandom[Flak](-0.1,0.1),FRandom[Flak](-0.1,0.1)); s.alpha = scale.x/0.5; s.SetShade("AAAAAA"); } else if ( waterlevel > 0 ) { let s = Spawn("UTBubble",pos); s.vel = (FRandom[Flak](-0.1,0.1),FRandom[Flak](-0.1,0.1),FRandom[Flak](-0.1,0.1)); s.scale *= scale.x*0.5; } if ( trail ) trail.alpha = max(0,11-frame)/11.; if ( InStateSequence(CurState,FindState("Death")) ) return; roll += rollvel; pitch += pitchvel; angle += pitchvel; } void A_HandleBounce() { // chunks in vanilla have a special variation on the standard reflect formula that causes them to bounce differently when hitting a surface head-on // (0.5 to 0.8 reduction perpendicular to the surface normal, to be specific) Vector3 HitNormal = -vel.unit(); F3DFloor ff; if ( BlockingFloor ) { // find closest 3d floor for its normal for ( int i=0; i= (BlockingMobj.pos.x+BlockingMobj.radius) ) HitNormal = (1,0,0); else if ( (pos.y+radius) <= (BlockingMobj.pos.y-BlockingMobj.radius) ) HitNormal = (0,-1,0); else if ( (pos.y-radius) >= (BlockingMobj.pos.y+BlockingMobj.radius) ) HitNormal = (0,1,0); else if ( pos.z >= (BlockingMobj.pos.z+BlockingMobj.height) ) HitNormal = (0,0,1); else if ( (pos.z+height) <= BlockingMobj.pos.z ) HitNormal = (0,0,-1); } // undo the bounce, we need to hook in our own vel = oldvel; // re-do the bounce with our formula vel = 0.8*((vel dot HitNormal)*HitNormal*(-1.8+FRandom[Flak](0.0,0.8))+vel); bHITOWNER = true; int numpt = Random[Flak](2,3); if ( (frame < 10) && Random[Flak](0,1) ) { for ( int i=0; i victim.GetGibHealth()) ) { victim.SpawnBlood(pos,AngleTo(victim),damage); A_StartSound("flak/meat",volume:0.3); A_AlertMonsters(); } return 1; } return -1; } States { Spawn: FCH1 A 0; Goto Idle; FCH2 A 0; Goto Idle; FCH3 A 0; Goto Idle; FCH4 A 0; Goto Idle; Idle: #### # -1; Stop; Bounce: #### # 0 A_HandleBounce(); Goto Idle; Death: #### # 0 { bMOVEWITHSECTOR = true; A_SetTics(Random[Flak](30,50)); } #### # 1 { A_SetScale(scale.x-0.002); if ( scale.x <= 0.0 ) Destroy(); } Wait; Crash: TNT1 A 0 { let l = Spawn("BulletImpact",pos); Vector3 dir; if ( tracer ) dir = level.Vec3Diff(pos,tracer.Vec3Offset(0,0,tracer.height/2)).unit(); else dir = vel.unit(); l.angle = atan2(dir.y,dir.x); l.pitch = asin(-dir.z); A_StartSound("flak/hit",volume:0.3); A_AlertMonsters(); } XDeath: TNT1 A 2; // must exist for at least two tics so the accumulator gets the right inflictor Stop; Dummy: FCH1 ABCDEFGHIJKL -1; FCH2 ABCDEFGHIJKL -1; FCH3 ABCDEFGHIJKL -1; FCH4 ABCDEFGHIJKL -1; Stop; } } Class SlugSmoke : Actor { double lifetime, lifespeed; Default { Radius 0.1; Height 0; +NOBLOCKMAP; +NOGRAVITY; +DONTSPLASH; +NOTELEPORT; } override void PostBeginPlay() { Super.PostBeginPlay(); lifetime = 0; lifespeed = FRandom[Flak](0.004,0.008); } override void Tick() { Super.Tick(); if ( isFrozen() ) return; lifetime += lifespeed; let s = Spawn("UTSmoke",pos); s.vel = (FRandom[Flak](-0.5,0.5),FRandom[Flak](-0.5,0.5),FRandom[Flak](-0.5,0.5)); s.vel.z += 2.; s.alpha = scale.x; s.SetShade("AAAAAA"); scale.x = max(0,1-lifetime); if ( scale.x <= 0 ) Destroy(); } States { Spawn: TNT1 A -1; Stop; } } Class SlugLight : PaletteLight { Default { Args 0,0,0,80; ReactionTime 20; } } Class FlakSlug : Actor { Mixin UTMissileFix; Default { Obituary "$O_FLAKCANNON"; DamageType 'FlakDeath'; Radius 2; Height 2; Gravity 0.35; Speed 20; PROJECTILE; -NOGRAVITY; +SKYEXPLODE; +EXPLODEONWATER; +HITTRACER; +FORCERADIUSDMG; +NODAMAGETHRUST; } override void PostBeginPlay() { Super.PostBeginPlay(); vel.z += 3; } action void A_FlakExplode() { bForceXYBillboard = true; A_SetRenderStyle(1.0,STYLE_Add); A_SprayDecal("RocketBlast",50); A_NoGravity(); A_SetScale(1.2); UTMainHandler.DoBlast(self,120,75000); A_Explode(70,120); A_QuakeEx(4,4,4,8,0,170,"",QF_RELATIVE|QF_SCALEDOWN,falloff:120,rollIntensity:0.2); A_StartSound("flak/explode",CHAN_VOICE); A_AlertMonsters(); if ( !Tracer ) Spawn("SlugSmoke",pos); Spawn("SlugLight",pos); Vector3 x, y, z; double a, s; [x, y, z] = dt_CoordUtil.GetAxes(pitch,angle,roll); Actor p; Vector3 spawnofs; if ( BlockingMobj ) spawnofs = level.Vec3Diff(pos,BlockingMobj.Vec3Offset(0,0,BlockingMobj.height/2)).unit()*8; else if ( BlockingFloor ) spawnofs = BlockingFloor.floorplane.Normal*8; else if ( BlockingCeiling ) spawnofs = BlockingCeiling.ceilingplane.Normal*8; else if ( BlockingLine ) { spawnofs = (-BlockingLine.delta.y,BlockingLine.delta.x,0).unit()*8; if ( !BlockingLine.sidedef[1] || (CurSector == BlockingLine.frontsector) ) spawnofs *= -1; } for ( int i=0; i<5; i++ ) { p = Spawn("FlakChunk",Vec3Offset(spawnofs.x,spawnofs.y,spawnofs.z)); p.bHITOWNER = true; a = FRandom[Flak](0,360); s = FRandom[Flak](0,0.1); Vector3 dir = dt_Utility.ConeSpread(x,y,z,a,s); p.angle = atan2(dir.y,dir.x); p.pitch = -asin(dir.z); p.vel = dt_Utility.Vec3FromAngle(p.angle,p.pitch)*(p.speed+FRandom[Flak](-3,3)); p.target = target; } int numpt = Random[Flak](8,12); for ( int i=0; i 2 ) Destroy(); } } Class FlakMag : UTCasing { Default { BounceSound "flak/sbounce"; } override void PostBeginPlay() { Super.PostBeginPlay(); if ( special1 ) { anglevel *= .1; pitchvel *= .1; } } States { Death: PCAS A -1 { pitch = RandomPick[Junk](-90,90); angle = FRandom[Junk](0,360); } Stop; } } Class FlakCannon : UTWeapon { transient ui Canvas AmmoLed; ui TextureID LedFont; override void RenderOverlay( RenderEvent e ) { Vector2 fnt[] = { (0,1), (21,1), (42,1), (63,1), (84,1), (105,1), (126,1), (147,1), (168,1), (189,1) }; if ( !AmmoLed ) AmmoLed = TexMan.GetCanvas("FlakALed"); if ( !LedFont ) LedFont = TexMan.CheckForTexture("models/LEDFont2.png",TexMan.Type_Any); AmmoLed.Clear(0,0,128,64,"Black"); int dg3 = (Ammo1.Amount/100)%10; int dg2 = (Ammo1.Amount/10)%10; int dg1 = Ammo1.Amount%10; AmmoLed.DrawTexture(LedFont,false,34,14,DTA_SrcX,fnt[dg3].x,DTA_SrcY,fnt[dg3].y,DTA_SrcWidth,20,DTA_SrcHeight,35,DTA_DestWidth,20,DTA_DestHeight,35,DTA_Color,0xFFFF0000); AmmoLed.DrawTexture(LedFont,false,54,14,DTA_SrcX,fnt[dg2].x,DTA_SrcY,fnt[dg2].y,DTA_SrcWidth,20,DTA_SrcHeight,35,DTA_DestWidth,20,DTA_DestHeight,35,DTA_Color,0xFFFF0000); AmmoLed.DrawTexture(LedFont,false,74,14,DTA_SrcX,fnt[dg1].x,DTA_SrcY,fnt[dg1].y,DTA_SrcWidth,20,DTA_SrcHeight,35,DTA_DestWidth,20,DTA_DestHeight,35,DTA_Color,0xFFFF0000); } action void A_Loading( bool first = false ) { if ( first ) A_StartSound("flak/load",CHAN_WEAPONMISC,CHANF_OVERLAP); else A_StartSound("flak/reload",CHAN_WEAPONMISC,CHANF_OVERLAP); } action void A_FireChunks() { Weapon weap = Weapon(invoker); if ( !weap ) return; if ( weap.Ammo1.Amount <= 0 ) return; if ( !weap.DepleteAmmo(weap.bAltFire,true,1) ) return; A_StartSound("flak/fire",CHAN_WEAPON); invoker.FireEffect(); UTMainHandler.DoFlash(self,Color(160,255,96,0),1); A_AlertMonsters(); A_QuakeEx(1,1,1,3,0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:0.05); Vector3 x, y, z; double a, s; [x, y, z] = dt_CoordUtil.GetAxes(pitch,angle,roll); Vector3 origin = level.Vec3Offset(Vec2OffsetZ(0,0,player.viewz),10*x+4*y-3*z); A_Overlay(-2,"MuzzleFlash"); A_OverlayFlags(-2,PSPF_RENDERSTYLE|PSPF_FORCESTYLE,true); A_OverlayRenderstyle(-2,STYLE_Add); [x, y, z] = dt_CoordUtil.GetAxes(BulletSlope(),angle,roll); Vector3 offsets[8]; // vanilla adds these to each chunk offsets[0] = (0,0,0); offsets[1] = -z; offsets[2] = 2*y+z; offsets[3] = -y; offsets[4] = 2*y-z; offsets[5] = (0,0,0); offsets[6] = y-z; offsets[7] = 2*y+z; Actor p; for ( int i=0; i<8; i++ ) { p = Spawn("FlakChunk",level.Vec3Offset(origin,offsets[i])); a = FRandom[Flak](0,360); s = FRandom[Flak](0,0.1); Vector3 dir = dt_Utility.ConeSpread(x,y,z,a,s); p.angle = atan2(dir.y,dir.x); p.pitch = -asin(dir.z); p.vel = dt_Utility.Vec3FromAngle(p.angle,p.pitch)*(p.speed+FRandom[Flak](-3,3)); p.target = self; } int numpt = Random[Flak](20,30); for ( int i=0; i 0 ) A_Loading(); } FLKL BCEFGIJKMNO 1; Goto Idle; AltLoading: FLKL A 2 { A_CheckReload(); if ( invoker.Ammo1.Amount > 0 ) A_Loading(); } FLKL BCEFGIJKMNO 2; Goto Idle; Idle: FLKI A 1 A_WeaponReady(); Wait; Fire: FLKF A 1 A_FireChunks(); FLKF BCDEFGHI 1; FLKF J 4 { Vector3 x, y, z, origin; [x,y,z] = dt_CoordUtil.GetAxes(pitch,angle,roll); origin = level.Vec3Offset(Vec2OffsetZ(0,0,player.viewz),x*4.+y*3.-z*8.); let c = Spawn("FlakMag",origin); c.angle = angle; c.pitch = pitch; c.vel = vel*.5+x*FRandom[Junk](-.8,.1)+y*FRandom[Junk](-.2,.2)-z*FRandom[Junk](1,2); } Goto Loading; AltFire: FLKA A 1 A_FireSlug(); FLKA BCDEFGHIJK 1; FLKA K 4; Goto AltLoading; Select: FLKS A 1 A_Raise(int.max); Wait; Deselect: FLKD ABCDEFGHIJ 1; FLKD J 1 A_Lower(int.max); Wait; MuzzleFlash: FMUZ A 3 Bright { let l = Spawn("FlakLight",pos); l.target = self; } Stop; } }