swwmgz_m/zscript/swwm_cbt.zsc
Marisa Kirisame 7e3e78b209 Fixed item lists having scrollbars when they shouldn't.
Removed Wallbuster reload timer debug cvar (not needed).
Removed CBT easter egg.
Removed PrecacheClasses from ZMAPINFO (no longer needed since 4.5 as UE1 models load much faster).
2020-12-11 22:51:47 +01:00

2133 lines
63 KiB
Text

// Blackmann Arms "Wallbuster" Heavy Armor Perforator Shotgun (planned for unreleased Total Destruction UT mod as the "Armor Perforator")
// Slot 3, replaces Super Shotgun, Ethereal Crossbow, Frost Shards
Class WallbusterReloadMenu : GenericMenu
{
transient Font TewiFont, MPlusFont, MiniwiFont, k6x8Font;
TextureID MainWindow, AmmoIcon[4];
int sel0;
Array<int> queue;
int AmmoSets[4];
bool isrclick, ismclick;
transient CVar pauseme, lang;
// if playing in Japanese, returns an alternate font of the same height
// Tewi -> MPlus
// Miniwi -> k6x8
Font LangFont( Font req )
{
if ( !lang ) lang = CVar.GetCVar('language',players[consoleplayer]);
if ( lang.GetString() ~== "jp" )
{
if ( req == MiniwiFont ) return k6x8Font;
return MPlusFont;
}
return req;
}
override void Init( Menu parent )
{
Super.Init(parent);
if ( (gamestate != GS_LEVEL) || (players[consoleplayer].Health <= 0) || !(players[consoleplayer].ReadyWeapon is 'Wallbuster') )
{
EventHandler.SendNetworkEvent("swwmcbt.",consoleplayer);
Close();
return;
}
TewiFont = Font.GetFont('TewiShaded');
MPlusFont = Font.GetFont('MPlusShaded');
MiniwiFont = Font.GetFont('MiniwiShaded');
k6x8Font = Font.GetFont('k6x8Shaded');
MainWindow = TexMan.CheckForTexture("graphics/HUD/WallbusterMenu.png",TexMan.Type_Any);
AmmoIcon[0] = TexMan.CheckForTexture("graphics/HUD/RedShell.png",TexMan.Type_Any);
AmmoIcon[1] = TexMan.CheckForTexture("graphics/HUD/GreenShell.png",TexMan.Type_Any);
AmmoIcon[2] = TexMan.CheckForTexture("graphics/HUD/BlueShell.png",TexMan.Type_Any);
AmmoIcon[3] = TexMan.CheckForTexture("graphics/HUD/PurpleShell.png",TexMan.Type_Any);
MenuSound("menu/demotab");
queue.Clear();
sel0 = CVar.GetCVar('swwm_cbtlast',players[consoleplayer]).GetInt();;
}
override void Ticker()
{
Super.Ticker();
if ( !pauseme ) pauseme = CVar.GetCVar('swwm_cbtpause',players[consoleplayer]);
if ( pauseme.GetBool() ) menuactive = Menu.On;
else menuactive = Menu.OnNoPause;
if ( (players[consoleplayer].Health > 0) && (players[consoleplayer].ReadyWeapon is 'Wallbuster') && (gamestate == GS_LEVEL) ) return;
MenuEvent(MKEY_BACK,false);
}
private bool IsDone()
{
static const Class<Ammo> types[] = {"RedShell","GreenShell","BlueShell","PurpleShell"};
if ( queue.Size() >= 25 ) return true;
for ( int i=0; i<4; i++ )
{
if ( (players[consoleplayer].mo.CountInv(types[i])-AmmoSets[i]) > 0 )
return false;
}
return true;
}
private bool PushAmmo( bool autoshift = false )
{
static const Class<Ammo> types[] = {"RedShell","GreenShell","BlueShell","PurpleShell"};
if ( queue.Size() >= 25 ) return true;
if ( (players[consoleplayer].mo.CountInv(types[sel0])-AmmoSets[sel0]) <= 0 )
{
if ( autoshift )
{
// switch to next available ammo
for ( int i=0; i<4; i++ )
{
int idx = (sel0+i)%4;
if ( (players[consoleplayer].mo.CountInv(types[idx])-AmmoSets[idx]) > 0 )
{
sel0 = idx;
CVar.GetCVar('swwm_cbtlast',players[consoleplayer]).SetInt(sel0);
return PushAmmo(true);
}
}
}
MenuSound("menu/noinvuse");
return false;
}
if ( !autoshift ) MenuSound("menu/demosel");
AmmoSets[sel0]++;
queue.Push(sel0);
return true;
}
private void ShuffleAmmo()
{
static const Class<Ammo> types[] = {"RedShell","GreenShell","BlueShell","PurpleShell"};
// there's probably a better way to do this but I'm lazy
Array<Int> candidates;
candidates.Clear();
for ( int i=0; i<4; i++ )
{
if ( (players[consoleplayer].mo.CountInv(types[i])-AmmoSets[i]) <= 0 )
continue;
candidates.Push(i);
}
if ( candidates.Size() <= 0 ) return;
sel0 = candidates[Random[WallbusterMenu](0,candidates.Size()-1)];
CVar.GetCVar('swwm_cbtlast',players[consoleplayer]).SetInt(sel0);
AmmoSets[sel0]++;
queue.Push(sel0);
}
private bool PopAmmo()
{
if ( queue.Size() <= 0 ) return false;
AmmoSets[queue[queue.Size()-1]]--;
queue.Pop();
return true;
}
override bool MenuEvent( int mkey, bool fromcontroller )
{
switch ( mkey )
{
case MKEY_BACK:
queue.Clear();
for ( int i=0; i<4; i++ ) AmmoSets[i] = 0;
MenuSound("menu/democlose");
EventHandler.SendNetworkEvent("swwmcbt.",consoleplayer);
Close();
return true;
case MKEY_ENTER:
if ( queue.Size() <= 0 )
{
while ( queue.Size() < 25 )
{
if ( !PushAmmo(true) )
break;
}
}
String cbt = "swwmcbt.";
for ( int i=0; i<queue.Size(); i++ )
cbt.AppendFormat("%d,",queue[i]);
MenuSound("menu/democlose");
EventHandler.SendNetworkEvent(cbt,consoleplayer);
Close();
return true;
case MKEY_UP:
if ( queue.Size() <= 0 )
{
MenuSound("menu/noinvuse");
return true;
}
PopAmmo();
MenuSound("menu/demoscroll");
return true;
case MKEY_DOWN:
if ( IsDone() )
{
MenuSound("menu/noinvuse");
return true;
}
PushAmmo();
return true;
case MKEY_RIGHT:
MenuSound("menu/demotab");
sel0++;
if ( sel0 > 3 ) sel0 = 0;
CVar.GetCVar('swwm_cbtlast',players[consoleplayer]).SetInt(sel0);
return true;
case MKEY_LEFT:
MenuSound("menu/demotab");
sel0--;
if ( sel0 < 0 ) sel0 = 3;
CVar.GetCVar('swwm_cbtlast',players[consoleplayer]).SetInt(sel0);
return true;
case MKEY_PAGEUP:
if ( queue.Size() <= 0 )
{
MenuSound("menu/noinvuse");
return true;
}
int i = 0;
while ( (queue.Size() > 0) && (i++ < 5) )
{
if ( !PopAmmo() )
break;
}
MenuSound("menu/demoscroll");
return true;
case MKEY_PAGEDOWN:
if ( IsDone() )
{
MenuSound("menu/noinvuse");
return true;
}
int j = 0;
while ( (queue.Size() < 25) && (j++ < 5) )
{
if ( !PushAmmo(true) )
return true;
}
MenuSound("menu/demosel");
return true;
case MKEY_CLEAR:
if ( queue.Size() <= 0 ) MenuSound("menu/noinvuse");
else
{
MenuSound("menu/demoscroll");
queue.Clear();
for ( int i=0; i<4; i++ ) AmmoSets[i] = 0;
}
return true;
}
return Super.MenuEvent(mkey,fromcontroller);
}
override bool OnUiEvent( UIEvent ev )
{
int y;
bool res;
switch ( ev.type )
{
case UIEvent.Type_KeyDown:
if ( ev.keychar == UiEvent.Key_Tab )
{
// shuffle!
queue.Clear();
for ( int i=0; i<4; i++ ) AmmoSets[i] = 0;
bool didsomething = false;
while ( !IsDone() )
{
ShuffleAmmo();
didsomething = true;
}
MenuSound(didsomething?"menu/demosel":"menu/noinvuse");
}
break;
case UIEvent.Type_LButtonDown:
isrclick = false;
ismclick = false;
return Super.OnUIEvent(ev);
break;
case UIEvent.Type_RButtonDown:
isrclick = true;
ismclick = false;
// copy over what base menus do for L click
y = ev.MouseY;
res = MouseEventBack(MOUSE_Click,ev.MouseX,y);
if ( res ) y = -1;
res |= MouseEvent(MOUSE_Click,ev.MouseX,y);
if ( res ) SetCapture(true);
return false;
break;
case UIEvent.Type_MButtonDown:
isrclick = false;
ismclick = true;
// copy over what base menus do for L click
y = ev.MouseY;
res = MouseEventBack(MOUSE_Click,ev.MouseX,y);
if ( res ) y = -1;
res |= MouseEvent(MOUSE_Click,ev.MouseX,y);
if ( res ) SetCapture(true);
return false;
break;
case UIEvent.Type_RButtonUp:
case UIEvent.Type_MButtonUp:
// copy over what base menus do for L release
if ( mMouseCapture )
{
SetCapture(false);
y = ev.MouseY;
res = MouseEventBack(MOUSE_Release,ev.MouseX,y);
if ( res ) y = -1;
res |= MouseEvent(MOUSE_Release,ev.MouseX,y);
}
return false;
break;
}
return Super.OnUIEvent(ev);
}
override void Drawer()
{
static const Class<Ammo> types[] = {"RedShell","GreenShell","BlueShell","PurpleShell"};
Super.Drawer();
double hs = max(min(floor(Screen.GetWidth()/640.),floor(Screen.GetHeight()/400.)),1.);
Vector2 ss = (Screen.GetWidth(),Screen.GetHeight())/hs;
Vector2 origin = (ss.x-132,ss.y-26)/2.;
Screen.DrawTexture(MainWindow,false,origin.x,origin.y,DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true);
int ox = 27, oy = 2;
for ( int i=0; i<4; i++ )
{
Screen.DrawTexture(AmmoIcon[i],false,origin.x+ox,origin.y+oy,DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true,DTA_ColorOverlay,(i==sel0)?Color(0,0,0,0):Color(128,0,0,0));
String astr = String.Format("%3d",players[consoleplayer].mo.CountInv(types[i])-AmmoSets[i]);
Screen.DrawText(TewiFont,Font.CR_FIRE,origin.x+ox-(TewiFont.StringWidth(astr)+1),origin.y+oy-2,astr,DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true,DTA_ColorOverlay,(i==sel0)?Color(0,0,0,0):Color(128,0,0,0));
ox += 33;
}
// pointer (▸)
Screen.DrawChar(TewiFont,Font.CR_GREEN,origin.x+2+33*sel0,origin.y,0x25B8,DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true);
int siz = queue.Size()-1;
ox = 2+siz*5+(siz/5);
oy = 15;
for ( int i=0; i<=siz; i++ )
{
Screen.DrawTexture(AmmoIcon[queue[i]],false,origin.x+ox,origin.y+oy,DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true);
ox -= 5;
if ( !((i+1)%5) ) ox--;
}
// text stuff
String str;
Font fnt;
int boxw, sw;
double x, y;
fnt = LangFont(TewiFont);
str = StringTable.Localize("$SWWM_BUSTERTITLE");
sw = fnt.StringWidth(str);
boxw = sw;
fnt = LangFont(MiniwiFont);
str = "(C)2148 Akari Labs";
sw = fnt.StringWidth(str);
if ( sw > boxw ) boxw = sw;
x = floor((ss.x-boxw)/2.);
y = origin.y-30;
Screen.Dim("Black",.8,int((x-2)*hs),int((y-1)*hs),int((boxw+4)*hs),int(25*hs));
fnt = LangFont(TewiFont);
str = StringTable.Localize("$SWWM_BUSTERTITLE");
sw = fnt.StringWidth(str);
x = floor((ss.x-sw)/2.);
Screen.DrawText(fnt,Font.CR_FIRE,x,y,str,DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true);
y += 14;
fnt = LangFont(MiniwiFont);
str = "(C)2148 Akari Labs";
sw = fnt.StringWidth(str);
x = floor((ss.x-sw)/2.);
Screen.DrawText(fnt,Font.CR_GOLD,x,y,str,DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true);
y = origin.y+36;
fnt = LangFont(MiniwiFont);
str = StringTable.Localize("$SWWM_BUSTERKEYS");
BrokenLines l = fnt.BreakLines(str,300);
boxw = 0;
for ( int i=0; i<l.Count(); i++ )
{
sw = l.StringWidth(i);
if ( sw > boxw ) boxw = sw;
}
x = floor((ss.x-boxw)/2.);
Screen.Dim("Black",.8,int((x-2)*hs),int((y-2)*hs),int((boxw+4)*hs),int((9*l.Count()+2)*hs));
for ( int i=0; i<l.Count(); i++ )
{
Screen.DrawText(fnt,Font.CR_WHITE,x,y,l.StringAt(i),DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true);
y += 9;
}
}
}
Class BustedQuake : Actor
{
Default
{
Radius 0.1;
Height 0;
+NOGRAVITY;
+NOCLIP;
+DONTSPLASH;
+NOTELEPORT;
+NOINTERACTION;
}
override void PostBeginPlay()
{
if ( special1 < 3 ) A_StartSound("wallbuster/smallbust",CHAN_VOICE,CHANF_OVERLAP,min(1.,special1*.32),1./max(1.,special1*.35),1.-special1*.05);
else A_StartSound("wallbuster/bigbust",CHAN_VOICE,CHANF_OVERLAP,min(1.,special1*.16),1./max(1.,special1*.35),1.-special1*.03);
A_QuakeEx(special1,special1,special1,20+special1*5,0,300+special1*90,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:special1*.1);
A_AlertMonsters(swwm_uncapalert?0:2500);
}
override void Tick()
{
if ( isFrozen() ) return;
tics--;
if ( tics <= 0 ) Destroy();
}
States
{
Spawn:
TNT1 A 700;
Stop;
}
}
// Bustin' makes me feel good
Class BusterWall : Thinker
{
Sector hitsector;
int accdamage;
Array<int> acchits;
int hitplane;
bool busted;
Vector3 bustdir;
int busttics, delay;
double cutheight;
// cached
Vector3 boundsmin, boundsmax, step;
override void Tick()
{
if ( busted )
{
busttics++;
if ( busttics > 12 )
{
Destroy();
return;
}
SpawnDebris();
return;
}
// fade out damage
if ( delay > 0 )
{
delay--;
return;
}
accdamage = int(accdamage*.9-5);
if ( accdamage <= 0 )
{
Destroy();
return;
}
}
private void SpawnDebris( bool initial = false )
{
double x, y, z;
for ( z=boundsmin.z; z<boundsmax.z; z+=step.z )
for ( y=boundsmin.y; y<boundsmax.y; y+=step.y )
for ( x=boundsmin.x; x<boundsmax.x; x+=step.x )
{
Vector3 spot = (x,y,z);
if ( level.PointInSector(spot.xy) != hitsector ) continue;
spot += (FRandom[Wallbuster](-step.x,step.x),FRandom[Wallbuster](-step.y,step.y),FRandom[Wallbuster](-step.z,step.z));
if ( !level.IsPointInLevel(spot) ) continue;
if ( (initial || !(busttics%3)) && !Random[Wallbuster](0,2) )
{
Vector3 pvel = (bustdir+(FRandom[Wallbuster](-1.,1.),FRandom[Wallbuster](-1.,1.),FRandom[Wallbuster](-1.,1.))).unit()*FRandom[Wallbuster](-2.,8.);
let s = Actor.Spawn("SWWMSmoke",spot);
s.vel = pvel;
s.scale *= 2.5;
s.special1 = Random[Wallbuster](3,8);
s.SetShade(Color(1,1,1)*Random[Wallbuster](40,120));
}
if ( (!initial && (busttics%2)) || (busttics > 4) ) continue;
int numpt = Random[Wallbuster](0,3);
for ( int i=0; i<numpt; i++ )
{
Vector3 pvel = (bustdir+(FRandom[Wallbuster](-1.,1.),FRandom[Wallbuster](-1.,1.),FRandom[Wallbuster](-1.,1.))).unit()*FRandom[Wallbuster](9.,24.);
let s = Actor.Spawn("SWWMSpark",spot);
s.vel = pvel;
}
numpt = Random[Wallbuster](1,2);
for ( int i=0; i<numpt; i++ )
{
Vector3 pvel = (bustdir+(FRandom[Wallbuster](-.6,.6),FRandom[Wallbuster](-.6,.6),FRandom[Wallbuster](-.6,.6))).unit()*FRandom[Wallbuster](2.,16.);
let s = Actor.Spawn("SWWMChip",spot);
s.vel = pvel;
s.scale *= FRandom[Wallbuster](1.2,2.4);
s.A_SetTranslation('Rubble');
}
}
}
private static bool IsIOSWall( Line l )
{
TextureID facetex[9];
facetex[0] = TexMan.CheckForTexture("ZZZFACE1",TexMan.Type_Wall);
facetex[1] = TexMan.CheckForTexture("ZZZFACE2",TexMan.Type_Wall);
facetex[2] = TexMan.CheckForTexture("ZZZFACE3",TexMan.Type_Wall);
facetex[3] = TexMan.CheckForTexture("ZZZFACE4",TexMan.Type_Wall);
facetex[4] = TexMan.CheckForTexture("ZZZFACE5",TexMan.Type_Wall);
facetex[5] = TexMan.CheckForTexture("DBRAIN1",TexMan.Type_Wall);
facetex[6] = TexMan.CheckForTexture("DBRAIN2",TexMan.Type_Wall);
facetex[7] = TexMan.CheckForTexture("DBRAIN3",TexMan.Type_Wall);
facetex[8] = TexMan.CheckForTexture("DBRAIN4",TexMan.Type_Wall);
for ( int i=0; i<9; i++ )
{
for ( int j=0; j<3; j++ )
{
if ( l.sidedef[0].GetTexture(j) == facetex[i] ) return true;
if ( l.sidedef[1] && l.sidedef[1].GetTexture(j) == facetex[i] ) return true;
}
}
return false;
}
static bool ProjectileBust( Actor a, int accdamage, Vector3 x )
{
LineTracer faketracer = new("LineTracer");
F3DFloor ff;
Vector3 HitNormal;
if ( a.BlockingFloor )
{
// find closest 3d floor for its normal
for ( int i=0; i<a.BlockingFloor.Get3DFloorCount(); i++ )
{
if ( !(a.BlockingFloor.Get3DFloor(i).flags&F3DFloor.FF_SOLID) ) continue;
if ( !(a.BlockingFloor.Get3DFloor(i).top.ZAtPoint(a.pos.xy) ~== a.floorz) ) continue;
ff = a.BlockingFloor.Get3DFloor(i);
break;
}
if ( ff )
{
faketracer.Results.ffloor = ff;
HitNormal = -ff.top.Normal;
}
else HitNormal = a.BlockingFloor.floorplane.Normal;
faketracer.Results.HitType = TRACE_HitFloor;
faketracer.Results.HitSector = a.BlockingFloor;
}
else if ( a.BlockingCeiling )
{
// find closest 3d floor for its normal
for ( int i=0; i<a.BlockingCeiling.Get3DFloorCount(); i++ )
{
if ( !(a.BlockingCeiling.Get3DFloor(i).flags&F3DFloor.FF_SOLID) ) continue;
if ( !(a.BlockingCeiling.Get3DFloor(i).bottom.ZAtPoint(a.pos.xy) ~== a.ceilingz) ) continue;
ff = a.BlockingCeiling.Get3DFloor(i);
break;
}
if ( ff )
{
faketracer.Results.ffloor = ff;
HitNormal = -ff.bottom.Normal;
}
else HitNormal = a.BlockingCeiling.ceilingplane.Normal;
faketracer.Results.HitType = TRACE_HitCeiling;
faketracer.Results.HitSector = a.BlockingCeiling;
}
else if ( a.BlockingLine )
{
HitNormal = (-a.BlockingLine.delta.y,a.BlockingLine.delta.x,0).unit();
int wside = SWWMUtility.PointOnLineSide(a.pos.xy,a.BlockingLine);
if ( !wside ) HitNormal *= -1;
faketracer.Results.HitType = TRACE_HitWall;
faketracer.Results.HitLine = a.BlockingLine;
faketracer.Results.Side = wside;
faketracer.Results.Tier = TIER_Middle;
// guess the tier hit
if ( a.BlockingLine.sidedef[1] )
{
double ceil = a.BlockingLine.sidedef[!wside].sector.ceilingplane.ZAtPoint(a.pos.xy);
double flor = a.BlockingLine.sidedef[!wside].sector.floorplane.ZAtPoint(a.pos.xy);
if ( a.pos.z >= ceil ) faketracer.Results.Tier = TIER_Upper;
else if ( (a.pos.z+a.Height) <= flor ) faketracer.Results.Tier = TIER_Lower;
}
}
else if ( a.BlockingMobj )
{
Vector3 diff = level.Vec3Diff(a.BlockingMobj.Vec3Offset(0,0,a.BlockingMobj.Height/2),a.pos);
HitNormal = diff.unit();
faketracer.Results.HitType = TRACE_HitActor;
}
return Bust(faketracer.Results,accdamage,a.target,x,a.pos.z+a.Height/2.);
}
static bool BustLinetrace( FLineTraceData d, int accdamage, Actor instigator, Vector3 x, double hitz )
{
LineTracer faketracer = new("LineTracer");
faketracer.Results.HitType = d.HitType;
faketracer.Results.HitSector = d.HitSector;
faketracer.Results.HitLine = d.HitLine;
faketracer.Results.ffloor = d.Hit3DFloor;
faketracer.Results.Side = d.LineSide;
faketracer.Results.Tier = (d.LinePart==Side.Top)?TIER_UPPER:(d.LinePart==Side.Bottom)?TIER_LOWER:TIER_Middle;
return Bust(faketracer.Results,accdamage,instigator,x,hitz);
}
static bool Bust( TraceResults d, int accdamage, Actor instigator, Vector3 x, double hitz )
{
// we can't blow up 3D floors
if ( d.ffloor ) return false;
Sector hs = d.HitSector;
int hp;
if ( d.HitType == TRACE_HitWall )
{
// no busting the goat
if ( IsIOSWall(d.HitLine) ) return false;
// onesided wall? no bust
if ( !d.HitLine.sidedef[1] ) return false;
// sector is opposite of side hit
hs = d.HitLine.sidedef[!d.Side].sector;
// what part we hit?
if ( d.Tier == TIER_Upper ) hp = 1; // ceiling
else if ( d.Tier == TIER_Lower ) hp = 0; // floor
else return false; // middle ignored
}
else if ( d.HitType == TRACE_HitCeiling ) hp = 1;
else if ( d.HitType == TRACE_HitFloor ) hp = 0;
else return false; // this isn't a valid hit, needs to be world geometry
// needs at least two two-sided lines
int lcnt = 0;
for ( int i=0; i<hs.Lines.Size(); i++ ) if ( hs.Lines[i].sidedef[1] ) lcnt++;
if ( lcnt < 2 ) return false;
// Check if it's a door
if ( !swwm_cbtall && !SWWMUtility.IsDoorSector(hs,hp) ) return false;
let ti = ThinkerIterator.Create("BusterWall",STAT_USER);
BusterWall iter, bust = null;
while ( iter = BusterWall(ti.Next()) )
{
if ( (iter.hitsector != hs) || (iter.hitplane != hp) ) continue;
bust = iter;
break;
}
bool mnew = false;
if ( !bust )
{
bust = new("BusterWall");
bust.ChangeStatNum(STAT_USER);
bust.hitsector = hs;
bust.accdamage = 0;
bust.hitplane = hp;
bust.bustdir = x;
mnew = true;
}
bust.delay = max(bust.delay,5+min(20,accdamage>>4));
bust.accdamage += accdamage;
bust.acchits.Push(accdamage);
bust.bustdir = (bust.bustdir+x)*.5;
double extracut = FRandom[Wallbuster](.01,.04)*bust.accdamage;
// is this actually sticking out?
double thisheight, othersheight, partheight, cutheight;
if ( hp )
{
thisheight = hs.FindLowestCeilingPoint();
othersheight = hs.FindHighestCeilingSurrounding();
if ( (thisheight-othersheight) >= -4. ) return false;
cutheight = min(hitz+extracut,othersheight-4);
}
else
{
thisheight = hs.FindHighestFloorPoint();
othersheight = hs.FindLowestFloorSurrounding();
if ( (thisheight-othersheight) <= 4. ) return false;
cutheight = max(hitz-extracut,othersheight+4);
}
if ( hp ) bust.cutheight = mnew?cutheight:max(bust.cutheight,cutheight);
else bust.cutheight = mnew?cutheight:min(bust.cutheight,cutheight);
partheight = abs(thisheight-bust.cutheight);
// skip if we don't cut off enough
if ( partheight < 4. ) return false;
// skip if already busted
if ( bust.busted ) return true;
// not enough total damage
if ( bust.accdamage < 100 ) return false;
// estimate sector volume
Vector2 a = (32767,32767), b = (-32768,-32768);
for ( int i=0; i<hs.lines.Size(); i++ )
{
Line l = hs.lines[i];
if ( l.v1.p.x < a.x ) a.x = l.v1.p.x;
if ( l.v2.p.x < a.x ) a.x = l.v2.p.x;
if ( l.v1.p.y < a.y ) a.y = l.v1.p.y;
if ( l.v2.p.y < a.y ) a.y = l.v2.p.y;
if ( l.v1.p.x > b.x ) b.x = l.v1.p.x;
if ( l.v2.p.x > b.x ) b.x = l.v2.p.x;
if ( l.v1.p.y > b.y ) b.y = l.v1.p.y;
if ( l.v2.p.y > b.y ) b.y = l.v2.p.y;
}
double girthitude = (b.x-a.x)*(b.y-a.y)*partheight;
// do a grid check to approximate "real" volume, useful for diagonal doors
double ystep = (b.y-a.y)/64.;
double xstep = (b.x-a.x)/64.;
int inspot = 0, allspot = 0;
for ( double y=a.y; y<=b.y; y+=ystep ) for ( double x=a.x; x<=b.x; x+=xstep )
{
allspot++;
if ( level.PointInSector((x,y)) == hs ) inspot++;
}
if ( allspot <= 0 ) return false; // what the fuck?
girthitude = (girthitude*inspot)/allspot;
// too fucking huge
if ( (girthitude > 16777216) || (max(partheight,max(b.x-a.x,b.y-a.y)) > 1024) ) return false;
// not strong enough to bust
if ( bust.accdamage < girthitude/300. ) return false;
// report bust
if ( Instigator.player )
{
let s = SWWMStats.Find(Instigator.player);
if ( s ) s.busts++;
}
bust.busted = true;
bust.busttics = 0;
// shush
hs.flags |= Sector.SECF_SILENTMOVE;
// filler texture
TextureID rubble = TexMan.CheckForTexture("ASHWALL2",TexMan.Type_Any);
// equivalents for other iwads
if ( !rubble.IsValid() ) rubble = TexMan.CheckForTexture("ASHWALL",TexMan.Type_Any);
if ( !rubble.IsValid() ) rubble = TexMan.CheckForTexture("LOOSERCK",TexMan.Type_Any);
if ( !rubble.IsValid() ) rubble = TexMan.CheckForTexture("WASTE03",TexMan.Type_Any);
if ( !rubble.IsValid() ) rubble = TexMan.CheckForTexture("SCHWAL01",TexMan.Type_Any); // Strife has a dedicated "destroyed wall" texture, nice
// activate all shoot/use specials (not locked) associated with this sector's two-sided lines
for ( int i=0; i<hs.Lines.Size(); i++ )
{
Line l = hs.Lines[i];
int locknum = SWWMUtility.GetLineLock(l);
if ( locknum && (!instigator || !instigator.CheckKeys(locknum,false,true)) ) continue;
if ( !l.sidedef[1] ) continue;
int away = 0;
if ( l.sidedef[0].sector == hs ) away = 1;
// temporarily set filler texture so switches don't play a sound
TextureID oldtex[3][2];
for ( int j=0; j<3; j++ ) for ( int k=0; k<2; k++ )
{
oldtex[j][k] = l.sidedef[k].GetTexture(j);
l.sidedef[k].SetTexture(j,rubble);
}
l.Activate(instigator,away,SPAC_Use);
l.Activate(instigator,away,SPAC_Impact);
for ( int j=0; j<3; j++ ) for ( int k=0; k<2; k++ )
l.sidedef[k].SetTexture(j,oldtex[j][k]);
// clear any use/impact specials
if ( l.Activation&(SPAC_Use|SPAC_Impact) )
l.Activation &= ~(SPAC_Use|SPAC_Impact);
}
// stop all movers
let ti2 = ThinkerIterator.Create("SectorEffect",STAT_SECTOREFFECT);
SectorEffect se;
while ( se = SectorEffect(ti2.Next()) )
{
if ( se.GetSector() != hs ) continue;
se.Destroy();
}
bust.step = (clamp((b.x-a.x)/4.,2.,32.),clamp((b.y-a.y)/4.,2.,32.),clamp(partheight/4.,2.,32.));
// quakin'
let q = Actor.Spawn("BustedQuake",(hs.centerspot.x,hs.centerspot.y,thisheight));
q.special1 = clamp(int(girthitude**.15),1,9);
if ( hp )
{
// blow up that ceiling
hs.MoveCeiling(abs(partheight),bust.cutheight,0,1,false);
bust.boundsmin = (a.x,a.y,thisheight)+(1,1,1);
bust.boundsmax = (b.x,b.y,bust.cutheight)-(1,1,1);
}
else
{
// blow up that floor
hs.MoveFloor(abs(partheight),abs(bust.cutheight),0,-1,false,true);
bust.boundsmin = (a.x,a.y,bust.cutheight)+(1,1,1);
bust.boundsmax = (b.x,b.y,thisheight)-(1,1,1);
}
bust.SpawnDebris(true);
hs.SetTexture(hp,rubble);
hs.SetXScale(hp,1.);
hs.SetYScale(hp,1.);
hs.SetAngle(hp,0.);
for ( int i=0; i<hs.Lines.Size(); i++ )
{
Line l = hs.Lines[i];
if ( !l.sidedef[1] )
{
if ( hp && !(l.flags&Line.ML_DONTPEGBOTTOM) )
l.sidedef[0].AddTextureYOffset(1,-partheight); // shift down
else if ( !hp && (l.flags&Line.ML_DONTPEGBOTTOM) )
l.sidedef[0].AddTextureYOffset(1,partheight); // shift up
continue;
}
int away = 0;
if ( l.sidedef[0].sector == hs ) away = 1;
for ( int j=0; j<2; j++ )
{
if ( l.sidedef[j].GetTexture(hp?0:2).IsValid() )
{
if ( j == away )
{
if ( hp && !(l.flags&Line.ML_DONTPEGTOP) )
l.sidedef[j].AddTextureYOffset(0,-partheight); // shift down
else if ( !hp && !(l.flags&Line.ML_DONTPEGBOTTOM) )
l.sidedef[j].AddTextureYOffset(2,partheight); // shift up
}
}
else
{
l.sidedef[j].SetTexture(hp?0:2,rubble);
l.sidedef[j].SetTextureXScale(hp?0:2,1.);
l.sidedef[j].SetTextureYScale(hp?0:2,1.);
}
}
}
// damnums
Vector3 bcenter = (bust.boundsmin+bust.boundsmax)*.5;
if ( CVar.GetCVar('swwm_accdamage',players[consoleplayer]).GetBool() )
SWWMScoreObj.Spawn(-bust.accdamage,level.Vec3Offset(bcenter,(FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8))),Font.CR_RED);
else for ( int i=0; i<bust.acchits.Size(); i++ )
SWWMScoreObj.Spawn(-bust.acchits[i],level.Vec3Offset(bcenter,(FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8))),Font.CR_RED);
return true;
}
}
Class Wallbuster : SWWMWeapon
{
Class<Ammo> loaded[25];
bool fired[25];
int rotation[6];
bool initialized;
Array<Class<Ammo> > reloadqueue;
transient bool waitreload;
int whichspin;
transient CVar cbtmuffler;
int rnum;
transient bool cancelreload;
transient ui TextureID WeaponBox, AmmoIcon[4], LoadIcon[4], UsedIcon[4], EmptyIcon;
transient ui Font TewiFont;
Class<Ammo> curobt;
ui Vector3 cpos25[25];
ui Color ccol25[25];
ui Vector2 lagvpos25[25];
override String GetObituary( Actor victim, Actor inflictor, Name mod, bool playerattack )
{
if ( curobt is 'RedShell' ) return StringTable.Localize("$O_WALLBUSTER_RED");
if ( curobt is 'GreenShell' ) return StringTable.Localize("$O_WALLBUSTER_GREEN");
if ( curobt is 'BlueShell' ) return StringTable.Localize("$O_WALLBUSTER_BLUE");
if ( curobt is 'PurpleShell' ) return StringTable.Localize("$O_WALLBUSTER_PURPLE");
return Super.GetObituary(victim,inflictor,mod,playerattack);
}
override void DrawWeapon( double TicFrac, double bx, double by, Vector2 hs, Vector2 ss )
{
static const Class<Ammo> types[] = {"RedShell","GreenShell","BlueShell","PurpleShell"};
/*
(Layout of indices)
0
4 1
3 2
L 9
K M 8 5
O N 7 6
H D
G I C E
F J B A
*/
static const int barrelposx[] =
{
29,24,26,32,34,
10,12,18,20,15,
17,23,25,20,15,
41,43,38,33,35,
48,43,38,40,46
};
static const int barrelposy[] =
{
46,42,36,36,42,
31,25,25,31,35,
8, 8,14,18,14,
8,14,18,14, 8,
31,35,31,25,25
};
if ( !TewiFont ) TewiFont = Font.GetFont('TewiShaded');
if ( !WeaponBox )
{
WeaponBox = TexMan.CheckForTexture("graphics/HUD/WallbusterDisplay.png",TexMan.Type_Any);
AmmoIcon[0] = TexMan.CheckForTexture("graphics/HUD/RedShell.png",TexMan.Type_Any);
AmmoIcon[1] = TexMan.CheckForTexture("graphics/HUD/GreenShell.png",TexMan.Type_Any);
AmmoIcon[2] = TexMan.CheckForTexture("graphics/HUD/BlueShell.png",TexMan.Type_Any);
AmmoIcon[3] = TexMan.CheckForTexture("graphics/HUD/PurpleShell.png",TexMan.Type_Any);
LoadIcon[0] = TexMan.CheckForTexture("graphics/HUD/WallbusterRed.png",TexMan.Type_Any);
LoadIcon[1] = TexMan.CheckForTexture("graphics/HUD/WallbusterGreen.png",TexMan.Type_Any);
LoadIcon[2] = TexMan.CheckForTexture("graphics/HUD/WallbusterBlue.png",TexMan.Type_Any);
LoadIcon[3] = TexMan.CheckForTexture("graphics/HUD/WallbusterPurple.png",TexMan.Type_Any);
UsedIcon[0] = TexMan.CheckForTexture("graphics/HUD/WallbusterRedUsed.png",TexMan.Type_Any);
UsedIcon[1] = TexMan.CheckForTexture("graphics/HUD/WallbusterGreenUsed.png",TexMan.Type_Any);
UsedIcon[2] = TexMan.CheckForTexture("graphics/HUD/WallbusterBlueUsed.png",TexMan.Type_Any);
UsedIcon[3] = TexMan.CheckForTexture("graphics/HUD/WallbusterPurpleUsed.png",TexMan.Type_Any);
EmptyIcon = TexMan.CheckForTexture("graphics/HUD/WallbusterEmpty.png",TexMan.Type_Any);
}
Screen.DrawTexture(WeaponBox,false,bx-54,by-72,DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true);
int ox = 6;
int oy = 60;
for ( int i=0; i<4; i++ )
{
Screen.DrawTexture(AmmoIcon[i],false,bx-ox,by-oy,DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true);
String astr = String.Format("%3d",Owner.CountInv(types[i]));
Screen.DrawText(TewiFont,Font.CR_FIRE,bx-ox-(TewiFont.StringWidth(astr)+1),by-oy-2,astr,DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true);
oy += 10;
if ( i == 1 )
{
oy = 60;
ox = 33;
}
}
// and here's the big clusterdick
for ( int i=0; i<25; i++ )
{
int idx = i;
int group = idx/5;
// shift based on group rotation
int gidx = i%5;
gidx = (gidx-rotation[group]);
while ( gidx < 0 ) gidx += 5;
idx = gidx+group*5;
// shift based on full rotation
idx = (idx-rotation[5]*5);
while ( idx < 0 ) idx += 25;
if ( !loaded[i] )
{
Screen.DrawTexture(EmptyIcon,false,bx-barrelposx[idx],by-barrelposy[idx],DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true);
continue;
}
int which = 0;
for ( int j=0; j<4; j++ )
{
if ( !(loaded[i] is types[j]) ) continue;
which = j;
break;
}
Screen.DrawTexture(fired[i]?UsedIcon[which]:LoadIcon[which],false,bx-barrelposx[idx],by-barrelposy[idx],DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true);
}
}
override void HudTick()
{
Super.HudTick();
// 25-trace
for ( int i=0; i<25; i++ )
[cpos25[i], ccol25[i]] = TraceForCrosshair25(i);
}
override void RenderUnderlay( RenderEvent e )
{
// draw custom crosshair
if ( automapactive || (players[consoleplayer].Camera is 'SecurityCamera') ) return;
if ( !phair || !phair.GetBool() ) return;
if ( !ch_on ) ch_on = CVar.GetCVar('crosshairon',players[consoleplayer]);
if ( !ch_on.GetBool() ) return;
if ( !ch_force ) ch_force = CVar.GetCVar('crosshairforce',players[consoleplayer]);
if ( ch_force.GetBool() ) return;
let sb = SWWMStatusBar(StatusBar);
if ( !sb ) return;
sb.viewport.FromHud();
sb.proj.CacheResolution();
sb.proj.CacheFov(players[consoleplayer].fov);
sb.proj.OrientForRenderUnderlay(e);
sb.proj.BeginProjection();
if ( !ch_img ) ch_img = CVar.GetCVar('crosshair',players[consoleplayer]);
int cnum = abs(ch_img.GetInt());
if ( !cnum ) return;
String tn = String.Format("XHAIR%s%d",(Screen.GetWidth()<640)?"S":"B",cnum);
TextureID ctex = TexMan.CheckForTexture(tn,TexMan.Type_MiscPatch);
if ( !ctex.IsValid() ) ctex = TexMan.CheckForTexture(String.Format("XHAIR%s1",(Screen.GetWidth()<640)?"S":"B"),TexMan.Type_MiscPatch);
if ( !ctex.IsValid() ) ctex = TexMan.CheckForTexture("XHAIRS1",TexMan.Type_MiscPatch);
Vector2 ts = TexMan.GetScaledSize(ctex);
if ( !ch_siz ) ch_siz = CVar.GetCVar('crosshairscale',players[consoleplayer]);
double cs = ch_siz.GetFloat();
double sz = 1.;
if ( cs > 0. ) sz = Screen.GetHeight()*cs/200.;
if ( !ch_grow ) ch_grow = CVar.GetCVar('crosshairgrow',players[consoleplayer]);
if ( ch_grow.GetBool() ) sz *= sb.CrosshairSize;
Vector3 tdir = level.Vec3Diff(e.ViewPos,cpos);
// project
sb.proj.ProjectWorldPos(e.ViewPos+tdir);
Vector2 vpos;
if ( !sb.proj.IsInFront() ) return;
Vector2 npos = sb.proj.ProjectToNormal();
vpos = sb.viewport.SceneToWindow(npos);
if ( lagvpos == (0,0) ) lagvpos = vpos;
else lagvpos = lagvpos*.7+vpos*.3; // this will be fucky depending on the FPS, but oh well
for ( int i=0; i<25; i++ )
{
tdir = level.Vec3Diff(e.ViewPos,cpos25[i]);
// project
sb.proj.ProjectWorldPos(e.ViewPos+tdir);
if ( !sb.proj.IsInFront() ) return;
npos = sb.proj.ProjectToNormal();
vpos = sb.viewport.SceneToWindow(npos);
if ( lagvpos25[i] == (0,0) ) lagvpos25[i] = vpos;
else lagvpos25[i] = lagvpos25[i]*.7+vpos*.3; // this will be fucky depending on the FPS, but oh well
Screen.DrawTexture(ctex,false,int(lagvpos25[i].x),int(lagvpos25[i].y),DTA_DestWidthF,ts.x*sz,DTA_DestHeightF,ts.y*sz,DTA_AlphaChannel,true,DTA_FillColor,ccol25[i]);
}
}
ui Vector3, Color TraceForCrosshair25( int i )
{
if ( !ctr ) ctr = new("SWWMCrosshairTracer");
ctr.ignoreme = Owner;
Vector3 x, y, z, ofs;
double s;
[x, y, z] = swwm_CoordUtil.GetAxes(Owner.pitch,Owner.angle,Owner.roll);
ofs = GetTraceOffset25(i);
Vector3 origin = level.Vec3Offset(Owner.Vec2OffsetZ(0,0,Owner.player.viewz),ofs.x*x+ofs.y*y+ofs.z*z);
ctr.Trace(origin,level.PointInSector(origin.xy),x,10000.,0);
if ( !ch_col ) ch_col = CVar.GetCVar('crosshaircolor',players[consoleplayer]);
Color col = ch_col.GetInt();
if ( !ch_hp ) ch_hp = CVar.GetCVar('crosshairhealth',players[consoleplayer]);
if ( ch_hp.GetInt() >= 2 )
{
int hp = Clamp(Owner.Health,0,200);
double sat = (hp<150)?1.:(1.-(hp-150)/100.);
Vector3 rgb = SWWMUtility.HSVtoRGB((hp/300.,sat,1.));
col = Color(int(rgb.x*255),int(rgb.y*255),int(rgb.z*255));
}
else if ( ch_hp.GetInt() == 1 )
{
double hp = Clamp(Owner.Health,0,100)/100.;
if ( hp <= 0 ) col = Color(255,0,0);
else if ( hp < .3 ) col = Color(255,int(hp*255/.3),0);
else if ( hp < .85 ) col = Color(int((.6-hp)*255/.3),255,0);
else col = Color(0,255,0);
}
else if ( (ctr.Results.HitType == TRACE_HitActor) && ctr.Results.HitActor.bSHOOTABLE )
{
// show target health, rather than our own
double hp = ctr.Results.HitActor.Health/double(ctr.Results.HitActor.GetSpawnHealth());
if ( hp <= 0 ) col = Color(255,0,0);
else if ( hp < .3 ) col = Color(255,int(hp*255/.3),0);
else if ( hp < .85 ) col = Color(int((.6-hp)*255/.3),255,0);
else col = Color(0,255,0);
}
if ( ctr.Results.HitType == TRACE_HitNone ) return level.Vec3Offset(origin,x*10000.), col;
else return ctr.Results.HitPos, col;
}
clearscope Vector3 GetTraceOffset25( int i ) const
{
double t1 = 90-int(i%5)*72;
double t2 = 360-int(i/5)*72;
Vector2 b = (cos(t1),sin(t1))*1.2;
b.y += 3.;
Vector2 n = (b.x*cos(t2)-b.y*sin(t2),b.x*sin(t2)+b.y*cos(t2));
return (10.,3.5+n.x,-6.+n.y);
}
override bool UsesAmmo( Class<Ammo> kind )
{
static const Class<Ammo> types[] = {"RedShell","GreenShell","BlueShell","PurpleShell"};
for ( int i=0; i<4; i++ ) if ( kind is types[i] ) return true;
return false;
}
override bool ReportHUDAmmo()
{
static const Class<Ammo> types[] = {"RedShell","GreenShell","BlueShell","PurpleShell"};
for ( int i=0; i<4; i++ ) if ( Owner.CountInv(types[i]) > 0 ) return true;
for ( int i=0; i<25; i++ ) if ( loaded[i] && !fired[i] ) return true;
return false;
}
override bool CheckAmmo( int firemode, bool autoswitch, bool requireammo, int ammocount )
{
static const Class<Ammo> types[] = {"RedShell","GreenShell","BlueShell","PurpleShell"};
if ( (firemode == PrimaryFire) || (firemode == AltFire) )
{
for ( int i=0; i<4; i++ ) if ( Owner.CountInv(types[i]) > 0 ) return true;
for ( int i=0; i<25; i++ ) if ( loaded[i] && !fired[i] ) return true;
return false;
}
return Super.CheckAmmo(firemode,autoswitch,requireammo,ammocount);
}
bool CanReload()
{
static const Class<Ammo> types[] = {"RedShell","GreenShell","BlueShell","PurpleShell"};
for ( int i=0; i<4; i++ ) if ( Owner.CountInv(types[i]) > 0 ) return true;
return false;
}
override void AttachToOwner( Actor other )
{
Super.AttachToOwner(other);
if ( !initialized )
{
// first wallbuster has five barrels loaded
initialized = true;
for ( int i=0; i<25; i++ )
loaded[i] = (i<5)?(Class<Ammo>)("RedShell"):null;
for ( int i=0; i<25; i++ )
fired[i] = false;
}
}
override void DetachFromOwner()
{
A_StopSound(CHAN_AMBEXTRA+1);
A_StopSound(CHAN_AMBEXTRA+2);
Super.DetachFromOwner();
}
action void A_CBTFlash( StateLabel flashlabel = null, int index = 0 )
{
if ( !player || !player.ReadyWeapon )
return;
Weapon weap = player.ReadyWeapon;
State flashstate = null;
if ( !flashlabel )
{
if ( weap.bAltFire )
flashstate = weap.FindState('AltFlash');
if ( !flashstate )
flashstate = weap.FindState('Flash');
}
else flashstate = weap.FindState(flashlabel);
player.SetPSprite(PSP_FLASH-index,flashstate+index);
A_OverlayFlags(PSP_FLASH-index,PSPF_RENDERSTYLE|PSPF_FORCESTYLE,true);
A_OverlayRenderStyle(PSP_FLASH-index,STYLE_Add);
}
action void ProcessTraceHit( SpreadgunTracer t, Vector3 origin, Vector3 dir, int dmg, double mm, Class<Actor> impact = "SpreadImpact", int bc = 1, bool large = false, bool bust = false )
{
// Wall busting
if ( bust )
{
int bustdmg = dmg;
if ( t is 'SpreadSlugTracer' ) bustdmg = int(SpreadSlugTracer(t).penetration);
BusterWall.Bust(t.Results,bustdmg,self,t.Results.HitVector,t.Results.HitPos.z);
}
for ( int i=0; i<t.ShootThroughList.Size(); i++ )
{
t.ShootThroughList[i].Activate(self,0,SPAC_PCross);
if ( t.ShootThroughList[i].special == GlassBreak ) // fuck glass
t.ShootThroughList[i].Activate(target,0,SPAC_Impact);
}
for ( int i=0; i<t.WaterHitList.Size(); i++ )
{
let b = Spawn(large?"InvisibleSplasher":"SmolInvisibleSplasher",t.WaterHitList[i].hitpos);
b.A_CheckTerrain();
}
for ( int i=5; i<t.Results.Distance; i+=10 )
{
if ( !Random[Boolet](0,bc) ) continue;
let b = Actor.Spawn("SWWMBubble",level.Vec3Offset(origin,dir*i));
b.Scale *= FRandom[Boolet](.1,.3);
}
for ( int i=0; i<t.HitList.Size(); i++ )
{
int realdmg = dmg?dmg:t.HitList[i].HitDamage;
SWWMDamageAccumulator.Accumulate(t.HitList[i].HitActor,realdmg,invoker,self,'shot',!large&&!swwm_shotgib);
SWWMUtility.DoKnockback(t.HitList[i].HitActor,t.HitList[i].x+(0,0,0.025),mm*FRandom[Wallbuster](0.4,1.2));
if ( t.HitList[i].HitActor.bNOBLOOD || t.HitList[i].HitActor.bINVULNERABLE || t.HitList[i].HitActor.bDORMANT )
{
let p = Spawn(impact,t.HitList[i].HitLocation);
p.angle = atan2(t.HitList[i].x.y,t.HitList[i].x.x)+180;
p.pitch = asin(t.HitList[i].x.z);
p.special1 = max(0,(bc-5)/4);
}
else
{
t.HitList[i].HitActor.TraceBleed(realdmg,self);
t.HitList[i].HitActor.SpawnBlood(t.HitList[i].HitLocation,atan2(t.HitList[i].x.y,t.HitList[i].x.x)+180,realdmg);
if ( large ) t.HitList[i].HitActor.A_StartSound("spreadgun/slugf",CHAN_DAMAGE,CHANF_OVERLAP,1.,2.);
else t.HitList[i].HitActor.A_StartSound("spreadgun/pelletf",CHAN_DAMAGE,CHANF_OVERLAP,.4,4.);
}
}
if ( (t.Results.HitType != TRACE_HitNone) && (t.Results.HitType != TRACE_HasHitSky) && (t.Results.HitType != TRACE_HitActor) )
{
Vector3 hitnormal = -t.Results.HitVector;
if ( t.Results.HitType == TRACE_HitFloor )
{
if ( t.Results.FFloor ) hitnormal = -t.Results.FFloor.top.Normal;
else hitnormal = t.Results.HitSector.floorplane.Normal;
}
else if ( t.Results.HitType == TRACE_HitCeiling )
{
if ( t.Results.FFloor ) hitnormal = -t.Results.FFloor.bottom.Normal;
else hitnormal = t.Results.HitSector.ceilingplane.Normal;
}
else if ( t.Results.HitType == TRACE_HitWall )
{
hitnormal = (-t.Results.HitLine.delta.y,t.Results.HitLine.delta.x,0).unit();
if ( !t.Results.Side ) hitnormal *= -1;
}
if ( t.Results.HitLine ) t.Results.HitLine.RemoteActivate(self,t.Results.Side,SPAC_Impact,t.Results.HitPos);
let p = Spawn(impact,t.Results.HitPos+hitnormal*4);
p.angle = atan2(hitnormal.y,hitnormal.x);
p.pitch = asin(-hitnormal.z);
p.special1 = max(0,(bc-5)/4);
}
}
override Vector3 GetTraceOffset()
{
return (10.,3.5,-6.);
}
action void A_FireShells( int num = 1 )
{
static const Class<Ammo> types[] = {"RedShell","GreenShell","BlueShell","PurpleShell"};
static const statelabel flashes[] = {"FlashRed","FlashGreen","FlashBlue","FlashPurple"};
static const String sounds[] = {"spreadgun/redfire","spreadgun/greenfire","spreadgun/bluefire","spreadgun/purplefire"};
static const Color cols[] = {Color(40,255,192,64),Color(36,255,192,80),Color(48,32,176,255),Color(24,255,224,96)};
static const int louds[] = {800,1000,1100,1200,1400,600,2500};
int redflashstr = 0;
int blueflashstr = 0;
// speen
if ( num == 1 ) invoker.whichspin = 1;
else if ( num == 5 ) invoker.whichspin = 2;
else invoker.whichspin = 0;
// first pass, count fired rounds
int howmany = 0;
for ( int i=0; i<num; i++ )
{
// get physical index
int idx = i;
// shift based on full rotation
idx = (idx+invoker.rotation[5]*5);
while ( idx > 24 ) idx -= 25;
int group = idx/5;
// shift based on group rotation
int gidx = i%5;
gidx = (gidx+invoker.rotation[group]);
while ( gidx > 4 ) gidx -= 5;
idx = gidx+group*5;
if ( !invoker.loaded[idx] || invoker.fired[idx] ) continue;
howmany++;
}
if ( howmany <= 0 )
{
A_StartSound("wallbuster/dryfire",CHAN_WEAPON,CHANF_OVERLAP);
player.SetPsprite(PSP_WEAPON,ResolveState("DryFire"));
return;
}
Vector3 x, y, z, origin, x2, y2, z2, dir;
double a, s;
[x, y, z] = swwm_CoordUtil.GetAxes(pitch,angle,roll);
[x2, y2, z2] = swwm_CoordUtil.GetAxes(BulletSlope(),angle,roll);
if ( !invoker.cbtmuffler ) invoker.cbtmuffler = CVar.GetCVar('swwm_earbuster',players[consoleplayer]);
int alertness = 0;
// second pass, play the fire effects
for ( int i=0; i<num; i++ )
{
// get physical index
int idx = i;
// shift based on full rotation
idx = (idx+invoker.rotation[5]*5);
while ( idx > 24 ) idx -= 25;
int group = idx/5;
// shift based on group rotation
int gidx = i%5;
gidx = (gidx+invoker.rotation[group]);
while ( gidx > 4 ) gidx -= 5;
idx = gidx+group*5;
if ( !invoker.loaded[idx] || invoker.fired[idx] ) continue;
int which = 0;
for ( int j=0; j<4; j++ )
{
if ( !(invoker.loaded[idx] is types[j]) ) continue;
which = j;
break;
}
double rfact = invoker.cbtmuffler.GetBool()?.85:.6;
A_StartSound(sounds[which],CHAN_WEAPON,CHANF_OVERLAP,1./(howmany**rfact),.6-howmany*.004,1.-howmany*.012);
if ( which == 0 ) redflashstr = max(120,redflashstr+10);
else if ( which == 1 ) redflashstr = max(90,redflashstr+8);
else if ( which == 2 ) blueflashstr = max(160,blueflashstr+10);
else if ( which == 3 ) redflashstr = max(60,redflashstr+6);
A_CBTFlash(flashes[which],i);
if ( alertness > louds[which] ) alertness += louds[which]/4;
else alertness += louds[which];
}
if ( howmany < 3 ) player.SetPsprite(PSP_WEAPON,ResolveState("FireOne"));
else if ( howmany < 15 ) player.SetPsprite(PSP_WEAPON,ResolveState("FireFive"));
else player.SetPsprite(PSP_WEAPON,ResolveState("FireTwentyFive"));
int qk = min(9,1+howmany/5);
int ql = min(25,6+howmany/2);
A_QuakeEx(qk,qk,qk,ql,0,8,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:qk*.15);
A_ZoomFactor(1.-qk*.04,ZOOM_INSTANT);
A_ZoomFactor(1.);
A_AlertMonsters(swwm_uncapalert?0:alertness);
A_PlayerFire();
if ( redflashstr > 0 )
{
let l = Spawn("SWWMWeaponLight",pos);
l.args[3] = redflashstr;
l.target = self;
}
if ( blueflashstr > 0 )
{
let l = Spawn("SWWMWeaponLight",pos);
l.args[0] = 96;
l.args[1] = 224;
l.args[2] = 255;
l.args[3] = blueflashstr;
l.target = self;
}
// third pass, actually fire them
Vector3 base = level.Vec3Offset(Vec2OffsetZ(0,0,player.viewz),10*x+3.5*y-6*z);
SpreadgunTracer st;
SpreadSlugTracer sst;
for ( int i=0; i<num; i++ )
{
// get physical index
int idx = i;
// shift based on full rotation
idx = (idx+invoker.rotation[5]*5);
while ( idx > 24 ) idx -= 25;
int group = idx/5;
// shift based on group rotation
int gidx = i%5;
gidx = (gidx+invoker.rotation[group]);
while ( gidx > 4 ) gidx -= 5;
idx = gidx+group*5;
if ( !invoker.loaded[idx] || invoker.fired[idx] ) continue;
int which = 0;
for ( int j=0; j<4; j++ )
{
if ( !(invoker.loaded[idx] is types[j]) ) continue;
which = j;
break;
}
invoker.fired[idx] = true;
double t1 = 90-int(i%5)*72;
double t2 = 360-int(i/5)*72;
Vector2 b = (cos(t1),sin(t1))*1.2;
b.y += 3.;
Vector2 n = (b.x*cos(t2)-b.y*sin(t2),b.x*sin(t2)+b.y*cos(t2));
origin = level.Vec3Offset(base,n.x*y+n.y*z);
invoker.curobt = types[which];
switch ( which )
{
case 1:
// slug
if ( !sst ) sst = new("SpreadSlugTracer");
sst.ignoreme = self;
sst.penetration = 275.;
a = FRandom[Wallbuster](0,360);
s = FRandom[Wallbuster](0,.002);
dir = (x2+y2*cos(a)*s+z2*sin(a)*s).unit();
sst.hitlist.Clear();
sst.shootthroughlist.Clear();
sst.waterhitlist.Clear();
sst.Trace(origin,level.PointInSector(origin.xy),dir,8000.,TRACE_HitSky);
ProcessTraceHit(sst,origin,dir,0,12000,"SlugImpact",1+howmany/8,true,(howmany>1));
for ( int i=0; i<(3-howmany/10); i++ )
{
let s = Spawn("SWWMViewSmoke",origin);
SWWMViewSmoke(s).ofs = (15,3,-3);
s.target = self;
s.SetShade(Color(1,1,1)*Random[Wallbuster](96,192));
s.alpha *= 0.5;
}
for ( int i=0; i<(6-howmany/4); i++ )
{
let s = Spawn("SWWMSmoke",origin);
s.scale *= .8;
s.alpha *= .3;
s.SetShade(Color(1,1,1)*Random[Wallbuster](96,192));
s.vel += vel*.5+x*FRandom[Wallbuster](3.,5.);
}
for ( int i=0; i<(10-howmany/3); i++ )
{
let s = Spawn("SWWMSpark",origin);
s.scale *= .2;
s.alpha *= .4;
s.vel += vel*.5+x*FRandom[Wallbuster](4.,8.)+y*FRandom[Wallbuster](-1,1)+z*FRandom[Wallbuster](-1,1);
}
SWWMUtility.DoKnockback(self,-x,18000.);
break;
case 2:
// saltshot
for ( int j=0; j<(8-howmany/5); j++ )
{
a = FRandom[Wallbuster](0,360);
s = FRandom[Wallbuster](0,.3);
let b = Spawn("SaltBeam",level.Vec3Offset(origin,y*cos(a)*s+z*sin(a)*s));
b.target = self;
b.angle = atan2(x2.y,x2.x);
b.pitch = asin(-x2.z);
b.Stamina += howmany*16;
b.Accuracy += 2+howmany/5;
b.Args[1] = (howmany>1)?2:1; // came from wallbuster
}
for ( int i=0; i<(9-howmany/3); i++ )
{
let s = Spawn("SWWMViewSmoke",origin);
SWWMViewSmoke(s).ofs = (15,3,-3);
s.target = self;
s.SetShade(Color(1,3,4)*Random[Wallbuster](32,63));
s.A_SetRenderStyle(.2,STYLE_AddShaded);
}
for ( int i=0; i<(16-howmany/2); i++ )
{
let s = Spawn("SWWMSmoke",origin);
s.special1 = 1;
s.scale *= .9;
s.SetShade(Color(1,3,4)*Random[Wallbuster](32,63));
s.A_SetRenderStyle(.3,STYLE_AddShaded);
s.vel += vel*.5+x*FRandom[Wallbuster](3.,5.)+y*FRandom[Wallbuster](-1,1)+z*FRandom[Wallbuster](-1,1);
}
for ( int i=0; i<(20-howmany/2); i++ )
{
let s = Spawn("SWWMSpark",origin);
s.scale *= .3;
s.alpha *= .4;
s.vel += vel*.5+x*FRandom[Wallbuster](4.,8.)+y*FRandom[Wallbuster](-2,2)+z*FRandom[Wallbuster](-2,2);
}
SWWMUtility.DoKnockback(self,-x,17000.);
break;
case 3:
// lead ball
a = FRandom[Wallbuster](0,360);
s = FRandom[Wallbuster](0,.006);
dir = (x2+y2*cos(a)*s+z2*sin(a)*s).unit();
let p = Spawn("TheBall",origin);
p.target = self;
p.angle = atan2(dir.y,dir.x);
p.pitch = asin(-dir.z);
p.vel = dir*p.speed*1.25;
p.special1 = (howmany>1)?2:1; // came from wallbuster
for ( int i=0; i<(4-howmany/8); i++ )
{
let s = Spawn("SWWMViewSmoke",origin);
SWWMViewSmoke(s).ofs = (15,3,-3);
s.target = self;
s.SetShade(Color(1,1,1)*Random[Wallbuster](96,192));
s.alpha *= 0.4;
}
for ( int i=0; i<(8-howmany/4); i++ )
{
let s = Spawn("SWWMSmoke",origin);
s.scale *= .6;
s.alpha *= .25;
s.SetShade(Color(1,1,1)*Random[Wallbuster](96,192));
s.vel += vel*.5+x*FRandom[Wallbuster](3.,5.);
}
for ( int i=0; i<(8-howmany/4); i++ )
{
let s = Spawn("SWWMSpark",origin);
s.scale *= .2;
s.alpha *= .4;
s.vel += vel*.5+x*FRandom[Wallbuster](4.,8.)+y*FRandom[Wallbuster](-1,1)+z*FRandom[Wallbuster](-1,1);
}
SWWMUtility.DoKnockback(self,-x,7200.);
break;
default:
// buckshot
if ( !st ) st = new("SpreadgunTracer");
st.ignoreme = self;
// attempt to uniformize expected damage
int expecteddmg = 200;
int numshot = 31-howmany;
int individualdmg = int(ceil(expecteddmg/double(numshot)));
for ( int j=0; j<numshot; j++ )
{
a = FRandom[Wallbuster](0,360);
s = FRandom[Wallbuster](0,.1);
dir = (x2+y2*cos(a)*s+z2*sin(a)*s).unit();
st.hitlist.Clear();
st.shootthroughlist.Clear();
st.waterhitlist.Clear();
st.Trace(origin,level.PointInSector(origin.xy),dir,8000.,TRACE_HitSky);
ProcessTraceHit(st,origin,dir,individualdmg,7000,bc:5+howmany/6,false,(howmany>1));
}
for ( int i=0; i<(9-howmany/3); i++ )
{
let s = Spawn("SWWMViewSmoke",origin);
SWWMViewSmoke(s).ofs = (15,3,-3);
s.target = self;
s.SetShade(Color(1,1,1)*Random[Wallbuster](96,192));
s.alpha *= .2;
}
for ( int i=0; i<(16-howmany/2); i++ )
{
let s = Spawn("SWWMSmoke",origin);
s.special1 = 1;
s.scale *= .9;
s.alpha *= .3;
s.SetShade(Color(1,1,1)*Random[Wallbuster](96,192));
s.vel += vel*.5+x*FRandom[Wallbuster](3.,5.)+y*FRandom[Wallbuster](-1,1)+z*FRandom[Wallbuster](-1,1);
}
for ( int i=0; i<(20-howmany/2); i++ )
{
let s = Spawn("SWWMSpark",origin);
s.scale *= .3;
s.alpha *= .4;
s.vel += vel*.5+x*FRandom[Wallbuster](4.,8.)+y*FRandom[Wallbuster](-2,2)+z*FRandom[Wallbuster](-2,2);
}
SWWMUtility.DoKnockback(self,-x,12000.);
break;
}
}
}
action void A_SpinOne()
{
A_StartSound("wallbuster/spin",CHAN_WEAPON,CHANF_OVERLAP);
invoker.rotation[invoker.rotation[5]]++;
while ( invoker.rotation[invoker.rotation[5]] > 4 ) invoker.rotation[invoker.rotation[5]] -= 5;
}
action void A_SpinBig()
{
A_StartSound("wallbuster/spinbig",CHAN_WEAPON,CHANF_OVERLAP);
invoker.rotation[5]++;
while ( invoker.rotation[5] > 4 ) invoker.rotation[5] -= 5;
}
action void A_HandSpin()
{
A_StartSound("wallbuster/handspin",CHAN_WEAPON,CHANF_OVERLAP);
invoker.rotation[5]--;
while ( invoker.rotation[5] < 0 ) invoker.rotation[5] += 5;
}
action void A_OpenMenu()
{
invoker.reloadqueue.Clear();
invoker.waitreload = true;
if ( player == players[consoleplayer] )
Menu.SetMenu('WallbusterReloadMenu');
}
action void A_WaitMenu()
{
if ( invoker.waitreload ) return;
if ( invoker.reloadqueue.Size() <= 0 ) player.SetPsprite(PSP_WEAPON,ResolveState("EndReload"));
else player.SetPsprite(PSP_WEAPON,ResolveState("Detach"));
}
action void A_StartDetachOverlays()
{
static const statelabel shells[] = {"DetachShell0","DetachShell1","DetachShell2","DetachShell3","DetachShell4"};
static const statelabel shellsfired[] = {"DetachShellF0","DetachShellF1","DetachShellF2","DetachShellF3","DetachShellF4"};
for ( int i=0; i<5; i++ )
{
// get physical index
int idx = i;
// shift based on full rotation
idx = (idx+invoker.rotation[5]*5);
while ( idx > 24 ) idx -= 25;
int group = idx/5;
// shift based on group rotation
int gidx = i%5;
gidx = (gidx+invoker.rotation[group]);
while ( gidx > 4 ) gidx -= 5;
idx = gidx+group*5;
if ( !invoker.loaded[idx] ) continue;
if ( invoker.fired[idx] )
A_Overlay(PSP_WEAPON+1+i,shellsfired[i]);
else A_Overlay(PSP_WEAPON+1+i,shells[i]);
}
A_StartSound("wallbuster/meleestart",CHAN_WEAPON,CHANF_OVERLAP);
}
action void A_StartAttachOverlays()
{
static const statelabel shells[] = {"AttachShell0","AttachShell1","AttachShell2","AttachShell3","AttachShell4"};
for ( int i=0; i<5; i++ )
{
// get physical index
int idx = i;
// shift based on full rotation
idx = (idx+invoker.rotation[5]*5);
while ( idx > 24 ) idx -= 25;
int group = idx/5;
// shift based on group rotation
int gidx = i%5;
gidx = (gidx+invoker.rotation[group]);
while ( gidx > 4 ) gidx -= 5;
idx = gidx+group*5;
if ( !invoker.loaded[idx] ) continue;
A_Overlay(PSP_WEAPON+1+i,shells[i]);
}
A_StartSound("wallbuster/meleeend",CHAN_WEAPON,CHANF_OVERLAP);
}
action void A_DropShells()
{
static const Class<Ammo> types[] = {"RedShell","GreenShell","BlueShell","PurpleShell"};
static const Class<Actor> casetypes[] = {"RedShellCasing","GreenShellCasing","BlueShellCasing","PurpleShellCasing"};
for ( int i=0; i<5; i++ )
{
Class<Ammo> loaded = invoker.loaded[invoker.rotation[5]*5+i];
int which = -1;
for ( int j=0; j<4; j++ )
{
if ( loaded != types[j] ) continue;
which = j;
break;
}
if ( which != -1 )
{
if ( invoker.fired[invoker.rotation[5]*5+i] )
{
Vector3 x, y, z;
[x, y, z] = swwm_CoordUtil.GetAxes(pitch,angle,roll);
Vector3 origin = level.Vec3Offset(Vec2OffsetZ(0,0,player.viewz),10*x-3*y-13*z);
let c = Spawn(casetypes[which],origin);
c.angle = angle;
c.pitch = pitch;
c.vel = x*FRandom[Junk](-.2,.2)+y*FRandom[Junk](-.2,.2)-(0,0,FRandom[Junk](2,3));
c.vel += vel*.5;
}
else
{
let amo = FindInventory(types[which]);
if ( !amo )
{
amo = Inventory(Spawn(types[which]));
amo.AttachToOwner(self);
amo.Amount = 0;
}
if ( amo.Amount < amo.MaxAmount ) amo.Amount++;
else if ( !sv_infiniteammo && !FindInventory('PowerInfiniteAmmo',true) )
Spawn(types[which],Vec3Angle(10,(angle-20)+i*10,height/3));
}
}
invoker.loaded[invoker.rotation[5]*5+i] = null;
invoker.fired[invoker.rotation[5]*5+i] = false;
}
invoker.rotation[invoker.rotation[5]] = 4;
}
action void A_LoadShell()
{
if ( invoker.reloadqueue.Size() <= 0 ) return;
Class<Ammo> toload = invoker.reloadqueue[invoker.reloadqueue.Size()-1];
invoker.reloadqueue.Pop();
invoker.loaded[invoker.rotation[5]*5+invoker.rotation[invoker.rotation[5]]] = toload;
if ( !sv_infiniteammo && !FindInventory('PowerInfiniteAmmo',true) )
{
let am = FindInventory(toload);
if ( am && (am.Amount > 0) ) am.Amount--;
}
if ( (invoker.reloadqueue.Size() > 0) && (invoker.rotation[invoker.rotation[5]] > 0) )
invoker.rotation[invoker.rotation[5]]--;
A_StartSound("wallbuster/load",CHAN_WEAPON,CHANF_OVERLAP);
}
Default
{
Tag "$T_WALLBUSTER";
Inventory.PickupMessage "$I_WALLBUSTER";
Obituary "$O_WALLBUSTER_RED";
Inventory.Icon "graphics/HUD/Icons/W_Wallbuster.png";
Weapon.SlotNumber 4;
Weapon.SelectionOrder 400;
Weapon.UpSound "wallbuster/select";
Weapon.AmmoType1 "RedShell";
Weapon.AmmoGive1 5;
SWWMWeapon.DropAmmoType "Shell";
Stamina 35000;
+SWWMWEAPON.NOFIRSTGIVE;
Radius 30;
Height 36;
}
States
{
Spawn:
XZW1 A -1;
Stop;
Select:
XZW2 G 1 A_FullRaise();
XZW2 HIJKLMNOP 2;
Goto Ready;
Ready:
XZW2 A 1
{
int flg = WRF_ALLOWUSER1;
if ( invoker.CheckAmmo(PrimaryFire,false) ) flg |= WRF_ALLOWZOOM;
if ( invoker.CanReload() ) flg |= WRF_ALLOWRELOAD;
A_WeaponReady(flg);
if ( player.cmd.buttons&(BT_ATTACK|BT_ALTATTACK|BT_ZOOM) )
invoker.CheckAmmo(EitherFire,true);
}
Wait;
Fire:
XZW2 A 0 A_FireShells(1);
Goto FireOne;
AltFire:
XZW2 A 0 A_FireShells(5);
Goto FireFive;
Zoom:
XZW2 A 0 A_FireShells(25);
Goto FireTwentyFive;
FireOne:
XZW2 AQRSTUVWX 1;
XZW2 A 0
{
if ( invoker.whichspin == 2 ) return ResolveState("BigSpin");
if ( invoker.whichspin == 1 ) return ResolveState("OneSpin");
return ResolveState("Ready");
}
Goto Ready;
FireFive:
XZW2 A 2;
XZW3 CDEFGHIJK 2;
XZW2 A 0
{
if ( invoker.whichspin == 2 ) return ResolveState("BigSpin");
if ( invoker.whichspin == 1 ) return ResolveState("OneSpin");
return ResolveState("Ready");
}
Goto Ready;
FireTwentyFive:
XZW2 A 2;
XZW3 STUVWXYZ 2;
XZW4 ABC 2;
XZW2 A 0
{
if ( invoker.whichspin == 2 ) return ResolveState("BigSpin");
if ( invoker.whichspin == 1 ) return ResolveState("OneSpin");
return ResolveState("Ready");
}
Goto Ready;
DryFire:
XZW2 A 2;
XZW2 XA 4;
XZW2 A 0
{
if ( invoker.whichspin == 2 ) return ResolveState("BigSpin");
if ( invoker.whichspin == 1 ) return ResolveState("OneSpin");
return ResolveState("Ready");
}
Goto Ready;
OneSpin:
XZW2 A 1 A_SpinOne();
XZW2 YZ 2;
XZW3 A 2;
XZW3 B 0;
XZW2 A 0 A_JumpIf(invoker.rotation[invoker.rotation[5]]==0,"BigSpin");
Goto Ready;
BigSpin:
XZW2 A 1 A_SpinBig();
XZW3 LMNOPQ 2;
XZW3 R 0;
XZW2 A 0;
Goto Ready;
Reload:
XZW2 A 2 A_StartSound("wallbuster/meleestart",CHAN_WEAPON,CHANF_OVERLAP);
XZW4 DEFG 2;
XZW4 HIJK 1;
XZW4 L 1 A_StartSound("wallbuster/unlock",CHAN_WEAPON,CHANF_OVERLAP);
XZW4 MNOPQ 1;
XZW4 RSTUV 1;
XZW4 W 1 A_OpenMenu();
XZW4 W 1 A_WaitMenu();
Wait;
Detach:
XZW4 W 0
{
invoker.rnum = invoker.reloadqueue.Size();
invoker.cancelreload = false;
A_Overlay(-9999,"CheckCancelReload");
// if it's a full rotation, don't hand-spin
if ( invoker.rnum > 20 ) return ResolveState(null);
// jump to spin if current group is full
int nset = 0, nsetp = 0;
for ( int i=0; i<5; i++ )
{
// get physical index
int idx = i;
// shift based on full rotation
idx = (idx+invoker.rotation[5]*5);
while ( idx > 24 ) idx -= 25;
int group = idx/5;
// shift based on group rotation
int gidx = i%5;
gidx = (gidx+invoker.rotation[group]);
while ( gidx > 4 ) gidx -= 5;
idx = gidx+group*5;
if ( !invoker.loaded[idx] || invoker.fired[idx] ) continue;
nset++;
}
// but previous isn't
for ( int i=20; i<25; i++ )
{
// get physical index
int idx = i;
// shift based on full rotation
idx = (idx+invoker.rotation[5]*5);
while ( idx > 24 ) idx -= 25;
int group = idx/5;
// shift based on group rotation
int gidx = i%5;
gidx = (gidx+invoker.rotation[group]);
while ( gidx > 4 ) gidx -= 5;
idx = gidx+group*5;
if ( !invoker.loaded[idx] || invoker.fired[idx] ) continue;
nsetp++;
}
return A_JumpIf((nset>=5)&&(nsetp<5),"DetachSpin");
}
XZW4 WXYZ 1;
XZW5 ABCDEF 1;
XZW5 G 1 A_StartSound("wallbuster/detach",CHAN_WEAPON,CHANF_OVERLAP);
Goto DetachHold;
DetachSpin:
XZWE U 0;
XZWE VWXYZ 1;
XZWF A 2
{
invoker.cancelreload = false;
A_Overlay(-9999,"CheckCancelReload");
A_HandSpin();
}
XZWF BCDEFG 2;
XZWF J 1 A_StartSound("wallbuster/detach",CHAN_WEAPON,CHANF_OVERLAP);
XZWF K 0;
Goto DetachHold;
DetachHold:
XZW5 H 1
{
A_StartDetachOverlays();
A_PlayerReload();
}
XZW5 IJKLMNOPQ 1;
XZW5 R 2 A_DropShells();
XZW5 ST 2;
XZW5 V 2 A_LoadShell();
XZW5 X 2 A_LoadShell();
XZW5 Y 2 A_LoadShell();
XZW6 A 2 A_LoadShell();
XZW6 C 2 A_LoadShell();
XZW6 E 0
{
if ( invoker.cancelreload ) invoker.reloadqueue.Clear();
player.SetPSprite(-9999,null);
return A_JumpIf((invoker.reloadqueue.Size()>0),"AttachSpin");
}
Goto Attach;
CheckCancelReload:
TNT1 A 1
{
if ( player.cmd.buttons&BT_RELOAD )
invoker.cancelreload = true;
}
Wait;
DetachShell0:
XZWA EFGHIJKLMN 1;
Stop;
DetachShell1:
XZWB CDEFGHIJKL 1;
Stop;
DetachShell2:
XZWC ABCDEFGHIJ 1;
Stop;
DetachShell3:
XZWC YZ 1;
XZWD ABCDEFGH 1;
Stop;
DetachShell4:
XZWD WXYZ 1;
XZWE ABCDEF 1;
Stop;
DetachShellF0:
XZWF LMNOPQRSTU 1;
Stop;
DetachShellF1:
XZWF VWXYZ 1;
XZWG ABCDE 1;
Stop;
DetachShellF2:
XZWG FGHIJKLMNO 1;
Stop;
DetachShellF3:
XZWG PQRSTUVWXY 1;
Stop;
DetachShellF4:
XZWG Z 1;
XZWH ABCDEFGHI 1;
Stop;
Attach:
XZW6 EF 2;
XZW6 G 1 A_StartAttachOverlays();
XZW6 HIJKL 1;
XZW6 M 1 A_StartSound("wallbuster/attach",CHAN_WEAPON,CHANF_OVERLAP);
XZW6 NOPQ 1;
XZW6 RS 2;
XZW6 TUVWXYZ 1;
XZW7 ABC 1;
Goto EndReload;
AttachSpin:
XZW7 JK 2;
XZW7 L 1 A_StartAttachOverlays();
XZW7 MNOPQR 1;
XZW7 S 1 A_StartSound("wallbuster/attach",CHAN_WEAPON,CHANF_OVERLAP);
XZW7 TUVW 1;
XZW7 XYZ 1;
XZW8 ABC 1;
XZW8 D 2
{
invoker.cancelreload = false;
A_Overlay(-9999,"CheckCancelReload");
A_HandSpin();
}
XZW8 EFGHIL 2;
XZW8 M 1 A_StartSound("wallbuster/detach",CHAN_WEAPON,CHANF_OVERLAP);
XZW8 N 0;
Goto DetachHold;
AttachShell0:
XZWA OPQRSTUVWXYZ 1;
XZWB AB 1;
Stop;
AttachShell1:
XZWB MNOPQRSTUVWXYZ 1;
Stop;
AttachShell2:
XZWC KLMNOPQRSTUVWX 1;
Stop;
AttachShell3:
XZWD IJKLMNOPQRSTUV 1;
Stop;
AttachShell4:
XZWE GHIJKLMNOPQRST 1;
Stop;
EndReload:
XZW4 W 1;
XZW8 OPQRS 1;
XZW8 T 1 A_StartSound("wallbuster/lock",CHAN_WEAPON,CHANF_OVERLAP);
XZW8 UVWXY 1;
XZW8 Z 1 A_StartSound("wallbuster/meleeend",CHAN_WEAPON,CHANF_OVERLAP);
XZW9 ABC 1;
XZW9 DEF 2;
Goto Ready;
User1:
XZW2 A 2
{
A_StartSound("wallbuster/meleestart",CHAN_WEAPON,CHANF_OVERLAP);
A_StartSound("demolitionist/wswing",CHAN_WEAPON,CHANF_OVERLAP);
A_PlayerMelee();
}
XZW9 GHI 2;
XZW9 J 2 A_Parry(9);
XZW9 KLM 1;
XZW9 N 1 A_Melee(70,"demolitionist/whitl",1.2);
XZW9 OPQRS 2;
XZW9 T 2 A_StartSound("wallbuster/meleeend",CHAN_WEAPON,CHANF_OVERLAP);
XZW9 UVW 2;
XZW9 XYZ 1;
XZWA ABCD 2;
Goto Ready;
Deselect:
XZW2 A 2 A_StartSound("wallbuster/deselect",CHAN_WEAPON,CHANF_OVERLAP);
XZW2 BCDEFG 2;
XZW2 G -1 A_FullLower();
Stop;
FlashRed:
XZWW A 2 Bright;
Stop;
XZWW B 2 Bright;
Stop;
XZWW C 2 Bright;
Stop;
XZWW D 2 Bright;
Stop;
XZWW E 2 Bright;
Stop;
XZWW F 2 Bright;
Stop;
XZWW G 2 Bright;
Stop;
XZWW H 2 Bright;
Stop;
XZWW I 2 Bright;
Stop;
XZWW J 2 Bright;
Stop;
XZWW K 2 Bright;
Stop;
XZWW L 2 Bright;
Stop;
XZWW M 2 Bright;
Stop;
XZWW N 2 Bright;
Stop;
XZWW O 2 Bright;
Stop;
XZWW P 2 Bright;
Stop;
XZWW Q 2 Bright;
Stop;
XZWW R 2 Bright;
Stop;
XZWW S 2 Bright;
Stop;
XZWW T 2 Bright;
Stop;
XZWW U 2 Bright;
Stop;
XZWW V 2 Bright;
Stop;
XZWW W 2 Bright;
Stop;
XZWW X 2 Bright;
Stop;
XZWW Y 2 Bright;
Stop;
FlashGreen:
XZWX A 2 Bright;
Stop;
XZWX B 2 Bright;
Stop;
XZWX C 2 Bright;
Stop;
XZWX D 2 Bright;
Stop;
XZWX E 2 Bright;
Stop;
XZWX F 2 Bright;
Stop;
XZWX G 2 Bright;
Stop;
XZWX H 2 Bright;
Stop;
XZWX I 2 Bright;
Stop;
XZWX J 2 Bright;
Stop;
XZWX K 2 Bright;
Stop;
XZWX L 2 Bright;
Stop;
XZWX M 2 Bright;
Stop;
XZWX N 2 Bright;
Stop;
XZWX O 2 Bright;
Stop;
XZWX P 2 Bright;
Stop;
XZWX Q 2 Bright;
Stop;
XZWX R 2 Bright;
Stop;
XZWX S 2 Bright;
Stop;
XZWX T 2 Bright;
Stop;
XZWX U 2 Bright;
Stop;
XZWX V 2 Bright;
Stop;
XZWX W 2 Bright;
Stop;
XZWX X 2 Bright;
Stop;
XZWX Y 2 Bright;
Stop;
FlashBlue:
XZWY A 2 Bright;
Stop;
XZWY B 2 Bright;
Stop;
XZWY C 2 Bright;
Stop;
XZWY D 2 Bright;
Stop;
XZWY E 2 Bright;
Stop;
XZWY F 2 Bright;
Stop;
XZWY G 2 Bright;
Stop;
XZWY H 2 Bright;
Stop;
XZWY I 2 Bright;
Stop;
XZWY J 2 Bright;
Stop;
XZWY K 2 Bright;
Stop;
XZWY L 2 Bright;
Stop;
XZWY M 2 Bright;
Stop;
XZWY N 2 Bright;
Stop;
XZWY O 2 Bright;
Stop;
XZWY P 2 Bright;
Stop;
XZWY Q 2 Bright;
Stop;
XZWY R 2 Bright;
Stop;
XZWY S 2 Bright;
Stop;
XZWY T 2 Bright;
Stop;
XZWY U 2 Bright;
Stop;
XZWY V 2 Bright;
Stop;
XZWY W 2 Bright;
Stop;
XZWY X 2 Bright;
Stop;
XZWY Y 2 Bright;
Stop;
FlashPurple:
XZWZ A 2 Bright;
Stop;
XZWZ B 2 Bright;
Stop;
XZWZ C 2 Bright;
Stop;
XZWZ D 2 Bright;
Stop;
XZWZ E 2 Bright;
Stop;
XZWZ F 2 Bright;
Stop;
XZWZ G 2 Bright;
Stop;
XZWZ H 2 Bright;
Stop;
XZWZ I 2 Bright;
Stop;
XZWZ J 2 Bright;
Stop;
XZWZ K 2 Bright;
Stop;
XZWZ L 2 Bright;
Stop;
XZWZ M 2 Bright;
Stop;
XZWZ N 2 Bright;
Stop;
XZWZ O 2 Bright;
Stop;
XZWZ P 2 Bright;
Stop;
XZWZ Q 2 Bright;
Stop;
XZWZ R 2 Bright;
Stop;
XZWZ S 2 Bright;
Stop;
XZWZ T 2 Bright;
Stop;
XZWZ U 2 Bright;
Stop;
XZWZ V 2 Bright;
Stop;
XZWZ W 2 Bright;
Stop;
XZWZ X 2 Bright;
Stop;
XZWZ Y 2 Bright;
Stop;
}
}