Added Lead Ball ammo type to Spreadgun.

Partial implementation of Fuck Your Shit rounds, currently in progress.
Added various shader effects to some powerups, and to player damage.
Added custom view effects to player death, disabled "face attacker" because it looks weird with model-based players.
Added "untouchable" spree tracking to the Stats tab.
Implemented "emergency reboot system" for people who want a less shameful form of the Resurrect cheat. Cooldown for consecutive reboots can be configured.
Rebalanced armors.
Small language string corrections.
Adjusted pickup model sizes of some weapons.
Fixed missing punch sound (damn typos).
Fix targetter always displaying voodoo dolls.
Fix uptime breaking when loading saves, now based on total playtime rather than gametic.
Readjusted Spreadgun ammo availability.
Flush HUD interpolators alongside messages, fixes things such as the score VERY slowly counting up when loading a save.
Spare armors now only get auto-used on pickup if there is NO armor available of that type.
Added some extra visual effects to punching walls and non-bleeding actors.
Slightly altered the melee range so it's not as awkward.
Fixed punching not using flesh sounds for bleeding actors.
Pusher primary now drags the player towards their target, like the Chainsaw.
Fixed the player having no pain sounds whatsoever.
Fixed stair step anchoring not working.
The damage dealt when walljumping on a monster now also gets boosted by the Ragekit.
This commit is contained in:
Mari the Deer 2020-03-04 16:55:45 +01:00
commit d1b1a0541d
33 changed files with 1112 additions and 89 deletions

View file

@ -13,7 +13,7 @@ Class ArmorNugget : SWWMArmor
override int HandleDamage( int damage, Name damageType, int flags )
{
double factor = amount*.01;
double factor = amount*.1;
return int(ceil(damage*factor));
}
}
@ -57,9 +57,9 @@ Class BlastSuit : SWWMArmor
Default
{
Inventory.Icon "graphics/HUD/Icons/I_BlastSuit.png";
Inventory.Amount 200;
Inventory.MaxAmount 200;
Inventory.InterHubAmount 200;
Inventory.Amount 150;
Inventory.MaxAmount 150;
Inventory.InterHubAmount 150;
SWWMArmor.ArmorPriority 2;
SWWMArmor.DrainMessage "$D_BLASTSUIT";
SWWMArmor.GiverArmor "BlastSuitItem";
@ -67,7 +67,7 @@ Class BlastSuit : SWWMArmor
override int HandleDamage( int damage, Name damageType, int flags )
{
double factor = .75;
double factor = .3;
if ( flags&DMG_EXPLOSION ) factor = 1.-(1.-factor)*.5;
return int(ceil(damage*factor));
}
@ -102,9 +102,9 @@ Class WarArmor : SWWMArmor
Default
{
Inventory.Icon "graphics/HUD/Icons/I_WarArmor.png";
Inventory.Amount 600;
Inventory.MaxAmount 600;
Inventory.InterHubAmount 600;
Inventory.Amount 250;
Inventory.MaxAmount 250;
Inventory.InterHubAmount 250;
SWWMArmor.ArmorPriority 6;
SWWMArmor.DrainMessage "$D_WARARMOR";
SWWMArmor.GiverArmor "WarArmorItem";
@ -114,8 +114,8 @@ Class WarArmor : SWWMArmor
{
double factor;
// should be enough "elemental" damage types I guess
if ( (damageType == 'Fire') || (damageType == 'Ice') || (damageType == 'Slime') || (damageType == 'Lightning') || (damageType == 'Wind') || (damageType == 'Water') ) factor = .9;
else factor = .8;
if ( (damageType == 'Fire') || (damageType == 'Ice') || (damageType == 'Slime') || (damageType == 'Lightning') || (damageType == 'Wind') || (damageType == 'Water') ) factor = .8;
else factor = .5;
if ( flags&DMG_EXPLOSION ) factor = 1.-(1.-factor)*.7;
return int(ceil(damage*factor));
}

View file

@ -46,7 +46,7 @@ Class SWWMStats : Thinker
PlayerInfo myplayer;
int lastspawn, dashcount, boostcount, stompcount, airtime, kills,
deaths, damagedealt, damagetaken, mkill, hiscore, topdealt,
toptaken;
toptaken, skill;
double grounddist, airdist, fuelusage, topspeed;
Array<WeaponUsage> wstats;
Array<Class<Weapon> > alreadygot;
@ -596,8 +596,11 @@ Class SWWMCombatTracker : Thinker
if ( newhealth != lasthealth ) updated = level.maptime+35;
if ( (mytarget.bISMONSTER || mytarget.player) && !mytarget.bINVISIBLE )
{
// enemies within 2000mu that have us as target
if ( mytarget.target && (mytarget.target.Health > 0) && (mytarget.target.player == players[consoleplayer]) && mytarget.CheckSight(mytarget.target) && (mytarget.Vec3To(mytarget.target).length() < 2000) ) updated = level.maptime+70;
if ( mytarget.player ) updated = level.maptime+35;
// players (but not voodoo dolls), always visible in sp/coop
if ( !deathmatch && mytarget.player && (mytarget.player.mo == mytarget) ) updated = level.maptime+35;
// enemies we're directly aiming at within 600mu
if ( players[consoleplayer].mo.CheckSight(mytarget) && ((mytarget.Vec3To(players[consoleplayer].mo).length() < 600) || (players[consoleplayer].mo.AimTarget() == mytarget)) ) updated = level.maptime;
}
lasthealth = newhealth;
@ -1544,6 +1547,12 @@ Class SWWMHandler : EventHandler
Demolitionist(players[i].mo).CheckUnderwaterAmb(true);
}
}
PlayerInfo p = players[consoleplayer];
Shader.SetEnabled(p,"RagekitShader",false);
Shader.SetEnabled(p,"GhostShader",false);
Shader.SetEnabled(p,"InvinciShader",false);
Shader.SetEnabled(p,"Glitch",false);
Shader.SetEnabled(p,"Grain",false);
}
override void PlayerDied( PlayerEvent e )
@ -1596,7 +1605,7 @@ Class SWWMHandler : EventHandler
s = new("SWWMStats");
s.ChangeStatNum(Thinker.STAT_STATIC);
s.myplayer = p;
s.lastspawn = gametic;
s.lastspawn = level.totaltime;
s.favweapon = -1;
}
// reset some vars
@ -1640,7 +1649,7 @@ Class SWWMHandler : EventHandler
}
// reset uptime since player had just died
SWWMStats s = SWWMStats.Find(e.Thing.player);
if ( s ) s.lastspawn = gametic;
if ( s ) s.lastspawn = level.totaltime;
}
override void WorldTick()
@ -1865,6 +1874,8 @@ Class SWWMHandler : EventHandler
Console.Printf(StringTable.Localize("$SWWM_LASTMONSTER"),e.DamageSource.player.GetUserName(),5000);
}
spreecount[pnum]++;
if ( s && spreecount[pnum] > s.skill )
s.skill = spreecount[pnum];
}
}
@ -2002,7 +2013,6 @@ Class SWWMHandler : EventHandler
case 0:
case 1:
case 2:
case 3:
e.Replacement = redpool[Random[Replacement](0,1)];
break;
case 4:
@ -2023,67 +2033,65 @@ Class SWWMHandler : EventHandler
}
else if ( (e.Replacee == 'Shell') || (e.Replacee is 'CrossbowAmmo') )
{
switch( Random[Replacement](0,14) )
switch( Random[Replacement](0,13) )
{
case 0:
case 1:
case 2:
case 3:
e.Replacement = redpool[Random[Replacement](1,2)];
break;
case 3:
case 4:
case 5:
case 6:
e.Replacement = greenpool[Random[Replacement](1,2)];
break;
case 6:
case 7:
case 8:
e.Replacement = whitepool[Random[Replacement](0,1)];
break;
case 8:
case 9:
case 10:
case 11:
e.Replacement = purplepool[Random[Replacement](0,1)];
break;
case 11:
case 12:
case 13:
e.Replacement = bluepool[Random[Replacement](0,2)];
break;
case 14:
case 13:
e.Replacement = blackpool[0];
break;
}
}
else if ( (e.Replacee == 'ShellBox') || (e.Replacee is 'CrossbowHefty') )
{
switch( Random[Replacement](0,15) )
switch( Random[Replacement](0,14) )
{
case 0:
case 1:
case 2:
case 3:
e.Replacement = redpool[Random[Replacement](2,5)];
break;
case 3:
case 4:
case 5:
case 6:
e.Replacement = greenpool[Random[Replacement](2,4)];
break;
case 6:
case 7:
case 8:
case 9:
e.Replacement = whitepool[Random[Replacement](1,3)];
break;
case 9:
case 10:
case 11:
case 12:
e.Replacement = purplepool[Random[Replacement](1,2)];
break;
case 12:
case 13:
case 14:
e.Replacement = bluepool[Random[Replacement](2,3)];
break;
case 15:
case 14:
e.Replacement = blackpool[Random[Replacement](0,2)];
break;
}
@ -2364,6 +2372,27 @@ Class SWWMHandler : EventHandler
Shader.SetUniform1f(p,"InvinciShader","str",str);
}
else Shader.SetEnabled(p,"InvinciShader",false);
if ( pc && (mo is 'Demolitionist') )
{
let demo = Demolitionist(mo);
Shader.SetEnabled(p,"Glitch",true);
Shader.SetEnabled(p,"Grain",true);
Shader.SetUniform1f(p,"Glitch","Timer",(gametic+e.FracTic)/Thinker.TICRATE);
Shader.SetUniform1f(p,"Grain","Timer",(gametic+e.FracTic)/Thinker.TICRATE);
int lastdmg = (demo.Health>0)?demo.lastdamage:Random[Flicker](60,80);
int lastdmgtic = (demo.Health>0)?demo.lastdamagetic:(gametic+Random[Flicker](30,20));
double noiz = min(lastdmg*.3*max(0,(lastdmgtic-(gametic+e.Fractic))/35.),.7);
Shader.SetUniform1f(p,"Grain","ni",noiz);
noiz = min(lastdmg*.16*max(0,(lastdmgtic-(gametic+e.Fractic))/35.),.8);
Shader.SetUniform1f(p,"Glitch","str1",noiz);
noiz = min(lastdmg*.12*max(0,(lastdmgtic-(gametic+e.Fractic))/35.),3.5);
Shader.SetUniform1f(p,"Glitch","str2",noiz);
}
else
{
Shader.SetEnabled(p,"Glitch",false);
Shader.SetEnabled(p,"Grain",false);
}
}
static void DoFlash( Actor camera, Color c, int duration )

