1983 lines
57 KiB
Text
1983 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') ) 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);
|
|
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;
|
|
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;
|
|
}
|
|
}
|