Omnisight now handles 3d floors and midtextures, and exit line merging works properly. Reduced Pusher primary fire damage slightly. Cleared improper usages of GetAge().
1955 lines
56 KiB
Text
1955 lines
56 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 < 4 ) 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');
|
|
}
|
|
}
|
|
}
|
|
|
|
static void Bust( TraceResults d, int accdamage, Actor instigator, Vector3 x, double hitz )
|
|
{
|
|
// we can't blow up 3D floors (yet)
|
|
if ( d.ffloor ) return;
|
|
Sector hs = d.HitSector;
|
|
int hp;
|
|
if ( d.HitType == TRACE_HitWall )
|
|
{
|
|
// onesided wall? no bust (TODO: bust polyobjects)
|
|
if ( !d.HitLine.sidedef[1] ) return;
|
|
// 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; // middle ignored
|
|
}
|
|
else if ( d.HitType == TRACE_HitCeiling ) hp = 1;
|
|
else if ( d.HitType == TRACE_HitFloor ) hp = 0;
|
|
else return; // 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;
|
|
// Check if it's a door
|
|
if ( !swwm_cbtall && !SWWMUtility.IsDoorSector(hs,hp) ) return;
|
|
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;
|
|
cutheight = min(hitz+extracut,othersheight-4);
|
|
}
|
|
else
|
|
{
|
|
thisheight = hs.FindHighestFloorPoint();
|
|
othersheight = hs.FindLowestFloorSurrounding();
|
|
if ( (thisheight-othersheight) <= 4. ) return;
|
|
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;
|
|
// skip if already busted
|
|
if ( bust.busted ) return;
|
|
// not enough total damage
|
|
if ( bust.accdamage < 1000 ) return;
|
|
// 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; // 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;
|
|
// not strong enough to bust
|
|
if ( bust.accdamage < girthitude/300. ) return;
|
|
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);
|
|
}
|
|
}
|
|
|
|
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 is fully loaded
|
|
initialized = true;
|
|
for ( int i=0; i<25; i++ )
|
|
loaded[i] = "RedShell";
|
|
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);
|
|
SWWMHandler.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);
|
|
}
|
|
SWWMHandler.DoKnockback(self,-x,24000.);
|
|
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);
|
|
}
|
|
SWWMHandler.DoKnockback(self,-x,20000.);
|
|
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);
|
|
}
|
|
SWWMHandler.DoKnockback(self,-x,9000.);
|
|
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);
|
|
}
|
|
SWWMHandler.DoKnockback(self,-x,18000.);
|
|
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 2200;
|
|
Weapon.UpSound "wallbuster/select";
|
|
Weapon.AmmoType1 "RedShell";
|
|
Weapon.AmmoGive1 25;
|
|
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;
|
|
}
|
|
}
|