swwmgz_m/zscript/swwm_cbt.zsc
Marisa Kirisame 65db7e8367 0.9.8b release:
- Crouched gesture animations.
 - Proper crouch-jumping (now always enabled).
 - New fanart by Endie.
 - Swimming animations (also used by flight).
 - Hexen-style startup screen.
 - More model cleanup.
 - Prevent Wallbuster reload menu from opening on intermissions.
 - Intermissions now only handle fire and use for advance, to prevent some lil' accidents.
 - Holding altfire on intermissions hides ui elements, so the bg is fully visible.
 - Begin writing lore for collectibles (these will come in a couple updates).
 - Fix fuzz shader being affected by texture upscaling.
 - Enemies with >=1000 starting hp also can drop golden shells.
 - Explodium Gun no longer shows with a "1x" prefix on menus when single.
 - Player animation transition tweaks.
2020-09-24 14:05:04 +02:00

1984 lines
57 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 MouseEvent( int type, int mx, int my )
{
bool res = Super.MouseEvent(type,mx,my);
// TODO mouse input?
return res;
}
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 CBTHandler : StaticEventHandler
{
bool initme;
double cbtvol;
transient CVar cbtme;
override void WorldTick()
{
if ( !cbtme ) cbtme = CVar.GetCVar('swwm_cbtmeme',players[consoleplayer]);
if ( !players[consoleplayer].mo ) return;
let cbt = players[consoleplayer].mo.FindInventory("Wallbuster");
if ( !cbt ) return;
if ( cbt == players[consoleplayer].ReadyWeapon )
{
if ( !initme && (cbtme.GetInt() > 0) )
{
initme = true;
cbtvol = .6;
}
cbtvol = min(.6,cbtvol+.1);
if ( (cbtme.GetInt() >= 1) && !cbt.IsActorPlayingSound(CHAN_AMBEXTRA+1) )
cbt.A_StartSound("wallbuster/olddays",CHAN_AMBEXTRA+1,CHANF_UI|CHANF_LOOPING,cbtvol,0.);
else if ( (cbtme.GetInt() < 1) && cbt.IsActorPlayingSound(CHAN_AMBEXTRA+1) )
cbt.A_StopSound(CHAN_AMBEXTRA+1);
if ( (cbtme.GetInt() >= 2) && !cbt.IsActorPlayingSound(CHAN_AMBEXTRA+2) )
{
// restart the music
if ( cbt.IsActorPlayingSound(CHAN_AMBEXTRA+1) )
{
cbt.A_StopSound(CHAN_AMBEXTRA+1);
cbt.A_StartSound("wallbuster/olddays",CHAN_AMBEXTRA+1,CHANF_UI|CHANF_LOOPING,cbtvol,0.);
}
cbt.A_StartSound("wallbuster/cbt",CHAN_AMBEXTRA+2,CHANF_UI,cbtvol,0.);
}
else if ( (cbtme.GetInt() < 2) && cbt.IsActorPlayingSound(CHAN_AMBEXTRA+2) )
cbt.A_StopSound(CHAN_AMBEXTRA+2);
}
else cbtvol = max(0.,cbtvol-.1);
cbt.A_SoundVolume(CHAN_AMBEXTRA+1,cbtvol*((cbtme.GetInt()>=2)?.8:1.));
cbt.A_SoundVolume(CHAN_AMBEXTRA+2,cbtvol);
}
}
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);
}
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;
double cutheight;
// cached
Vector3 boundsmin, boundsmax, step;
override void Tick()
{
if ( busted )
{
busttics++;
if ( busttics > 12 )
{
Destroy();
return;
}
SpawnDebris();
return;
}
// fade out damage
accdamage = int(accdamage*.9-10);
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[5];
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);
for ( int i=0; i<5; 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 Bust( TraceResults d, int accdamage, Actor instigator, Vector3 x, double hitz )
{
// we can't blow up 3D floors (yet)
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 (TODO: bust polyobjects)
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.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 < 1000 ) 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, timeme;
bool timing;
int reloadtime[2], rnum;
transient bool cancelreload;
transient ui TextureID WeaponBox, AmmoIcon[4], LoadIcon[4], UsedIcon[4], EmptyIcon;
transient ui Font TewiFont;
Class<Ammo> curobt;
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 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 )
{
// Wall busting
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 )
{
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);
}
}
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(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+8*y-10*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 = int(i%5)*72+90;
double t2 = int(i/5)*72+90;
Vector2 b = (cos(t1),sin(t1))*2;
b.y += 8;
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);
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] = 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 = 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);
}
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);
}
action void A_StartReloadTiming()
{
invoker.timing = true;
invoker.reloadtime[0] = level.time;
}
action void A_StopReloadTiming()
{
invoker.timing = false;
invoker.reloadtime[1] = level.time;
if ( !invoker.timeme ) invoker.timeme = CVar.GetCVar('swwm_cbttime',players[consoleplayer]);
if ( (player == players[consoleplayer]) && invoker.timeme.GetBool() )
Console.Printf("Reloaded %d barrels in %gs",invoker.rnum,(invoker.reloadtime[1]-invoker.reloadtime[0])/35.);
}
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;
}
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);
A_StartReloadTiming();
}
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;
XZW2 A 0 A_StopReloadTiming();
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;
}
}