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).
2133 lines
63 KiB
Text
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;
|
|
}
|
|
}
|