View file

@ -0,0 +1,5 @@
// collectable items that may drop sometimes (future feature)
Class SWWMCollectable : Inventory
{
}

View file

@ -48,6 +48,12 @@ Class SWWMStatusBar : BaseStatusBar
override void FlushNotify()
{
// flush interpolators (useful since this virtual gets called
// when loading saves, too)
HealthInter.Reset(CPlayer.Health);
ScoreInter.Reset(SWWMCredits.Get(CPlayer));
FuelInter.Reset((CPlayer.mo is 'Demolitionist')?int(Demolitionist(CPlayer.mo).dashfuel):0);
DashInter.Reset((CPlayer.mo is 'Demolitionist')?int((40-Demolitionist(CPlayer.mo).dashcooldown)*3.):0);
if ( level.maptime <= 1 )
{
// flush ALL messages
@ -683,12 +689,12 @@ Class SWWMStatusBar : BaseStatusBar
}
if ( ht > 200 )
{
hw = int(min(ht-100,400)*0.25);
hw = int(min(ht-200,300)/3.);
Screen.DrawTexture(HealthTex[2],false,margin+2,ss.y-(margin+15),DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true,DTA_WindowRight,hw);
}
if ( ht > 500 )
{
hw = int(min(ht-500,500)*0.2);
hw = int(min(ht-500,500)/5.);
Screen.DrawTexture(HealthTex[3],false,margin+2,ss.y-(margin+15),DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true,DTA_WindowRight,hw);
}
int hcolor = Font.CR_RED;
@ -804,6 +810,50 @@ Class SWWMStatusBar : BaseStatusBar
// don't do anything
}
private void DrawDeath()
{
// death prompt
let demo = Demolitionist(CPlayer.mo);
if ( !demo || (CPlayer.Health > 0) ) return;
String str;
double alph;
int len;
double xx, yy;
if ( demo.player.viewheight <= 6 )
{
Screen.Dim("Black",min(demo.deadtimer/80.,1.),0,0,Screen.GetWidth(),Screen.GetHeight());
alph = clamp((demo.deadtimer-60)/60.,0.,1.);
str = String.Format(StringTable.Localize("$SWWM_URDED"),CPlayer.GetUserName());
len = mTewiFont.mFont.StringWidth(str);
xx = (ss.x-len)/2.;
yy = (ss.y-mTewiFont.mFont.GetHeight()*4)/2.;
Screen.DrawText(mTewiFont.mFont,Font.CR_RED,xx,yy,str,DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true,DTA_Alpha,alph);
alph = clamp((demo.deadtimer-120)/60.,0.,1.);
str = String.Format(StringTable.Localize("$SWWM_URDED2"),CPlayer.GetUserName());
len = mTewiFont.mFont.StringWidth(str);
xx = (ss.x-len)/2.;
yy = ss.y/2.;
Screen.DrawText(mTewiFont.mFont,Font.CR_RED,xx,yy,str,DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true,DTA_Alpha,alph);
if ( !swwm_revive )
return;
alph = clamp((demo.deadtimer-200)/60.,0.,1.);
str = String.Format(StringTable.Localize("$SWWM_URDED3"),CPlayer.GetUserName());
len = mTewiFont.mFont.StringWidth(str);
xx = (ss.x-len)/2.;
yy = (ss.y+mTewiFont.mFont.GetHeight()*2)/2.;
Screen.DrawText(mTewiFont.mFont,Font.CR_RED,xx,yy,str,DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true,DTA_Alpha,alph);
if ( demo.revivefail > level.maptime )
{
str = StringTable.Localize("$SWWM_REFAIL");
len = mTewiFont.mFont.StringWidth(str);
xx = (ss.x-len)/2.;
yy = ss.y-30;
if ( (gametic%12) < 6 )
Screen.DrawText(mTewiFont.mFont,Font.CR_RED,xx,yy,str,DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true);
}
}
}
override void Draw( int state, double TicFrac )
{
Super.Draw(state,TicFrac);
@ -823,5 +873,6 @@ Class SWWMStatusBar : BaseStatusBar
DrawStatus();
DrawWeapon();
DrawMessages();
DrawDeath();
}
}

View file

@ -106,7 +106,7 @@ Class SWWMSpareArmor : Inventory abstract
else shouldautouse = CVar.GetCVar('swwm_autousearmor',Owner.player).GetBool();
if ( pickup && !shouldautouse ) return false;
let cur = Owner.FindInventory(giveme);
if ( !cur || (cur.Amount < cur.MaxAmount) )
if ( !cur || (cur.Amount <= 0) )
{
Owner.GiveInventory(giveme,GetDefaultByType(giveme).Amount);
if ( UseSound ) Owner.A_StartSound(UseSound,CHAN_ITEMEXTRA,CHANF_DEFAULT,.6);
@ -356,6 +356,94 @@ Class SWWMWeaponLight : DynamicLight
}
}
Class PunchImpact : Actor
{
Default
{
Radius 0.1;
Height 0;
+NOGRAVITY;
+NOCLIP;
+DONTSPLASH;
+NOTELEPORT;
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
A_QuakeEx(2,2,2,12,0,200,"",QF_RELATIVE|QF_SCALEDOWN,falloff:100,rollIntensity:.3);
A_StartSound("demolitionist/punch",CHAN_VOICE);
A_SprayDecal("WallCrack",-20);
int numpt = Random[Ponch](5,10);
Vector3 x = (cos(angle)*cos(pitch),sin(angle)*cos(pitch),-sin(pitch));
for ( int i=0; i<numpt; i++ )
{
Vector3 pvel = (x+(FRandom[Ponch](-.8,.8),FRandom[Ponch](-.8,.8),FRandom[Ponch](-.8,.8))).unit()*FRandom[Ponch](.1,1.2);
let s = Spawn("SWWMSmoke",pos);
s.vel = pvel;
s.SetShade(Color(1,1,1)*Random[Ponch](128,192));
}
numpt = Random[Ponch](4,12);
for ( int i=0; i<numpt; i++ )
{
Vector3 pvel = (FRandom[Ponch](-1,1),FRandom[Ponch](-1,1),FRandom[Ponch](-1,1)).unit()*FRandom[Ponch](2,8);
let s = Spawn("SWWMSpark",pos);
s.vel = pvel;
}
numpt = Random[Ponch](4,8);
for ( int i=0; i<numpt; i++ )
{
Vector3 pvel = (FRandom[Ponch](-1,1),FRandom[Ponch](-1,1),FRandom[Ponch](-1,1)).unit()*FRandom[Ponch](2,8);
let s = Spawn("SWWMChip",pos);
s.vel = pvel;
}
Destroy();
}
}
Class BigPunchImpact : Actor
{
Default
{
Radius 0.1;
Height 0;
+NOGRAVITY;
+NOCLIP;
+DONTSPLASH;
+NOTELEPORT;
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
A_QuakeEx(8,8,8,18,0,600,"",QF_RELATIVE|QF_SCALEDOWN,falloff:200,rollIntensity:.9);
A_StartSound("pusher/althit",CHAN_VOICE);
A_SprayDecal("BigWallCrack",-20);
int numpt = Random[Ponch](9,16);
Vector3 x = (cos(angle)*cos(pitch),sin(angle)*cos(pitch),-sin(pitch));
for ( int i=0; i<numpt; i++ )
{
Vector3 pvel = (x+(FRandom[Ponch](-.8,.8),FRandom[Ponch](-.8,.8),FRandom[Ponch](-.8,.8))).unit()*FRandom[Ponch](.1,1.2);
let s = Spawn("SWWMSmoke",pos);
s.vel = pvel;
s.SetShade(Color(1,1,1)*Random[Ponch](128,192));
}
numpt = Random[Ponch](9,15);
for ( int i=0; i<numpt; i++ )
{
Vector3 pvel = (FRandom[Ponch](-1,1),FRandom[Ponch](-1,1),FRandom[Ponch](-1,1)).unit()*FRandom[Ponch](2,8);
let s = Spawn("SWWMSpark",pos);
s.vel = pvel;
}
numpt = Random[Ponch](9,16);
for ( int i=0; i<numpt; i++ )
{
Vector3 pvel = (FRandom[Ponch](-1,1),FRandom[Ponch](-1,1),FRandom[Ponch](-1,1)).unit()*FRandom[Ponch](2,8);
let s = Spawn("SWWMChip",pos);
s.vel = pvel;
}
Destroy();
}
}
// Base class for all SWWM Weapons
Class SWWMWeapon : Weapon abstract
{
@ -363,7 +451,7 @@ Class SWWMWeapon : Weapon abstract
FlagDef NoFirstGive : SWeaponFlags, 0; // don't give ammo on first pickup (for weapons with a clip count)
override void AttachToOwner (Actor other)
override void AttachToOwner( Actor other )
{
Inventory.AttachToOwner(other);
Ammo1 = AddAmmo(Owner,AmmoType1,bNoFirstGive?0:AmmoGive1);
@ -445,16 +533,17 @@ Class SWWMWeapon : Weapon abstract
private action bool TryMelee( double angle, int dmg )
{
FTranslatedLineTarget t;
double slope = AimLineAttack(angle,2*DEFMELEERANGE,t,0.,ALF_CHECK3D);
double slope = AimLineAttack(angle,1.5*DEFMELEERANGE,t,0.,ALF_CHECK3D);
FLineTraceData d;
LineTrace(angle,2*DEFMELEERANGE,slope,0,player.viewheight,data:d);
LineTrace(angle,1.5*DEFMELEERANGE,slope,0,player.viewheight,data:d);
bool raging = CountInv("RagekitPower");
if ( d.HitType == TRACE_HitActor )
{
bool bloodless = true;
double diff = deltaangle(self.angle,AngleTo(d.HitActor));
self.angle += clamp(diff,-5.,5.);
SWWMHandler.DoKnockback(d.HitActor,d.HitDir+(0,0,.2),dmg*2000);
if ( CountInv("RagekitPower") )
if ( raging )
{
invoker.bEXTREMEDEATH = true;
invoker.bNOEXTREMEDEATH = false;
@ -468,15 +557,22 @@ Class SWWMWeapon : Weapon abstract
dmg = d.HitActor.DamageMobj(invoker,self,dmg,'Melee',DMG_USEANGLE|DMG_THRUSTLESS,atan2(d.HitDir.y,d.HitDir.x));
invoker.bEXTREMEDEATH = invoker.default.bEXTREMEDEATH;
invoker.bNOEXTREMEDEATH = invoker.default.bEXTREMEDEATH;
if ( d.HitActor.player ) d.HitActor.A_QuakeEx(2,2,2,6,0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:0.25);
int quakin = raging?8:2;
if ( d.HitActor.player ) d.HitActor.A_QuakeEx(quakin,quakin,quakin,6,0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:0.125*quakin);
if ( !d.HitActor.bNOBLOOD && !d.HitActor.bINVULNERABLE )
{
d.HitActor.TraceBleed(dmg,invoker);
d.HitActor.SpawnBlood(d.HitLocation,atan2(d.HitDir.y,d.HitDir.x)+180,dmg);
bloodless = false;
}
else bloodless = false;
A_QuakeEx(1,1,1,3,0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:0.12);
A_StartSound(bloodless?"demolitionist/punch":"demolitionist/punchf",CHAN_WEAPON,CHANF_OVERLAP);
else
{
let p = Spawn(raging?"BigPunchImpact":"PunchImpact",d.HitLocation);
p.angle = atan2(-d.HitDir.y,-d.HitDir.x);
}
A_QuakeEx(quakin/2,quakin/2,quakin/2,3,0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:0.06*quakin);
if ( raging ) A_StartSound(bloodless?"pusher/althit":"pusher/altmeat",CHAN_WEAPON,CHANF_OVERLAP);
else A_StartSound(bloodless?"demolitionist/punch":"demolitionist/punchf",CHAN_WEAPON,CHANF_OVERLAP);
A_AlertMonsters(300);
return true;
}
@ -484,7 +580,8 @@ Class SWWMWeapon : Weapon abstract
}
action void A_Melee( int dmg = 40 )
{
int maxang = CountInv("RagekitPower")?24:12;
bool raging = CountInv("RagekitPower");
int maxang = raging?18:12;
for ( int i=0; i<maxang; i++ ) if ( TryMelee(angle+i*(45./16),dmg) || TryMelee(angle-i*(45./16),dmg) ) return;
// check for walls instead
FTranslatedLineTarget t;
@ -492,10 +589,29 @@ Class SWWMWeapon : Weapon abstract
FLineTraceData d;
LineTrace(angle,DEFMELEERANGE,slope,TRF_THRUACTORS,player.viewheight,data:d);
if ( d.HitType == TRACE_HitNone ) return;
if ( d.HitType == TRACE_HitWall )
d.HitLine.RemoteActivate(self,d.LineSide,SPAC_Impact,d.HitLocation-d.HitDir*4);
A_QuakeEx(1,1,1,3,0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:0.12);
A_StartSound("demolitionist/punch",CHAN_WEAPON,CHANF_OVERLAP);
Vector3 HitNormal = -d.HitDir;
if ( d.HitType == TRACE_HitFloor )
{
if ( d.Hit3DFloor ) HitNormal = -d.Hit3DFloor.top.Normal;
else HitNormal = d.HitSector.floorplane.Normal;
}
else if ( d.HitType == TRACE_HitCeiling )
{
if ( d.Hit3DFloor ) HitNormal = -d.Hit3DFloor.bottom.Normal;
else HitNormal = d.HitSector.ceilingplane.Normal;
}
else if ( d.HitType == TRACE_HitWall )
{
HitNormal = (-d.HitLine.delta.y,d.HitLine.delta.x,0).unit();
if ( !d.LineSide ) HitNormal *= -1;
d.HitLine.RemoteActivate(self,d.LineSide,SPAC_Impact,d.HitLocation+HitNormal*4);
}
let p = Spawn(raging?"BigPunchImpact":"PunchImpact",d.HitLocation+HitNormal*4);
p.angle = atan2(HitNormal.y,HitNormal.x);
p.pitch = asin(-HitNormal.z);
int quakin = raging?4:1;
A_QuakeEx(quakin,quakin,quakin,3,0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:0.12*quakin);
A_StartSound(raging?"pusher/althit":"demolitionist/punch",CHAN_WEAPON,CHANF_OVERLAP);
A_AlertMonsters(100);
}
override void PlayUpSound( Actor origin )

View file

@ -331,7 +331,6 @@ Class PusherWeapon : SWWMWeapon
A_QuakeEx(3,3,3,7,0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:.8);
A_AlertMonsters(1200);
int dmg = int(3+invoker.chargelevel*2);
bool bloodless = false;
if ( d.HitType == TRACE_HitActor )
{
double diff = deltaangle(self.angle,AngleTo(d.HitActor));
@ -351,6 +350,8 @@ Class PusherWeapon : SWWMWeapon
d.HitActor.SpawnBlood(d.HitLocation,atan2(d.HitDir.y,d.HitDir.x)+180,dmg);
d.HitActor.A_StartSound("pusher/meat",CHAN_ITEMEXTRA,CHANF_OVERLAP);
}
// move towards target
vel.xy += Vec2To(d.HitActor).unit();
}
else
{
@ -413,7 +414,6 @@ Class PusherWeapon : SWWMWeapon
A_QuakeEx(8,8,8,12,0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:.8);
A_AlertMonsters(1600);
int dmg = int(80*invoker.chargelevel);
bool bloodless = false;
if ( d.HitType == TRACE_HitActor )
{
double diff = deltaangle(self.angle,AngleTo(d.HitActor));

View file

@ -430,7 +430,6 @@ Class SWWMKnowledgeBaseMenu : GenericMenu
if ( players[consoleplayer].Health <= 0 )
{
// ded
MenuSound("menu/democlose");
Close();
return;
}
@ -440,7 +439,7 @@ Class SWWMKnowledgeBaseMenu : GenericMenu
// alphabetically sorted inventory
for ( Inventory inv=players[consoleplayer].mo.Inv; inv; inv=inv.Inv )
{
if ( (inv.Amount <= 0) || !inv.SpawnState.ValidateSpriteFrame() || (inv is 'Key') || (inv is 'BasicArmor') || (inv is 'HexenArmor') || (inv is 'Powerup') || (inv is 'SWWMArmor') || (!(inv is 'Ammo') && !(inv is 'Weapon') && !inv.bINVBAR) ) continue;
if ( (inv.Amount <= 0) || !inv.SpawnState.ValidateSpriteFrame() || (inv is 'Key') || (inv is 'BasicArmor') || (inv is 'HexenArmor') || (inv is 'Powerup') || (inv is 'SWWMArmor') || (!(inv is 'Ammo') && !(inv is 'Weapon') && !inv.bINVBAR) && !(inv is 'HammerspaceEmbiggener') && !(inv is 'SWWMCollectable') ) continue;
String tag = inv.GetTag();
bool greater = false;
for ( int i=0; i<invlist.Size(); i++ )
@ -796,9 +795,9 @@ Class SWWMKnowledgeBaseMenu : GenericMenu
xx = 9;
yy = 23;
// wish I could use macros for this
int thour = ((gametic-stats.lastspawn)/(3600*Thinker.TICRATE));
int tmin = ((gametic-stats.lastspawn)/(60*Thinker.TICRATE))%60;
int tsec = ((gametic-stats.lastspawn)/Thinker.TICRATE)%60;
int thour = ((level.totaltime-stats.lastspawn)/(3600*Thinker.TICRATE));
int tmin = ((level.totaltime-stats.lastspawn)/(60*Thinker.TICRATE))%60;
int tsec = ((level.totaltime-stats.lastspawn)/Thinker.TICRATE)%60;
str = String.Format("\cx%s\c-%02d\cu:\c-%02d\cu:\c-%02d",StringTable.Localize("$SWWM_STATUPTIME"),thour,tmin,tsec);
Screen.DrawText(TewiFont,Font.CR_WHITE,origin.x+xx,origin.y+yy,str,DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true);
yy += 16;
@ -852,6 +851,9 @@ Class SWWMKnowledgeBaseMenu : GenericMenu
str = String.Format("\cx%s\c-%d",StringTable.Localize("$SWWM_STATMKILL"),stats.mkill);
Screen.DrawText(TewiFont,Font.CR_WHITE,origin.x+xx,origin.y+yy,str,DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true);
yy += 16;
str = String.Format("\cx%s\c-%d",StringTable.Localize("$SWWM_STATSKILL"),stats.skill);
Screen.DrawText(TewiFont,Font.CR_WHITE,origin.x+xx,origin.y+yy,str,DTA_VirtualWidthF,ss.x,DTA_VirtualHeightF,ss.y,DTA_KeepRatio,true);
yy += 16;
str = String.Format("\cx%s\c-",StringTable.Localize("$SWWM_STATFAVWEAP"));
if ( stats.favweapon == -1 ) str = str.."N/A";
else

View file

@ -9,7 +9,7 @@ Class Demolitionist : PlayerPawn
bool sendtoground;
bool key_reentrant;
int lastdamage;
int lastdamage, lastdamagetic;
bool lastground;
int lastgroundtic;
double lastvelz, prevvelz;
@ -33,6 +33,7 @@ Class Demolitionist : PlayerPawn
};
int lastunder;
int deadtimer, revivefail;
Default
{
@ -503,7 +504,8 @@ Class Demolitionist : PlayerPawn
// lucky collar
if ( Health < 25 ) damage /= 4;
if ( source == self ) damage /= 2;
int lastdamage = Super.DamageMobj(inflictor,source,damage,mod,flags,angle);
lastdamage = Super.DamageMobj(inflictor,source,damage,mod,flags,angle);
lastdamagetic = max(lastdamagetic,gametic+clamp(lastdamage,10,80));
if ( (lastdamage > 0) && (PainChance == 0) && (level.maptime>lastmpain) )
{
lastmpain = level.maptime;
@ -627,7 +629,6 @@ Class Demolitionist : PlayerPawn
double spd = vel.length();
if ( spd > 10. ) vel = (vel+accel/TICRATE).unit()*spd;
else vel = vel+accel/TICRATE;
player.jumptics = -2;
}
if ( abs(roll) > 0. ) roll += clamp(deltaangle(roll,0),-3.,3.);
}
@ -635,7 +636,7 @@ Class Demolitionist : PlayerPawn
guidepitch *= .9;
guideroll *= .9;
// anchor to ground when going down steps
if ( !player.onground && !bFly && !bFlyCheat && (waterlevel < 2) && (abs(pos.z-floorz) <= maxdropoffheight) && (player.jumptics == 0) && (vel.z < 0) )
if ( lastground && !player.onground && !bFly && !bFlyCheat && (waterlevel < 2) && (abs(pos.z-floorz) <= maxdropoffheight) && (player.jumptics == 0) && (vel.z < 0) )
{
ssup = max(0,(pos.z-floorz));
SetOrigin(Vec2OffsetZ(0,0,floorz),true);
@ -801,8 +802,78 @@ Class Demolitionist : PlayerPawn
}
override void DeathThink()
{
// TODO reboot mechanic, death camera that doesn't move body
Super.DeathThink();
player.Uncrouch();
TickPSprites();
player.onground = (pos.Z<=floorz);
// ded (demo-chan falls faster tho)
player.deltaviewheight = 0;
if ( player.viewheight > 6 ) player.viewheight -= 3;
if ( player.viewheight < 6 ) player.viewheight = 6;
// center pitch
double dpitch = clamp(deltaangle(pitch,0),-6,6);
if ( abs(dpitch) < 3. ) pitch = 0.;
else pitch += dpitch;
// add roll
double droll = clamp(deltaangle(roll,50)*.5,-5,5);
if ( abs(droll) < 2. ) roll = 50.;
else roll += droll;
player.mo.CalcHeight();
if ( player.damagecount ) player.damagecount--;
if ( player.poisoncount ) player.poisoncount--;
if ( player.viewheight <= 6 )
{
deadtimer++;
if ( multiplayer || level.AllowRespawn || sv_singleplayerrespawn || G_SkillPropertyInt(SKILLP_PlayerRespawn) )
{
// standard behaviour, respawn normally
if ( (Level.maptime >= player.respawn_time) || ((player.cmd.buttons&BT_USE) && !player.Bot) )
{
player.cls = null;
player.playerstate = PST_REBORN;
if ( special1 > 2 ) special1 = 0;
}
}
else if ( (player.cmd.buttons&BT_USE) && (deadtimer > 150) )
{
// reload save
player.cls = null;
player.playerstate = PST_ENTER;
if ( special1 > 2 ) special1 = 0;
}
else if ( (player.cmd.buttons&BT_ATTACK) && (deadtimer > 150) && swwm_revive )
{
// reboot (if possible)
if ( !FindInventory("ReviveCooldown") )
{
player.Resurrect();
roll = 0;
let s = Spawn("DemolitionistShockwave",pos);
s.target = self;
s.special1 = 30;
ReactionTime = 17;
A_Stop();
A_AlertMonsters();
if ( player == players[consoleplayer] )
{
A_StartSound("demolitionist/hardland",CHAN_FOOTSTEP,CHANF_OVERLAP);
A_StartSound("demolitionist/hardland",CHAN_FOOTSTEP,CHANF_OVERLAP,pitch:.7);
A_StartSound("demolitionist/hardland",CHAN_FOOTSTEP,CHANF_OVERLAP,pitch:.4);
}
SWWMHandler.DoFlash(self,Color(255,255,255,255),10);
SWWMHandler.DoFlash(self,Color(255,128,192,255),30);
if ( special1 > 2 ) special1 = 0;
if ( swwm_revivecooldown > 0 )
GiveInventory("ReviveCooldown",1);
}
else
{
A_StartSound("*usefail",CHAN_ITEM,CHANF_UI|CHANF_LOCAL);
SWWMHandler.DoFlash(self,Color(64,255,0,0),30);
revivefail = level.maptime+20;
}
}
}
else deadtimer = 0;
}
override void PlayIdle()
{
@ -1480,3 +1551,38 @@ Class DemolitionistShockwave : Actor
Stop;
}
}
Class ReviveCooldown : Powerup
{
Default
{
Inventory.Icon "graphics/HUD/Icons/I_Revive.png";
Powerup.Duration -30;
}
override void Tick()
{
if ( !Owner ) Destroy();
if ( Owner.Health <= 0 ) return; // timer does not go down when dead
if ( (EffectTics == 0) || ((EffectTics > 0) && (--EffectTics == 0)) )
Destroy ();
}
override void InitEffect()
{
Super.InitEffect();
// adjust the duration
EffectTics = max(0,swwm_revivecooldown)*Thinker.TICRATE;
}
override void EndEffect()
{
Super.EndEffect();
if ( !Owner ) return;
Owner.A_StartSound("demolitionist/revive",CHAN_ITEMEXTRA);
if ( (EffectTics <= 0) && Owner && Owner.CheckLocalView() ) Console.Printf(StringTable.Localize("$D_REFAIL"));
}
override void OwnerDied()
{
// do nothing, this "powerup" is preserved on death
}
}

View file

@ -711,7 +711,7 @@ Class RagekitPower : Powerup
override void ModifyDamage( int damage, Name damageType, out int newdamage, bool passive, Actor inflictor, Actor source, int flags )
{
if ( !passive && ((damageType == 'Melee') || (damageType == 'Dash') || (damageType == 'GroundPound')) )
if ( !passive && ((damageType == 'Melee') || (damageType == 'Jump') || (damageType == 'Dash') || (damageType == 'GroundPound')) )
{
newdamage = damage*8;
if ( level.maptime > lasteffect+5 )

View file

@ -820,6 +820,600 @@ Class SaltBeam : Actor
}
}
Class OnFireLight : DynamicLight
{
OnFire of;
override void Tick()
{
Super.Tick();
if ( !of || !of.victim )
{
Destroy();
return;
}
Args[0] = clamp(of.Amount*4,0,255);
Args[1] = clamp(of.Amount*2,0,128);
Args[3] = int(max(of.victim.radius,of.victim.height)*2.+20+clamp(of.amount/5,0,80));
SetOrigin(of.Victim.Vec3Offset(0,0,of.Victim.Height/2),true);
}
}
Class OnFire : Actor
{
Actor victim, instigator, lite;
int amount, cnt, delay;
double oangle;
override void Tick()
{
if ( !victim )
{
Destroy();
return;
}
if ( victim.waterlevel > 0 )
{
if ( lite ) lite.Destroy();
amount -= int(victim.waterlevel**2);
}
if ( (victim.Health <= 0) || ((victim is 'FlamingChunk') && !victim.bMISSILE) ) amount = min(amount,100);
if ( !(level.maptime%3) )
amount--;
if ( victim.player ) amount -= int(abs(actor.deltaangle(victim.angle,oangle))/30);
oangle = victim.angle;
if ( amount < -30 )
{
Destroy();
return;
}
if ( cnt > 0 ) cnt--;
else
{
cnt = 10;
if ( victim.bSHOOTABLE && (victim.Health > 0) && (amount > 0) )
victim.DamageMobj(self,instigator,clamp(int(amount*(victim.bBOSS?0.05:0.15)),1,20),'Fire',DMG_THRUSTLESS); // the only reason why we need to use an actor
if ( !victim )
{
Destroy();
return;
}
}
if ( delay > 0 ) delay--;
if ( level.maptime%5 ) return;
int numpt = clamp(int(Random[FlameT](2,4)*amount*0.02),1,4);
double mult = max(victim.radius,victim.height)/30.;
numpt = int(clamp(numpt*mult**.5,1,5));
for ( int i=0; i<numpt; i++ )
{
Vector3 pos = victim.Vec3Offset(FRandom[FlameT](-victim.radius,victim.radius)*0.8,FRandom[FlameT](-victim.radius,victim.radius)*0.8,FRandom[FlameT](victim.height*0.2,victim.height*0.8));
double ang = FRandom[FlameT](0,360);
double pt = FRandom[FlameT](-90,90);
if ( amount > 0 )
{
let c = victim.Spawn("OnFireTrail",pos);
c.scale *= max(.3,mult*0.5);
c.vel = victim.vel*0.5+(cos(ang)*cos(pt),sin(ang)*cos(pt),-sin(pt))*FRandom[FlameT](.5,2.)*c.scale.x;
}
let s = victim.Spawn("SWWMSmoke",pos);
s.scale *= max(1.,1.6*mult);
s.alpha *= min(amount+30,100)*0.02;
s.vel = victim.vel*0.5+(cos(ang)*cos(pt),sin(ang)*cos(pt),-sin(pt))*FRandom[FlameT](.2,.6)*s.scale.x;
}
if ( amount <= 0 ) return;
// spread to nearby actors
let bt = BlockThingsIterator.Create(victim);
while ( bt.Next() )
{
let t = bt.Thing;
if ( !t || !t.bSHOOTABLE || (t.Health <= 0) || (t == victim) || ((t == instigator) && (delay > 0)) || (victim.Distance3D(t) > victim.radius+t.radius+20) || !victim.CheckSight(t) ) continue;
int amt = max(1,amount/10);
if ( IsOnFire(t) ) amt = min(5,amt);
Apply(t,instigator,amt);
}
}
static OnFire Apply( Actor victim, Actor instigator, int amount, int delay = 0 )
{
if ( amount <= 0 ) return null;
let ti = ThinkerIterator.Create("OnFire");
OnFire t;
while ( t = OnFire(ti.Next()) )
{
if ( t.victim != victim ) continue;
if ( instigator ) t.instigator = instigator;
t.amount = min(500,t.amount+amount);
t.cnt = min(t.cnt,5);
return t;
}
t = OnFire(Spawn("OnFire"));
t.victim = victim;
t.instigator = instigator;
t.amount = min(500,amount);
t.cnt = 1;
// for chunks
t.delay = delay;
t.lite = Actor.Spawn("OnFireLight",victim.pos);
OnFireLight(t.lite).of = t;
t.oangle = victim.angle;
return t;
}
static bool IsOnFire( Actor victim )
{
let ti = ThinkerIterator.Create("OnFire");
OnFire t;
while ( t = OnFire(ti.Next()) )
{
if ( t.victim != victim ) continue;
return (t.amount>0);
}
return false;
}
Default
{
+NOGRAVITY;
+NOBLOCKMAP;
+DONTSPLASH;
Obituary "$O_SPREADGUN_BLACK";
}
}
Class OnFireTrailLight : PaletteLight
{
Default
{
Tag "HellExpl";
Args 0,0,0,40;
ReactionTime 40;
}
override void Tick()
{
Super.Tick();
Args[0] /= 10;
Args[1] /= 10;
Args[2] /= 10;
Args[3] += 3;
if ( !target || (target.waterlevel > 0) )
{
Destroy();
return;
}
SetOrigin(target.pos,true);
}
}
Class OnFireTrail : Actor
{
override void PostBeginPlay()
{
Super.PostBeginPlay();
Scale.x *= RandomPick[ExploS](-1,1);
Scale.y *= RandomPick[ExploS](-1,1);
roll = FRandom[ExploS](0,360);
}
action void A_Flame()
{
if ( waterlevel > 0 )
vel *= 0.9;
else
{
vel *= 0.98;
vel.z += 0.2*abs(scale.x);
}
if ( waterlevel > 0 )
{
let s = Spawn("SWWMSmoke",pos);
s.vel = (FRandom[FlameT](-0.2,0.2),FRandom[FlameT](-0.2,0.2),FRandom[FlameT](-0.2,0.2));
s.vel += vel*0.3;
s.alpha *= alpha*4;
s.scale *= 0.5+abs(scale.x)*(.5+GetAge()/6.);
Destroy();
return;
}
if ( !Random[FlameT](0,int(40*(default.alpha-alpha))) )
{
let s = Spawn("SWWMSmoke",pos);
s.vel = (FRandom[FlameT](-0.2,0.2),FRandom[FlameT](-0.2,0.2),FRandom[FlameT](-0.2,0.2));
s.vel += vel*0.3;
s.alpha *= alpha*4;
s.scale *= 0.5+abs(scale.x)*(.5+GetAge()/6.);
}
}
Default
{
RenderStyle "Add";
Speed 2;
Radius 4;
Height 4;
Alpha 0.3;
Scale 0.6;
+NOBLOCKMAP;
+NOGRAVITY;
+NOFRICTION;
+SLIDESONWALLS;
+ACTIVATEPCROSS;
+ACTIVATEIMPACT;
+NOTELEPORT;
+FORCEXYBILLBOARD;
+ROLLSPRITE;
+ROLLCENTER;
+DROPOFF;
+NOBLOCKMONST;
+DONTSPLASH;
}
States
{
Spawn:
XFLM ABCDEFGHIJKLMNOPQRST 1 Bright
{
A_Flame();
A_SetScale(scale.x*0.98);
A_FadeOut(0.01);
vel.z += 0.1;
}
Wait;
}
}
Class FlamingChunk : Actor
{
double rollvel;
OnFire myfire;
Vector3 oldvel;
int deadtimer;
Actor lasthit;
override void PostBeginPlay()
{
Super.PostBeginPlay();
rollvel = FRandom[FlameT](10,30)*RandomPick[FlameT](-1,1);
Scale *= FRandom[FlameT](.8,1.2);
if ( waterlevel <= 0 ) myfire = OnFire.Apply(self,target,int(120*scale.x),6);
frame = Random[FlameT](0,5);
}
override int DoSpecialDamage( Actor target, int damage, Name damagetype )
{
if ( target != lasthit )
{
OnFire.Apply(target,self.target,myfire?myfire.Amount:1);
lasthit = target;
}
return damage;
}
override void Tick()
{
oldvel = vel;
Super.Tick();
if ( isFrozen() ) return;
if ( InStateSequence(CurState,ResolveState("Death")) )
{
deadtimer++;
if ( deadtimer > 300 ) A_FadeOut(0.05);
return;
}
}
void A_HandleBounce()
{
bHITOWNER = true;
lasthit = null;
Vector3 HitNormal = -vel.unit();
F3DFloor ff;
if ( BlockingFloor )
{
// find closest 3d floor for its normal
for ( int i=0; i<CurSector.Get3DFloorCount(); i++ )
{
if ( !(CurSector.Get3DFloor(i).top.ZAtPoint(pos.xy) ~== floorz) ) continue;
ff = CurSector.Get3DFloor(i);
break;
}
if ( ff ) HitNormal = -ff.top.Normal;
else HitNormal = BlockingFloor.floorplane.Normal;
}
else if ( BlockingCeiling )
{
// find closest 3d floor for its normal
for ( int i=0; i<CurSector.Get3DFloorCount(); i++ )
{
if ( !(CurSector.Get3DFloor(i).bottom.ZAtPoint(pos.xy) ~== ceilingz) ) continue;
ff = CurSector.Get3DFloor(i);
break;
}
if ( ff ) HitNormal = -ff.bottom.Normal;
else HitNormal = BlockingCeiling.ceilingplane.Normal;
}
else if ( BlockingLine )
{
HitNormal = (-BlockingLine.delta.y,BlockingLine.delta.x,0).unit();
if ( !BlockingLine.sidedef[1] || (CurSector == BlockingLine.frontsector) )
HitNormal *= -1;
}
else if ( BlockingMobj )
{
Vector3 diff = level.Vec3Diff(BlockingMobj.Vec3Offset(0,0,BlockingMobj.Height/2),pos);
HitNormal = diff.unit();
}
// undo the bounce, we need to hook in our own
vel = oldvel;
// re-do the bounce with our formula
vel = .8*((vel dot HitNormal)*HitNormal*-1.2+vel);
gravity = .35;
if ( (vel.length() < 5) && (pos.z <= floorz) )
{
ClearBounce();
ExplodeMissile();
}
rollvel = FRandom[FlameT](10,30)*RandomPick[FlameT](-1,1);
}
Default
{
Obituary "$O_SPREADGUN_BLACK";
DamageFunction 1;
DamageType 'Fire';
Radius 4;
Height 4;
Speed 15;
Gravity 0.2;
PROJECTILE;
-NOGRAVITY;
+ROLLSPRITE;
+ROLLCENTER;
+EXPLODEONWATER;
+FORCERADIUSDMG;
+FORCEXYBILLBOARD;
+NODAMAGETHRUST;
+INTERPOLATEANGLES;
+RIPPER;
+BLOODLESSIMPACT;
+NOTELEPORT;
+BOUNCEONWALLS;
+BOUNCEONFLOORS;
+BOUNCEONCEILINGS;
+USEBOUNCESTATE;
BounceFactor 1.0;
}
States
{
Spawn:
JUNK # 1 { roll += rollvel; }
Wait;
Bounce:
JUNK # 0 A_HandleBounce();
Goto Spawn;
Death:
JUNK # -1 { bMOVEWITHSECTOR = true; }
Stop;
Dummy:
JUNK ABCDEF -1;
Stop;
}
}
Class BallImpact : Actor
{
Default
{
Radius 0.1;
Height 0;
+NOGRAVITY;
+NOCLIP;
+DONTSPLASH;
+NOTELEPORT;
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
A_QuakeEx(3,3,3,12,0,200,"",QF_RELATIVE|QF_SCALEDOWN,falloff:100,rollIntensity:.3);
A_StartSound("spreadgun/ball",CHAN_VOICE);
A_SprayDecal("WallCrack",-20);
int numpt = Random[Spreadgun](5,10);
Vector3 x = (cos(angle)*cos(pitch),sin(angle)*cos(pitch),-sin(pitch));
for ( int i=0; i<numpt; i++ )
{
Vector3 pvel = (x+(FRandom[Spreadgun](-.8,.8),FRandom[Spreadgun](-.8,.8),FRandom[Spreadgun](-.8,.8))).unit()*FRandom[Spreadgun](.1,1.2);
let s = Spawn("SWWMSmoke",pos);
s.vel = pvel;
s.SetShade(Color(1,1,1)*Random[Spreadgun](128,192));
}
numpt = Random[Spreadgun](4,12);
for ( int i=0; i<numpt; i++ )
{
Vector3 pvel = (FRandom[Spreadgun](-1,1),FRandom[Spreadgun](-1,1),FRandom[Spreadgun](-1,1)).unit()*FRandom[Spreadgun](2,8);
let s = Spawn("SWWMSpark",pos);
s.vel = pvel;
}
numpt = Random[Spreadgun](4,8);
for ( int i=0; i<numpt; i++ )
{
Vector3 pvel = (FRandom[Spreadgun](-1,1),FRandom[Spreadgun](-1,1),FRandom[Spreadgun](-1,1)).unit()*FRandom[Spreadgun](2,8);
let s = Spawn("SWWMChip",pos);
s.vel = pvel;
}
Destroy();
}
}
Class TheBall : Actor
{
double heat;
int deadtimer;
Vector3 oldvel;
Actor lasthit;
Default
{
Obituary "$O_SPREADGUN_PURPLE";
+NOBLOCKMAP;
+BOUNCEONWALLS;
+BOUNCEONFLOORS;
+BOUNCEONCEILINGS;
+USEBOUNCESTATE;
+MISSILE;
+NODAMAGETHRUST;
+NOTELEPORT;
-NOGRAVITY;
Speed 80;
Gravity 0.1;
BounceFactor 1.0;
Radius 2;
Height 2;
}
override int SpecialMissileHit( Actor victim )
{
if ( (vel.length() <= 5) || ((victim == target) && !bHITOWNER) || (victim == lasthit) || (!victim.bSHOOTABLE && !victim.bSOLID) )
return 1;
// check if we should rip or bounce
// girthitude
double girth = (victim.radius+victim.height)/2.*max(50,victim.mass);
// how hard this damn thing is going to slam
double slamforce = vel.length()*350.+heat*120;
SWWMHandler.DoKnockback(victim,vel.unit(),slamforce);
bool bleeds = (victim && !victim.bINVULNERABLE && !victim.bNOBLOOD && victim.bSHOOTABLE);
int dmg = int(vel.length()*4.2+heat*80);
dmg = victim.DamageMobj(self,target,dmg,'Concussion',DMG_THRUSTLESS);
Vector3 dir = -vel.unit();
// slam jam
if ( bleeds )
{
A_StartSound("spreadgun/ballf",CHAN_VOICE,CHANF_OVERLAP,(vel.length()/75.)**.5);
victim.TraceBleed(dmg,self);
SpawnBlood(pos,atan2(dir.y,dir.x),dmg);
}
else
{
A_StartSound("spreadgun/ball",CHAN_VOICE,CHANF_OVERLAP,(vel.length()/75.)**.5);
if ( vel.length() > 15 )
{
let s = Spawn("BallImpact",pos);
s.angle = atan2(dir.y,dir.x);
s.pitch = asin(-dir.z);
}
}
if ( slamforce > girth )
{
vel *= .8;
return 1;
}
// force bounce
BlockingMobj = victim;
A_HandleBounce();
lasthit = victim;
// pretend to pass through
return 1;
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
A_StartSound("pusher/fly",CHAN_WEAPON,CHANF_LOOPING,.6,3.,2.);
heat = 1.;
}
override void Tick()
{
oldvel = vel;
Super.Tick();
if ( isFrozen() ) return;
if ( InStateSequence(CurState,ResolveState("Death")) )
{
deadtimer++;
if ( deadtimer > 300 ) A_FadeOut(0.05);
return;
}
heat -= 0.004+0.0004*vel.length();
A_SoundVolume(CHAN_WEAPON,vel.length()/75.);
if ( heat <= 0 ) return;
let s = Spawn("SWWMSmoke",pos);
s.alpha *= heat;
}
void A_HandleBounce()
{
bHITOWNER = true;
lasthit = null;
Vector3 HitNormal = -vel.unit();
F3DFloor ff;
if ( BlockingFloor )
{
// find closest 3d floor for its normal
for ( int i=0; i<CurSector.Get3DFloorCount(); i++ )
{
if ( !(CurSector.Get3DFloor(i).top.ZAtPoint(pos.xy) ~== floorz) ) continue;
ff = CurSector.Get3DFloor(i);
break;
}
if ( ff ) HitNormal = -ff.top.Normal;
else HitNormal = BlockingFloor.floorplane.Normal;
}
else if ( BlockingCeiling )
{
// find closest 3d floor for its normal
for ( int i=0; i<CurSector.Get3DFloorCount(); i++ )
{
if ( !(CurSector.Get3DFloor(i).bottom.ZAtPoint(pos.xy) ~== ceilingz) ) continue;
ff = CurSector.Get3DFloor(i);
break;
}
if ( ff ) HitNormal = -ff.bottom.Normal;
else HitNormal = BlockingCeiling.ceilingplane.Normal;
}
else if ( BlockingLine )
{
HitNormal = (-BlockingLine.delta.y,BlockingLine.delta.x,0).unit();
if ( !BlockingLine.sidedef[1] || (CurSector == BlockingLine.frontsector) )
HitNormal *= -1;
}
else if ( BlockingMobj )
{
Vector3 diff = level.Vec3Diff(BlockingMobj.Vec3Offset(0,0,BlockingMobj.Height/2),pos);
HitNormal = diff.unit();
}
// undo the bounce, we need to hook in our own
vel = oldvel;
// re-do the bounce with our formula
double bcefact = .9;
if ( BlockingMobj )
{
bcefact *= .7;
if ( !BlockingMobj.bINVULNERABLE && !BlockingMobj.bNOBLOOD )
bcefact *= .6;
}
vel = bcefact*((vel dot HitNormal)*HitNormal*-1.2+vel);
// slam jam
if ( !BlockingMobj )
{
A_StartSound("spreadgun/ball",CHAN_VOICE,CHANF_OVERLAP,max(0.,(vel.length()/60.-.1))**.5);
if ( vel.length() > 15 )
{
let s = Spawn("BallImpact",pos);
s.angle = atan2(HitNormal.y,HitNormal.x);
s.pitch = asin(-HitNormal.z);
}
}
gravity = .35;
if ( (vel.length() < 5) && (pos.z <= floorz) )
{
ClearBounce();
ExplodeMissile();
}
}
States
{
Spawn:
XZW1 A -1;
Stop;
Bounce:
XZW1 A 0 A_HandleBounce();
Goto Spawn;
Death:
XZW1 A -1
{
bMOVEWITHSECTOR = true;
A_StopSound(CHAN_WEAPON);
}
Stop;
}
}
Class Spreadgun : SWWMWeapon
{
bool fired; // shell was used
@ -1190,7 +1784,39 @@ Class Spreadgun : SWWMWeapon
Console.Printf("\cg// TODO Napalm Rounds\c-");
break;
case 5:
Console.Printf("\cg// TODO The Ball\c-");
a = FRandom[Spreadgun](0,360);
s = FRandom[Spreadgun](0,.03);
let b = Spawn("TheBall",origin);
b.target = self;
b.angle = atan2(x2.y,x2.x);
b.pitch = asin(-x2.z);
b.vel = x2*b.speed;
for ( int i=0; i<4; i++ )
{
let s = Spawn("SWWMViewSmoke",origin);
SWWMViewSmoke(s).ofs = (15,3,-3);
s.target = self;
s.SetShade(Color(1,1,1)*Random[Spreadgun](96,192));
s.alpha *= 0.4;
}
for ( int i=0; i<8; i++ )
{
let s = Spawn("SWWMSmoke",origin);
s.scale *= .6;
s.alpha *= .25;
s.SetShade(Color(1,1,1)*Random[Spreadgun](96,192));
s.vel += vel*.5+x*FRandom[Spreadgun](3.,5.);
}
for ( int i=0; i<8; i++ )
{
let s = Spawn("SWWMSpark",origin);
s.scale *= .2;
s.alpha *= .4;
s.vel += vel*.5+x*FRandom[Spreadgun](4.,8.)+y*FRandom[Spreadgun](-1,1)+z*FRandom[Spreadgun](-1,1);
}
SWWMHandler.DoKnockback(self,-x,2500.);
A_Recoil(1.);
invoker.srecoil = -.35;
break;
case 6:
Console.Printf("\cg// TODO Golden Shell\c-");