Frantic attempt at preventing desyncs by adding "FloatBobPhase 0" to a lot of actors. Boss brains count towards kills (prevents players from getting an "all kills" bonus without having actually beaten the boss proper).
1488 lines
44 KiB
Text
1488 lines
44 KiB
Text
// Inventory stuff
|
|
Mixin Class SWWMAutoUseFix
|
|
{
|
|
override bool HandlePickup( Inventory item )
|
|
{
|
|
if ( GetClass() == item.GetClass() )
|
|
{
|
|
if ( Use(true) ) Amount--;
|
|
// sell excess if there's a price
|
|
if ( bALWAYSPICKUP && (Amount+item.Amount > MaxAmount) && (Stamina > 0) )
|
|
{
|
|
int sellprice = int(Stamina*.5);
|
|
SWWMScoreObj.Spawn(sellprice,Owner.Vec3Offset(FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8)+Owner.Height/2),Font.CR_GOLD);
|
|
SWWMCredits.Give(Owner.player,sellprice);
|
|
if ( Owner.player )
|
|
Console.Printf(StringTable.Localize("$SWWM_SELLEXTRA"),Owner.player.GetUserName(),GetTag(),sellprice);
|
|
}
|
|
}
|
|
return Super.HandlePickup(item);
|
|
}
|
|
}
|
|
|
|
Class CrossLineFinder : LineTracer
|
|
{
|
|
Array<Line> clines;
|
|
Array<int> csides;
|
|
|
|
override ETraceStatus TraceCallback()
|
|
{
|
|
if ( (Results.HitType == TRACE_HitWall) && (Results.HitLine.activation&SPAC_Cross) )
|
|
{
|
|
clines.Push(Results.HitLine);
|
|
csides.Push(Results.Side);
|
|
}
|
|
return TRACE_Skip;
|
|
}
|
|
}
|
|
|
|
Mixin Class SWWMUseToPickup
|
|
{
|
|
// allow pickup by use
|
|
override bool Used( Actor user )
|
|
{
|
|
Vector3 itempos = Vec3Offset(0,0,Height/2),
|
|
userpos = user.Vec2OffsetZ(0,0,user.player.viewz);
|
|
// test vertical range
|
|
Vector3 diff = level.Vec3Diff(user.Vec3Offset(0,0,user.Height/2),Vec3Offset(0,0,Height/2));
|
|
double rang = user.player?PlayerPawn(user.player.mo).UseRange:(user.Height/2);
|
|
if ( abs(diff.z) > rang ) return false;
|
|
Touch(user);
|
|
// we got picked up
|
|
if ( bDestroyed || Owner || !bSPECIAL )
|
|
{
|
|
Vector3 tracedir = level.Vec3Diff(userpos,itempos);
|
|
double dist = tracedir.length();
|
|
tracedir /= dist;
|
|
let cf = new("CrossLineFinder");
|
|
cf.Trace(userpos,level.PointInSector(userpos.xy),tracedir,dist,0);
|
|
// trigger all player cross lines found between user and item
|
|
for ( int i=0; i<cf.clines.Size(); i++ )
|
|
cf.clines[i].Activate(user,cf.csides[i],SPAC_Cross);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
Mixin Class SWWMOverlapPickupSound
|
|
{
|
|
// overlap sounds
|
|
override void PlayPickupSound( Actor toucher )
|
|
{
|
|
let hnd = SWWMHandler(EventHandler.Find("SWWMHandler"));
|
|
if ( hnd )
|
|
{
|
|
if ( hnd.lastpickuptic[toucher.PlayerNumber()] == gametic )
|
|
return; // don't play if picked up on the same exact tic (overlapping items)
|
|
hnd.lastpickuptic[toucher.PlayerNumber()] = gametic;
|
|
}
|
|
double atten;
|
|
int flags = CHANF_OVERLAP|CHANF_MAYBE_LOCAL;
|
|
if ( bNoAttenPickupSound ) atten = ATTN_NONE;
|
|
else atten = ATTN_NORM;
|
|
if ( toucher && toucher.CheckLocalView() )
|
|
flags |= CHANF_NOPAUSE;
|
|
toucher.A_StartSound(PickupSound,CHAN_ITEM,flags,1.,atten);
|
|
}
|
|
}
|
|
|
|
// Base class for all SWWM Armors
|
|
Class SWWMArmor : Armor abstract
|
|
{
|
|
int priority;
|
|
String drainmsg;
|
|
Class<SWWMSpareArmor> parent;
|
|
|
|
Property ArmorPriority : priority;
|
|
Property DrainMessage : drainmsg;
|
|
Property GiverArmor : parent;
|
|
|
|
Default
|
|
{
|
|
+INVENTORY.AUTOACTIVATE;
|
|
+INVENTORY.UNTOSSABLE;
|
|
+INVENTORY.UNDROPPABLE;
|
|
+INVENTORY.KEEPDEPLETED;
|
|
+INVENTORY.ALWAYSPICKUP;
|
|
}
|
|
override void AttachToOwner( Actor other )
|
|
{
|
|
Super.AttachToOwner(other);
|
|
// find last armor that's better than us
|
|
Inventory found = null;
|
|
bool foundarmor = false;
|
|
for ( Inventory i=other.Inv; i; i=i.Inv )
|
|
{
|
|
if ( (i is 'SWWMArmor') && (i != self) ) foundarmor = true;
|
|
if ( !(i is 'SWWMArmor') || (i == self) || (SWWMArmor(i).priority < priority) ) continue;
|
|
found = i;
|
|
}
|
|
if ( !found && !foundarmor )
|
|
{
|
|
// check if first item in inventory is health or a sandwich
|
|
if ( (other.Inv is 'SWWMHealth') || (other.Inv is 'GrilledCheeseSandwich') )
|
|
{
|
|
// place ourselves before it
|
|
Inv = other.Inv;
|
|
other.Inv = self;
|
|
return;
|
|
}
|
|
// find first item with health or sandwich after it
|
|
for ( Inventory i=other.Inv; i; i=i.Inv )
|
|
{
|
|
if ( (i == self) || (!(i.Inv is 'SWWMHealth' ) && !(i.Inv is 'GrilledCheeseSandwich')) ) continue;
|
|
found = i;
|
|
break;
|
|
}
|
|
}
|
|
if ( !found && !foundarmor )
|
|
{
|
|
// find last of either invinciball or ragekit power
|
|
for ( Inventory i=other.Inv; i; i=i.Inv )
|
|
{
|
|
if ( !(i is 'InvinciballPower') && !(i is 'RagekitPower') ) continue;
|
|
found = i;
|
|
}
|
|
}
|
|
if ( !found ) return;
|
|
// place ourselves right after it
|
|
Inventory saved = found.Inv;
|
|
found.Inv = self;
|
|
other.Inv = Inv;
|
|
Inv = saved;
|
|
}
|
|
// for subclasses
|
|
virtual int HandleDamage( int damage, Name damageType, int flags )
|
|
{
|
|
return damage;
|
|
}
|
|
override void AbsorbDamage( int damage, Name damageType, out int newdamage, Actor inflictor, Actor source, int flags )
|
|
{
|
|
int saved;
|
|
if ( (amount > 0) && !DamageTypeDefinition.IgnoreArmor(damageType) && (damage > 0) )
|
|
{
|
|
SWWMHandler.DoFlash(Owner,Color(int(clamp(damage*.15,1,16)),255,224,192),3);
|
|
Owner.A_StartSound("armor/hit",CHAN_BODY,CHANF_DEFAULT,clamp(damage*.03,0.,1.),2.5);
|
|
saved = HandleDamage(damage,damageType,flags);
|
|
int healed = max(0,saved-damage);
|
|
saved = min(saved,damage);
|
|
if ( amount <= saved ) saved = amount;
|
|
newdamage -= saved;
|
|
if ( healed > 0 ) Owner.GiveBody(healed);
|
|
if ( (swwm_strictuntouchable == 1) && (saved > 0) && Owner.player )
|
|
{
|
|
let hnd = SWWMHandler(EventHandler.Find("SWWMHandler"));
|
|
if ( hnd ) hnd.tookdamage[Owner.PlayerNumber()] = true;
|
|
}
|
|
amount -= saved;
|
|
damage = newdamage;
|
|
bool shouldautouse = false;
|
|
if ( swwm_enforceautousearmor == 1 ) shouldautouse = true;
|
|
else if ( swwm_enforceautousearmor == -1 ) shouldautouse = false;
|
|
else shouldautouse = CVar.GetCVar('swwm_autousearmor',Owner.player).GetBool();
|
|
if ( (amount <= (MaxAmount-default.Amount)) && (Owner.CountInv(parent) > 0) && shouldautouse )
|
|
{
|
|
if ( GetDefaultByType(parent).UseSound ) Owner.A_StartSound(GetDefaultByType(parent).UseSound,CHAN_ITEMEXTRA,CHANF_DEFAULT,.6);
|
|
int tgive = 0;
|
|
bool acc = CVar.GetCVar('swwm_accdamage',players[consoleplayer]).GetBool();
|
|
while ( (amount <= (MaxAmount-default.Amount)) && (Owner.CountInv(parent) > 0) )
|
|
{
|
|
if ( acc ) tgive += default.Amount;
|
|
else SWWMScoreObj.Spawn(default.Amount,Owner.Vec3Offset(FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8)+Owner.Height/2),Font.CR_GREEN);
|
|
Amount += default.Amount;
|
|
Owner.TakeInventory(parent,1);
|
|
// absorb the extra damage too
|
|
saved = HandleDamage(damage,damageType,flags);
|
|
healed = max(0,saved-damage);
|
|
saved = min(saved,damage);
|
|
if ( amount <= saved ) saved = amount;
|
|
newdamage -= saved;
|
|
if ( healed > 0 ) Owner.GiveBody(healed);
|
|
amount -= saved;
|
|
damage = newdamage;
|
|
}
|
|
if ( acc ) SWWMScoreObj.Spawn(tgive,Owner.Vec3Offset(FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8)+Owner.Height/2),Font.CR_GREEN);
|
|
}
|
|
else if ( amount <= 0 )
|
|
{
|
|
if ( damage > 0 ) newdamage = ApplyDamageFactors(GetClass(),damageType,damage,damage);
|
|
if ( Owner.CheckLocalView() && (drainmsg != "") ) Console.Printf(StringTable.Localize(drainmsg));
|
|
DepleteOrDestroy();
|
|
return;
|
|
}
|
|
}
|
|
if ( damage > 0 ) newdamage = ApplyDamageFactors(GetClass(),damageType,damage,damage);
|
|
}
|
|
}
|
|
|
|
// gives armor when used
|
|
Class SWWMSpareArmor : Inventory abstract
|
|
{
|
|
Mixin SWWMAutoUseFix;
|
|
Mixin SWWMUseToPickup;
|
|
Mixin SWWMOverlapPickupSound;
|
|
|
|
Class<SWWMArmor> giveme;
|
|
|
|
Property GiveArmor : giveme;
|
|
|
|
override bool Use( bool pickup )
|
|
{
|
|
bool shouldautouse = false;
|
|
if ( swwm_enforceautousearmor == 1 ) shouldautouse = true;
|
|
else if ( swwm_enforceautousearmor == -1 ) shouldautouse = false;
|
|
else shouldautouse = CVar.GetCVar('swwm_autousearmor',Owner.player).GetBool();
|
|
let cur = Owner.FindInventory(giveme);
|
|
if ( !cur || (!pickup && (cur.Amount < cur.MaxAmount)) || (GetDefaultByType(giveme).Amount+cur.Amount <= cur.MaxAmount) )
|
|
{
|
|
if ( pickup && ((Owner.player == players[consoleplayer]) || bBigPowerup) ) Owner.A_StartSound(UseSound,CHAN_ITEMEXTRA);
|
|
Owner.GiveInventory(giveme,GetDefaultByType(giveme).Amount);
|
|
SWWMHandler.ArmorFlash(Owner.PlayerNumber());
|
|
SWWMScoreObj.Spawn(GetDefaultByType(giveme).Amount,Owner.Vec3Offset(FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8)+Owner.Height/2),Font.CR_GREEN);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
Default
|
|
{
|
|
+INVENTORY.INVBAR;
|
|
+INVENTORY.ISARMOR;
|
|
+INVENTORY.AUTOACTIVATE;
|
|
Inventory.MaxAmount 5;
|
|
Inventory.InterHubAmount 5;
|
|
Inventory.PickupFlash "SWWMGreenPickupFlash";
|
|
+FLOATBOB;
|
|
FloatBobStrength 0.25;
|
|
}
|
|
}
|
|
|
|
Class SWWMHealth : Inventory abstract
|
|
{
|
|
Mixin SWWMAutoUseFix;
|
|
Mixin SWWMUseToPickup;
|
|
Mixin SWWMOverlapPickupSound;
|
|
|
|
// can't use the Health class for whatever reason
|
|
// nice parser you got there I guess?
|
|
Class<Inventory> giveme;
|
|
|
|
Property GiveHealth : giveme;
|
|
|
|
override void AttachToOwner( Actor other )
|
|
{
|
|
Super.AttachToOwner(other);
|
|
// find last health item that's better than us
|
|
Inventory found = null;
|
|
for ( Inventory i=other.Inv; i; i=i.Inv )
|
|
{
|
|
if ( !(i is 'SWWMHealth') || (i == self) || (GetDefaultByType(SWWMHealth(i).giveme).Amount < GetDefaultByType(giveme).Amount) ) continue;
|
|
found = i;
|
|
}
|
|
if ( !found )
|
|
{
|
|
// find last armor item
|
|
for ( Inventory i=other.Inv; i; i=i.Inv )
|
|
{
|
|
if ( !(i is 'SWWMArmor') ) continue;
|
|
found = i;
|
|
}
|
|
}
|
|
if ( !found )
|
|
{
|
|
// check if the first item in inventory is a sandwich
|
|
if ( other.Inv is 'GrilledCheeseSandwich' )
|
|
{
|
|
// place ourselves before it
|
|
Inv = other.Inv;
|
|
other.Inv = self;
|
|
return;
|
|
}
|
|
// find first item next to a sandwich
|
|
for ( Inventory i=other.Inv; i; i=i.Inv )
|
|
{
|
|
if ( (i == self) || !(i.Inv is 'GrilledCheeseSandwich') ) continue;
|
|
found = i;
|
|
break;
|
|
}
|
|
}
|
|
if ( !found )
|
|
{
|
|
// find last of either invinciball or ragekit power
|
|
for ( Inventory i=other.Inv; i; i=i.Inv )
|
|
{
|
|
if ( !(i is 'InvinciballPower') && !(i is 'RagekitPower') ) continue;
|
|
found = i;
|
|
}
|
|
}
|
|
if ( !found ) return;
|
|
// place ourselves right after it
|
|
Inventory saved = found.Inv;
|
|
found.Inv = self;
|
|
other.Inv = Inv;
|
|
Inv = saved;
|
|
}
|
|
|
|
override bool Use( bool pickup )
|
|
{
|
|
bool shouldautouse = false;
|
|
if ( swwm_enforceautousehealth == 1 ) shouldautouse = true;
|
|
else if ( swwm_enforceautousehealth == -1 ) shouldautouse = false;
|
|
else shouldautouse = CVar.GetCVar('swwm_autousehealth',Owner.player).GetBool();
|
|
if ( Owner.Health >= GetDefaultByType(giveme).MaxAmount ) return false;
|
|
if ( pickup && ((Owner.player == players[consoleplayer]) || bBigPowerup) ) Owner.A_StartSound(UseSound,CHAN_ITEMEXTRA);
|
|
SWWMHandler.HealthFlash(Owner.PlayerNumber());
|
|
Owner.GiveInventory(giveme,GetDefaultByType(giveme).Amount);
|
|
SWWMScoreObj.Spawn(GetDefaultByType(giveme).Amount,Owner.Vec3Offset(FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8)+Owner.Height/2),Font.CR_BLUE);
|
|
AutoUseExtra();
|
|
return true;
|
|
}
|
|
|
|
virtual void AutoUseExtra()
|
|
{
|
|
}
|
|
|
|
override void DoEffect()
|
|
{
|
|
Super.DoEffect();
|
|
if ( Amount <= 0 ) DepleteOrDestroy();
|
|
}
|
|
|
|
override void AbsorbDamage( int damage, Name damageType, out int newdamage, Actor inflictor, Actor source, int flags )
|
|
{
|
|
if ( Owner.ApplyDamageFactor(damageType,damage) <= 0 )
|
|
return; // this damage type is ignored by the player, so it does not affect us
|
|
if ( damageType == 'EndLevel' )
|
|
return; // don't trigger on endlevel damage
|
|
bool shouldautouse = false;
|
|
if ( swwm_enforceautousehealth == 1 ) shouldautouse = true;
|
|
else if ( swwm_enforceautousehealth == -1 ) shouldautouse = false;
|
|
else shouldautouse = CVar.GetCVar('swwm_autousehealth',Owner.player).GetBool();
|
|
if ( !shouldautouse && !bBIGPOWERUP ) return;
|
|
if ( (Owner.Health-damage <= (GetDefaultByType(giveme).MaxAmount-GetDefaultByType(giveme).Amount)) )
|
|
{
|
|
newdamage = damage;
|
|
// lesser healing items can't prevent lethal damage
|
|
// bigger healing items only autoactivate on lethal damage
|
|
if ( !bBIGPOWERUP && (Owner.Health-damage <= 0) )
|
|
return;
|
|
else if ( bBIGPOWERUP && (Owner.Health-damage > 0) )
|
|
return;
|
|
if ( (swwm_strictuntouchable == 1) && Owner.player )
|
|
{
|
|
let hnd = SWWMHandler(EventHandler.Find("SWWMHandler"));
|
|
if ( hnd ) hnd.tookdamage[Owner.PlayerNumber()] = true;
|
|
}
|
|
if ( ((Owner.player == players[consoleplayer]) || bBigPowerup) ) Owner.A_StartSound(UseSound,CHAN_ITEMEXTRA);
|
|
int tgive = 0;
|
|
bool acc = CVar.GetCVar('swwm_accdamage',players[consoleplayer]).GetBool();
|
|
while ( (Amount > 0) && (newdamage > 0) )
|
|
{
|
|
if ( acc ) tgive += GetDefaultByType(giveme).Amount;
|
|
else SWWMScoreObj.Spawn(GetDefaultByType(giveme).Amount,Owner.Vec3Offset(FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8)+Owner.Height/2),Font.CR_BLUE);
|
|
newdamage = newdamage-GetDefaultByType(giveme).Amount;
|
|
if ( newdamage < 0 ) Owner.GiveBody(-newdamage,GetDefaultByType(giveme).MaxAmount);
|
|
newdamage = max(0,newdamage);
|
|
AutoUseExtra();
|
|
Amount--;
|
|
}
|
|
if ( acc ) SWWMScoreObj.Spawn(tgive,Owner.Vec3Offset(FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8)+Owner.Height/2),Font.CR_BLUE);
|
|
}
|
|
else newdamage = damage;
|
|
}
|
|
|
|
Default
|
|
{
|
|
+INVENTORY.INVBAR;
|
|
+INVENTORY.ISHEALTH;
|
|
+INVENTORY.AUTOACTIVATE;
|
|
Inventory.MaxAmount 5;
|
|
Inventory.InterHubAmount 5;
|
|
Inventory.UseSound "misc/health_pkup";
|
|
Inventory.PickupFlash "SWWMBluePickupFlash";
|
|
+FLOATBOB;
|
|
FloatBobStrength 0.25;
|
|
}
|
|
}
|
|
|
|
// Base casing classes
|
|
Class SWWMCasing : Actor abstract
|
|
{
|
|
SWWMCasing prevcasing, nextcasing;
|
|
bool killme;
|
|
int numbounces;
|
|
double pitchvel, anglevel;
|
|
double heat;
|
|
|
|
Default
|
|
{
|
|
Radius 2;
|
|
Height 2;
|
|
+NOBLOCKMAP;
|
|
+MISSILE;
|
|
+DROPOFF;
|
|
+MOVEWITHSECTOR;
|
|
+THRUACTORS;
|
|
+USEBOUNCESTATE;
|
|
+INTERPOLATEANGLES;
|
|
+NOTELEPORT;
|
|
+ROLLSPRITE;
|
|
+ROLLCENTER;
|
|
Mass 1;
|
|
Gravity 0.35;
|
|
BounceType "Hexen";
|
|
WallBounceFactor 0.65;
|
|
BounceFactor 0.65;
|
|
BounceSound "explodium/casing";
|
|
FloatBobPhase 0;
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
pitchvel = FRandom[Junk](10,30)*RandomPick[Junk](-1,1);
|
|
anglevel = FRandom[Junk](10,30)*RandomPick[Junk](-1,1);
|
|
heat = 1.0;
|
|
SWWMHandler.QueueCasing(self);
|
|
}
|
|
override void OnDestroy()
|
|
{
|
|
SWWMHandler.DeQueueCasing(self);
|
|
Super.OnDestroy();
|
|
}
|
|
override void Tick()
|
|
{
|
|
Super.Tick();
|
|
if ( isFrozen() ) return;
|
|
if ( killme ) A_FadeOut(.01);
|
|
if ( waterlevel > 0 )
|
|
{
|
|
vel.xy *= .98;
|
|
anglevel *= .98;
|
|
pitchvel *= .98;
|
|
}
|
|
if ( heat <= 0 ) return;
|
|
let s = Spawn("SWWMSmallSmoke",pos);
|
|
s.alpha *= heat;
|
|
heat -= 0.05;
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
XZW1 A 1
|
|
{
|
|
angle += anglevel;
|
|
pitch += pitchvel;
|
|
}
|
|
Loop;
|
|
Bounce:
|
|
XZW1 A 0
|
|
{
|
|
pitchvel = FRandom[Junk](10,30)*RandomPick[Junk](-1,1);
|
|
anglevel = FRandom[Junk](10,30)*RandomPick[Junk](-1,1);
|
|
vel = (vel.unit()+(FRandom[Junk](-.2,.2),FRandom[Junk](-.2,.2),FRandom[Junk](-.2,.2))).unit()*vel.length();
|
|
if ( numbounces && ((numbounces > 3) || (Random[Junk](1,20) < 17) || (vel.z > -1.4)) )
|
|
{
|
|
ClearBounce();
|
|
ExplodeMissile();
|
|
}
|
|
numbounces++;
|
|
}
|
|
Goto Spawn;
|
|
Death:
|
|
XZW1 B -1
|
|
{
|
|
pitch = roll = 0;
|
|
angle = FRandom[Junk](0,360);
|
|
}
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
Class SWWMBulletImpact : Actor
|
|
{
|
|
Default
|
|
{
|
|
RenderStyle "Add";
|
|
Radius 0.1;
|
|
Height 0;
|
|
+NOGRAVITY;
|
|
+NOBLOCKMAP;
|
|
+DONTSPLASH;
|
|
+NOTELEPORT;
|
|
+NOINTERACTION;
|
|
Scale 0.25;
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
A_SprayDecal("Pock",-20);
|
|
int numpt = int(Random[Junk](5,10)*scale.x*4);
|
|
Vector3 x = (cos(angle)*cos(pitch),sin(angle)*cos(pitch),-sin(pitch));
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
Vector3 pvel = (x+(FRandom[Junk](-.8,.8),FRandom[Junk](-.8,.8),FRandom[Junk](-.8,.8))).unit()*FRandom[Junk](0.1,1.2);
|
|
let s = Spawn("SWWMSmoke",pos+x*2);
|
|
s.vel = pvel;
|
|
s.SetShade(Color(1,1,1)*Random[Junk](128,192));
|
|
}
|
|
numpt = int(Random[Junk](3,8)*scale.x*4);
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
Vector3 pvel = (FRandom[Junk](-1,1),FRandom[Junk](-1,1),FRandom[Junk](-1,1)).unit()*FRandom[Junk](2,8);
|
|
let s = Spawn("SWWMSpark",pos+x*2);
|
|
s.vel = pvel;
|
|
}
|
|
numpt = int(Random[Junk](2,5)*scale.x*4);
|
|
for ( int i=0; i<numpt; i++ )
|
|
{
|
|
Vector3 pvel = (FRandom[Junk](-1,1),FRandom[Junk](-1,1),FRandom[Junk](-1,1)).unit()*FRandom[Junk](2,8);
|
|
let s = Spawn("SWWMChip",pos+x*2);
|
|
s.vel = pvel;
|
|
}
|
|
if ( !Random[Junk](0,3) ) A_StartSound("bullet/ricochet",CHAN_VOICE,attenuation:2.5);
|
|
else A_StartSound("bullet/hit",CHAN_VOICE,attenuation:3.0);
|
|
Spawn("InvisibleSplasher",pos);
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
TNT1 A 1;
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
Class SWWMWeaponLight : DynamicLight
|
|
{
|
|
int cnt;
|
|
Default
|
|
{
|
|
DynamicLight.Type "Point";
|
|
args 255,224,64,150;
|
|
}
|
|
override void Tick()
|
|
{
|
|
Super.Tick();
|
|
if ( !target )
|
|
{
|
|
Destroy();
|
|
return;
|
|
}
|
|
if ( target.player )
|
|
{
|
|
Vector3 x, y, z, origin;
|
|
[x, y, z] = swwm_CoordUtil.GetAxes(target.pitch,target.angle,target.roll);
|
|
origin = level.Vec3Offset(target.Vec2OffsetZ(0,0,target.player.viewz),x*12);
|
|
SetOrigin(origin,true);
|
|
}
|
|
else SetOrigin(target.pos,true);
|
|
if ( cnt++ > 2 ) Destroy();
|
|
}
|
|
}
|
|
|
|
Class PunchImpact : Actor
|
|
{
|
|
Default
|
|
{
|
|
Radius 0.1;
|
|
Height 0;
|
|
+NOGRAVITY;
|
|
+NOCLIP;
|
|
+DONTSPLASH;
|
|
+NOTELEPORT;
|
|
+NOINTERACTION;
|
|
}
|
|
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,CHANF_DEFAULT,bAMBUSH?.6:1.);
|
|
A_SprayDecal("WallCrack",-20);
|
|
int numpt = Random[Ponch](5,10);
|
|
if ( bAMBUSH ) numpt /= 3;
|
|
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);
|
|
if ( bAMBUSH ) numpt /= 3;
|
|
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);
|
|
if ( bAMBUSH ) numpt /= 3;
|
|
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 BigPunchSplash : Actor
|
|
{
|
|
Default
|
|
{
|
|
DamageType 'Melee';
|
|
Radius 0.1;
|
|
Height 0;
|
|
+NOGRAVITY;
|
|
+NOCLIP;
|
|
+DONTSPLASH;
|
|
+NOTELEPORT;
|
|
+NODAMAGETHRUST;
|
|
+FORCERADIUSDMG;
|
|
+NOINTERACTION;
|
|
}
|
|
override void PostBeginPlay()
|
|
{
|
|
Super.PostBeginPlay();
|
|
SWWMUtility.DoExplosion(self,special1,40000,120,60,ignoreme:target);
|
|
Destroy();
|
|
}
|
|
}
|
|
|
|
Class BigPunchImpact : Actor
|
|
{
|
|
Default
|
|
{
|
|
Radius 0.1;
|
|
Height 0;
|
|
+NOGRAVITY;
|
|
+NOCLIP;
|
|
+DONTSPLASH;
|
|
+NOTELEPORT;
|
|
+NOINTERACTION;
|
|
}
|
|
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,CHANF_DEFAULT,bAMBUSH?.6:1.);
|
|
A_SprayDecal("BigWallCrack",-20);
|
|
int numpt = Random[Ponch](9,16);
|
|
if ( bAMBUSH ) numpt /= 3;
|
|
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);
|
|
if ( bAMBUSH ) numpt /= 3;
|
|
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);
|
|
if ( bAMBUSH ) numpt /= 3;
|
|
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 ReflectedBullet : SWWMCasing {}
|
|
|
|
Class ParriedBuff : Inventory
|
|
{
|
|
Default
|
|
{
|
|
+INVENTORY.UNDROPPABLE;
|
|
+INVENTORY.UNTOSSABLE;
|
|
Inventory.Amount 1;
|
|
Inventory.MaxAmount 1;
|
|
}
|
|
override void DoEffect()
|
|
{
|
|
Super.DoEffect();
|
|
if ( !Owner.bMISSILE && !Owner.bSKULLFLY )
|
|
{
|
|
// lost soul is no longer attacking
|
|
Destroy();
|
|
return;
|
|
}
|
|
if ( special1 <= 0 ) return;
|
|
// smoke trail
|
|
Actor s;
|
|
if ( special1&1 )
|
|
{
|
|
s = Spawn("SWWMHalfSmoke",Owner.pos);
|
|
s.vel = Owner.vel*.3+(FRandom[Ponch](-1,1),FRandom[Ponch](-1,1),FRandom[Ponch](-1,1)).unit()*FRandom[Ponch](.1,.6);
|
|
s.scale *= 1.2;
|
|
s.alpha *= .3;
|
|
}
|
|
if ( special1 > 1 )
|
|
{
|
|
s = Spawn("SWWMHalfSmoke",Owner.pos);
|
|
s.vel = Owner.vel*.3+(FRandom[Ponch](-1,1),FRandom[Ponch](-1,1),FRandom[Ponch](-1,1)).unit()*FRandom[Ponch](.1,1.2);
|
|
s.scale *= 2.;
|
|
s.A_SetRenderStyle(s.alpha,STYLE_AddShaded);
|
|
s.SetShade(Color(4,2,1)*Random[Ponch](32,63));
|
|
}
|
|
}
|
|
}
|
|
|
|
// amplifies damage of parried projectiles
|
|
Class ParryDamageChecker : Inventory
|
|
{
|
|
Default
|
|
{
|
|
+Inventory.UNDROPPABLE;
|
|
+Inventory.UNTOSSABLE;
|
|
+Inventory.UNCLEARABLE;
|
|
Inventory.Amount 1;
|
|
Inventory.MaxAmount 1;
|
|
}
|
|
|
|
override void ModifyDamage( int damage, Name damageType, out int newdamage, bool passive, Actor inflictor, Actor source, int flags )
|
|
{
|
|
Inventory buff;
|
|
if ( inflictor && (buff=inflictor.FindInventory("ParriedBuff")) )
|
|
{
|
|
double mult;
|
|
if ( buff.special1 <= 1 ) mult = 1.5;
|
|
else if ( buff.special1 >= 2 ) mult = 8.;
|
|
if ( buff.special1&1 ) mult *= 2.;
|
|
newdamage = int(damage*mult);
|
|
}
|
|
}
|
|
}
|
|
|
|
Class ParryRing : Actor
|
|
{
|
|
Default
|
|
{
|
|
RenderStyle "Add";
|
|
Scale .1;
|
|
Alpha .3;
|
|
Radius 0.1;
|
|
Height 0;
|
|
+NOGRAVITY;
|
|
+NOBLOCKMAP;
|
|
+FORCEXYBILLBOARD;
|
|
+NOTELEPORT;
|
|
+NOINTERACTION;
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
XRG4 ABCDEFGHIJKLMNOPQRSTUVWX 1 A_SetScale(scale.x*(1+specialf1));
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
Class ParryField : Actor
|
|
{
|
|
int lasteggtic;
|
|
|
|
Default
|
|
{
|
|
+NOGRAVITY;
|
|
+NOCLIP;
|
|
+DONTSPLASH;
|
|
+NOTELEPORT;
|
|
+NOINTERACTION;
|
|
}
|
|
|
|
override void Tick()
|
|
{
|
|
if ( !master )
|
|
{
|
|
Destroy();
|
|
return;
|
|
}
|
|
Vector3 x, y, z, origin;
|
|
[x, y, z] = swwm_CoordUtil.GetAxes(master.pitch,master.angle,master.roll);
|
|
origin = level.Vec3Offset(master.Vec2OffsetZ(0,0,master.player.viewz),x*20);
|
|
SetOrigin(origin,false);
|
|
// check for projectiles to deflect
|
|
let ti = ThinkerIterator.Create("Actor");
|
|
Actor a;
|
|
while ( a = Actor(ti.Next()) )
|
|
{
|
|
if ( !((a.bMISSILE && !a.IsZeroDamage() && (a.target != master)) || a.bSKULLFLY) || a.bTHRUACTORS || (level.Vec3Diff(a.pos,pos).length() > 80) ) continue;
|
|
Vector3 vdir = a.vel;
|
|
Vector3 dir = level.Vec3Diff(master.Vec2OffsetZ(0,0,pos.z),a.pos).unit();
|
|
if ( a.bMISSILE )
|
|
{
|
|
// deflect directly to target
|
|
if ( !Random[Parry](0,2) && a.target )
|
|
dir = level.Vec3Diff(a.pos,a.target.Vec3Offset(0,0,a.target.height/2)).unit();
|
|
// push away
|
|
if ( a.bSEEKERMISSILE ) a.tracer = a.target;
|
|
a.target = master;
|
|
}
|
|
a.GiveInventory("ParriedBuff",1);
|
|
let buff = a.FindInventory("ParriedBuff");
|
|
double mvel = a.vel.length();
|
|
double nspeed = min(100,mvel*FRandom[Parry](1.2,1.4)+20);
|
|
a.angle = atan2(dir.y,dir.x);
|
|
a.pitch = asin(-dir.z);
|
|
let raging = RagekitPower(master.FindInventory("RagekitPower"));
|
|
if ( raging )
|
|
{
|
|
buff.special1 = 2;
|
|
nspeed = min(100,nspeed*2.);
|
|
raging.DoHitFX();
|
|
}
|
|
a.vel = dir*nspeed;
|
|
if ( a.bMISSILE ) a.speed = nspeed;
|
|
let i = Spawn(raging?"BigPunchImpact":"PunchImpact",a.pos);
|
|
i.target = master;
|
|
i.angle = atan2(dir.y,dir.x);
|
|
i.pitch = asin(-dir.z);
|
|
i.bAMBUSH = true;
|
|
A_QuakeEx(3,3,3,10,0,64,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:.2);
|
|
A_StartSound("demolitionist/parry",CHAN_WEAPON);
|
|
if ( (level.maptime > lasteggtic) && !Random[Parry](0,4) )
|
|
{
|
|
for ( int i=1; i<6; i++ )
|
|
{
|
|
let r = Spawn("ParryRing",a.pos);
|
|
r.specialf1 = i*.04;
|
|
}
|
|
buff.special1++;
|
|
A_StartSound("misc/soulsparry",CHAN_ITEM,CHANF_OVERLAP,1.,.5);
|
|
lasteggtic = level.maptime+5;
|
|
}
|
|
}
|
|
if ( --special1 <= 0 ) Destroy();
|
|
}
|
|
}
|
|
|
|
Class UseList
|
|
{
|
|
Line hitline;
|
|
int hitside, hitpart;
|
|
Actor hitactor;
|
|
Vector3 pos;
|
|
}
|
|
|
|
Class UseLineTracer : LineTracer
|
|
{
|
|
Array<UseList> uses;
|
|
Array<Line> glass;
|
|
|
|
static play bool TangibleLine( UseList u )
|
|
{
|
|
if ( u.hitpart != TIER_MIDDLE ) return true; // lower/upper/ffloor
|
|
Line l = u.HitLine;
|
|
if ( !l.sidedef[1] ) return true; // onesided line
|
|
Side s = l.sidedef[u.hitside];
|
|
if ( s.GetTexture(1).IsNull() ) return false; // no midtex
|
|
double ofs = s.GetTextureYOffset(1);
|
|
Vector2 siz = TexMan.GetScaledSize(s.GetTexture(1));
|
|
Vector2 tofs = TexMan.GetScaledOffset(s.GetTexture(1));
|
|
ofs += tofs.y;
|
|
ofs *= s.GetTextureYScale(1);
|
|
siz.y *= s.GetTextureYScale(1);
|
|
SecPlane ceil, flor;
|
|
if ( (l.frontsector.floorplane.ZatPoint(l.v1.p) > l.backsector.floorplane.ZatPoint(l.v1.p))
|
|
&& (l.frontsector.floorplane.ZatPoint(l.v2.p) > l.backsector.floorplane.ZatPoint(l.v2.p)) )
|
|
flor = l.frontsector.floorplane;
|
|
else flor = l.backsector.floorplane;
|
|
if ( (l.frontsector.ceilingplane.ZatPoint(l.v1.p) < l.backsector.ceilingplane.ZatPoint(l.v1.p))
|
|
&& (l.frontsector.ceilingplane.ZatPoint(l.v2.p) < l.backsector.ceilingplane.ZatPoint(l.v2.p)) )
|
|
ceil = l.frontsector.ceilingplane;
|
|
else ceil = l.backsector.ceilingplane;
|
|
double ceilpoint = max(ceil.ZatPoint(l.v1.p),ceil.ZatPoint(l.v2.p));
|
|
double florpoint = min(flor.ZatPoint(l.v1.p),flor.ZatPoint(l.v2.p));
|
|
if ( l.flags&Line.ML_DONTPEGBOTTOM )
|
|
{
|
|
if ( u.pos.z > florpoint+ofs+siz.y ) return false;
|
|
if ( u.pos.z < florpoint+ofs ) return false;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
if ( u.pos.z > ceilpoint+ofs ) return false;
|
|
if ( u.pos.z < (ceilpoint+ofs)-siz.y ) return false;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
override ETraceStatus TraceCallback()
|
|
{
|
|
if ( Results.HitType == TRACE_HitActor )
|
|
{
|
|
let u = new("UseList");
|
|
u.hitline = null;
|
|
u.hitactor = Results.HitActor;
|
|
u.pos = Results.HitPos;
|
|
uses.Push(u);
|
|
return TRACE_Skip;
|
|
}
|
|
if ( Results.HitType == TRACE_HitWall )
|
|
{
|
|
if ( Results.HitLine.Activation&(SPAC_Use|SPAC_UseThrough) )
|
|
{
|
|
let u = new("UseList");
|
|
u.hitline = Results.HitLine;
|
|
u.hitside = Results.Side;
|
|
u.hitpart = Results.FFloor?TIER_FFLOOR:Results.Tier;
|
|
u.hitactor = null;
|
|
u.pos = Results.HitPos;
|
|
uses.Push(u);
|
|
}
|
|
if ( Results.Tier == TIER_Middle )
|
|
{
|
|
if ( !Results.HitLine.sidedef[1] || (Results.HitLine.Flags&(Line.ML_BlockHitscan|Line.ML_BlockEverything|Line.ML_BlockUse)) )
|
|
return TRACE_Stop;
|
|
if ( Results.HitLine.special == GlassBreak ) // fuck glass
|
|
glass.Push(Results.HitLine);
|
|
return TRACE_Skip;
|
|
}
|
|
}
|
|
return TRACE_Stop;
|
|
}
|
|
}
|
|
|
|
Class SWWMCrosshairTracer : LineTracer
|
|
{
|
|
Actor ignoreme;
|
|
|
|
override ETraceStatus TraceCallback()
|
|
{
|
|
if ( Results.HitType == TRACE_HitActor )
|
|
{
|
|
if ( Results.HitActor == ignoreme ) return TRACE_Skip;
|
|
if ( Results.HitActor.bSHOOTABLE ) return TRACE_Stop;
|
|
return TRACE_Skip;
|
|
}
|
|
else if ( (Results.HitType == TRACE_HitWall) && (Results.Tier == TIER_Middle) )
|
|
{
|
|
if ( !Results.HitLine.sidedef[1] || (Results.HitLine.Flags&(Line.ML_BlockHitscan|Line.ML_BlockEverything)) )
|
|
return TRACE_Stop;
|
|
return TRACE_Skip;
|
|
}
|
|
return TRACE_Stop;
|
|
}
|
|
}
|
|
|
|
// Base class for all SWWM Weapons
|
|
Class SWWMWeapon : Weapon abstract
|
|
{
|
|
Mixin SWWMOverlapPickupSound;
|
|
Mixin SWWMUseToPickup;
|
|
|
|
private int SWeaponFlags;
|
|
Actor pfield; // instance of parry field for current melee attack
|
|
bool wallponch; // is punching a wall (for activation checks)
|
|
|
|
Class<Ammo> dropammotype;
|
|
|
|
Property DropAmmoType : dropammotype;
|
|
|
|
FlagDef NoFirstGive : SWeaponFlags, 0; // don't give ammo on first pickup (for weapons with a clip count)
|
|
FlagDef HideInMenu : SWeaponFlags, 1; // don't show in inventory menu (usually for sister weapons)
|
|
|
|
transient CVar phair; // use custom precise crosshair
|
|
transient ui CVar ch_img, ch_siz, ch_grow, ch_col, ch_hp, ch_on, ch_force;
|
|
ui SWWMCrosshairTracer ctr;
|
|
ui Vector3 cpos;
|
|
ui Color ccol;
|
|
ui Vector2 lagvpos;
|
|
|
|
override void DoEffect()
|
|
{
|
|
Super.DoEffect();
|
|
if ( !phair ) phair = CVar.GetCVar('swwm_precisecrosshair',players[consoleplayer]);
|
|
// force custom crosshair
|
|
if ( phair.GetBool() ) crosshair = 99;
|
|
else crosshair = 0;
|
|
}
|
|
override bool HandlePickup( Inventory item )
|
|
{
|
|
if ( (GetClass() == item.GetClass()) && !item.ShouldStay() )
|
|
{
|
|
int oldammo1 = Ammo1?Ammo1.Amount:0;
|
|
int oldammo2 = Ammo2?Ammo2.Amount:0;
|
|
if ( Weapon(item).PickupForAmmo(self) )
|
|
item.bPickupGood = true;
|
|
if ( (Amount+item.Amount > MaxAmount) && (Stamina > 0) )
|
|
{
|
|
// sell excess
|
|
int sellprice = Stamina;
|
|
// subtract prices of ammo given
|
|
if ( Ammo1 )
|
|
{
|
|
int ammogiven1 = Ammo1.Amount-oldammo1;
|
|
if ( ammogiven1 > 0 )
|
|
sellprice -= int(Ammo1.Stamina*(1.+.75*(ammogiven1-1)));
|
|
}
|
|
if ( Ammo2 )
|
|
{
|
|
int ammogiven2 = Ammo2.Amount-oldammo2;
|
|
if ( ammogiven2 > 0 )
|
|
sellprice -= int(Ammo2.Stamina*(1.+.75*(ammogiven2-1)));
|
|
}
|
|
sellprice = int(sellprice*.5);
|
|
SWWMScoreObj.Spawn(sellprice,Owner.Vec3Offset(FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8),FRandom[ScoreBits](-8,8)+Owner.Height/2),Font.CR_GOLD);
|
|
SWWMCredits.Give(Owner.player,sellprice);
|
|
if ( Owner.player )
|
|
Console.Printf(StringTable.Localize("$SWWM_SELLEXTRA"),Owner.player.GetUserName(),GetTag(),sellprice);
|
|
item.bPickupGood = true;
|
|
}
|
|
return true;
|
|
}
|
|
return Super.HandlePickup(item);
|
|
}
|
|
override void AttachToOwner( Actor other )
|
|
{
|
|
Inventory.AttachToOwner(other);
|
|
Ammo1 = AddAmmo(Owner,AmmoType1,bNoFirstGive?0:AmmoGive1);
|
|
Ammo2 = AddAmmo(Owner,AmmoType2,bNoFirstGive?0:AmmoGive2);
|
|
SisterWeapon = AddWeapon(SisterWeaponType);
|
|
if ( Owner.player )
|
|
{
|
|
if ( !Owner.player.GetNeverSwitch() && !bNo_Auto_Switch )
|
|
Owner.player.PendingWeapon = self;
|
|
if ( Owner.player.mo == players[consoleplayer].camera )
|
|
StatusBar.ReceivedWeapon(self);
|
|
}
|
|
GivenAsMorphWeapon = false;
|
|
}
|
|
override void DetachFromOwner()
|
|
{
|
|
Owner.A_StopSound(CHAN_WEAPON);
|
|
Owner.A_StopSound(CHAN_WEAPONEXTRA);
|
|
Owner.A_StopSound(CHAN_WEAPONEXTRA2);
|
|
Owner.A_StopSound(CHAN_WEAPONEXTRA3);
|
|
Super.DetachFromOwner();
|
|
}
|
|
override void OwnerDied()
|
|
{
|
|
if ( Owner.player && (Owner.player.ReadyWeapon == self) )
|
|
{
|
|
Owner.A_StopSound(CHAN_WEAPON);
|
|
Owner.A_StopSound(CHAN_WEAPONEXTRA);
|
|
Owner.A_StopSound(CHAN_WEAPONEXTRA2);
|
|
Owner.A_StopSound(CHAN_WEAPONEXTRA3);
|
|
}
|
|
A_ClearRefire();
|
|
Super.OwnerDied();
|
|
}
|
|
override String GetObituary( Actor victim, Actor inflictor, Name mod, bool playerattack )
|
|
{
|
|
if ( mod == 'Melee' ) return StringTable.Localize("$O_MELEE");
|
|
return Super.GetObituary(victim,inflictor,mod,playerattack);
|
|
}
|
|
// draw ammo on hud above weapon box
|
|
virtual ui void DrawWeapon( double TicFrac, double bx, double by, Vector2 hs, Vector2 ss )
|
|
{
|
|
}
|
|
// HUD-side ticking
|
|
virtual ui void HudTick()
|
|
{
|
|
[cpos, ccol] = TraceForCrosshair();
|
|
// avoid jumpy switching
|
|
if ( Owner.player.PendingWeapon is 'SWWMWeapon' )
|
|
{
|
|
SWWMWeapon(Owner.player.PendingWeapon).cpos = cpos;
|
|
SWWMWeapon(Owner.player.PendingWeapon).lagvpos = lagvpos;
|
|
}
|
|
}
|
|
// extra drawing, usually scopes
|
|
virtual ui void RenderUnderlay( RenderEvent e )
|
|
{
|
|
// draw custom crosshair
|
|
if ( automapactive || (players[consoleplayer].Camera is 'SecurityCamera') ) return;
|
|
if ( !phair || !phair.GetBool() ) return;
|
|
if ( !ch_on ) ch_on = CVar.GetCVar('crosshairon',players[consoleplayer]);
|
|
if ( !ch_on.GetBool() ) return;
|
|
if ( !ch_force ) ch_force = CVar.GetCVar('crosshairforce',players[consoleplayer]);
|
|
if ( ch_force.GetBool() ) return;
|
|
let sb = SWWMStatusBar(StatusBar);
|
|
if ( !sb ) return;
|
|
sb.viewport.FromHud();
|
|
sb.proj.CacheResolution();
|
|
sb.proj.CacheFov(players[consoleplayer].fov);
|
|
sb.proj.OrientForRenderUnderlay(e);
|
|
sb.proj.BeginProjection();
|
|
Vector3 tdir = level.Vec3Diff(e.ViewPos,cpos);
|
|
// project
|
|
sb.proj.ProjectWorldPos(e.ViewPos+tdir);
|
|
Vector2 vpos;
|
|
if ( !sb.proj.IsInFront() ) return;
|
|
Vector2 npos = sb.proj.ProjectToNormal();
|
|
vpos = sb.viewport.SceneToWindow(npos);
|
|
if ( lagvpos == (0,0) ) lagvpos = vpos;
|
|
else lagvpos = lagvpos*.7+vpos*.3; // this will be fucky depending on the FPS, but oh well
|
|
if ( !ch_img ) ch_img = CVar.GetCVar('crosshair',players[consoleplayer]);
|
|
int cnum = abs(ch_img.GetInt());
|
|
if ( !cnum ) return;
|
|
String tn = String.Format("XHAIR%s%d",(Screen.GetWidth()<640)?"S":"B",cnum);
|
|
TextureID ctex = TexMan.CheckForTexture(tn,TexMan.Type_MiscPatch);
|
|
if ( !ctex.IsValid() ) ctex = TexMan.CheckForTexture(String.Format("XHAIR%s1",(Screen.GetWidth()<640)?"S":"B"),TexMan.Type_MiscPatch);
|
|
if ( !ctex.IsValid() ) ctex = TexMan.CheckForTexture("XHAIRS1",TexMan.Type_MiscPatch);
|
|
Vector2 ts = TexMan.GetScaledSize(ctex);
|
|
if ( !ch_siz ) ch_siz = CVar.GetCVar('crosshairscale',players[consoleplayer]);
|
|
double cs = ch_siz.GetFloat();
|
|
double sz = 1.;
|
|
if ( cs > 0. ) sz = Screen.GetHeight()*cs/200.;
|
|
if ( !ch_grow ) ch_grow = CVar.GetCVar('crosshairgrow',players[consoleplayer]);
|
|
if ( ch_grow.GetBool() ) sz *= sb.CrosshairSize;
|
|
Screen.DrawTexture(ctex,false,int(lagvpos.x),int(lagvpos.y),DTA_DestWidthF,ts.x*sz,DTA_DestHeightF,ts.y*sz,DTA_AlphaChannel,true,DTA_FillColor,ccol);
|
|
}
|
|
ui Vector3, Color TraceForCrosshair()
|
|
{
|
|
if ( !ctr ) ctr = new("SWWMCrosshairTracer");
|
|
ctr.ignoreme = Owner;
|
|
Vector3 x, y, z, ofs;
|
|
double s;
|
|
[x, y, z] = swwm_CoordUtil.GetAxes(Owner.pitch,Owner.angle,Owner.roll);
|
|
ofs = GetTraceOffset();
|
|
Vector3 origin = level.Vec3Offset(Owner.Vec2OffsetZ(0,0,Owner.player.viewz),ofs.x*x+ofs.y*y+ofs.z*z);
|
|
ctr.Trace(origin,level.PointInSector(origin.xy),x,10000.,0);
|
|
if ( !ch_col ) ch_col = CVar.GetCVar('crosshaircolor',players[consoleplayer]);
|
|
Color col = ch_col.GetInt();
|
|
if ( !ch_hp ) ch_hp = CVar.GetCVar('crosshairhealth',players[consoleplayer]);
|
|
if ( ch_hp.GetInt() >= 2 )
|
|
{
|
|
int hp = Clamp(Owner.Health,0,200);
|
|
double sat = (hp<150)?1.:(1.-(hp-150)/100.);
|
|
Vector3 rgb = SWWMUtility.HSVtoRGB((hp/300.,sat,1.));
|
|
col = Color(int(rgb.x*255),int(rgb.y*255),int(rgb.z*255));
|
|
}
|
|
else if ( ch_hp.GetInt() == 1 )
|
|
{
|
|
double hp = Clamp(Owner.Health,0,100)/100.;
|
|
if ( hp <= 0 ) col = Color(255,0,0);
|
|
else if ( hp < .3 ) col = Color(255,int(hp*255/.3),0);
|
|
else if ( hp < .85 ) col = Color(int((.6-hp)*255/.3),255,0);
|
|
else col = Color(0,255,0);
|
|
}
|
|
else if ( (ctr.Results.HitType == TRACE_HitActor) && ctr.Results.HitActor.bSHOOTABLE )
|
|
{
|
|
// show target health, rather than our own
|
|
double hp = ctr.Results.HitActor.Health/double(ctr.Results.HitActor.GetSpawnHealth());
|
|
if ( hp <= 0 ) col = Color(255,0,0);
|
|
else if ( hp < .3 ) col = Color(255,int(hp*255/.3),0);
|
|
else if ( hp < .85 ) col = Color(int((.6-hp)*255/.3),255,0);
|
|
else col = Color(0,255,0);
|
|
}
|
|
if ( ctr.Results.HitType == TRACE_HitNone ) return level.Vec3Offset(origin,x*10000.), col;
|
|
else return ctr.Results.HitPos, col;
|
|
}
|
|
// where the trace is coming from relative to eyes
|
|
virtual clearscope Vector3 GetTraceOffset() const
|
|
{
|
|
return (0.,0.,0.);
|
|
}
|
|
// animations
|
|
action void A_PlayerFire()
|
|
{
|
|
let demo = Demolitionist(player.mo);
|
|
if ( demo && (demo.Health > 0) ) demo.PlayFire();
|
|
}
|
|
action void A_PlayerMelee( bool bFast = false )
|
|
{
|
|
let demo = Demolitionist(player.mo);
|
|
if ( demo && (demo.Health > 0) )
|
|
{
|
|
if ( bFast ) demo.PlayFastMelee();
|
|
else demo.PlayMelee();
|
|
}
|
|
}
|
|
action void A_PlayerReload()
|
|
{
|
|
let demo = Demolitionist(player.mo);
|
|
if ( demo && (demo.Health > 0) ) demo.PlayReload();
|
|
}
|
|
action void A_PlayerCheckGun()
|
|
{
|
|
let demo = Demolitionist(player.mo);
|
|
if ( demo && (demo.Health > 0) ) demo.PlayCheckGun();
|
|
}
|
|
// instant raise/lower
|
|
action void A_FullRaise()
|
|
{
|
|
if ( !player ) return;
|
|
if ( player.PendingWeapon != WP_NOCHANGE )
|
|
{
|
|
player.mo.DropWeapon();
|
|
return;
|
|
}
|
|
if ( !player.ReadyWeapon ) return;
|
|
let psp = player.GetPSprite(PSP_WEAPON);
|
|
if ( !psp ) return;
|
|
ResetPSprite(psp);
|
|
psp.y = WEAPONTOP;
|
|
// do not jump to ready state here, the weapon should do that
|
|
// directly once it finishes playing its select animation
|
|
}
|
|
action void A_FullLower()
|
|
{
|
|
if ( !player ) return;
|
|
if ( !player.ReadyWeapon )
|
|
{
|
|
player.mo.BringUpWeapon();
|
|
return;
|
|
}
|
|
let psp = player.GetPSprite(PSP_WEAPON);
|
|
if ( !psp ) return;
|
|
psp.y = WEAPONBOTTOM;
|
|
ResetPSprite(psp);
|
|
if ( player.playerstate == PST_DEAD )
|
|
{
|
|
// Player is dead, so don't bring up a pending weapon
|
|
// Player is dead, so keep the weapon off screen
|
|
player.SetPSprite(PSP_FLASH,null);
|
|
psp.SetState(player.ReadyWeapon.FindState('DeadLowered'));
|
|
return;
|
|
}
|
|
// [RH] Clear the flash state. Only needed for Strife.
|
|
player.SetPSprite(PSP_FLASH,null);
|
|
player.mo.BringUpWeapon();
|
|
}
|
|
action void A_Parry( int duration )
|
|
{
|
|
Vector3 x, y, z, origin;
|
|
[x, y, z] = swwm_CoordUtil.GetAxes(pitch,angle,roll);
|
|
origin = level.Vec3Offset(Vec2OffsetZ(0,0,player.viewz),x*20-(0,0,20));
|
|
if ( invoker.pfield ) invoker.pfield.Destroy();
|
|
invoker.pfield = Spawn("ParryField",origin);
|
|
invoker.pfield.master = self;
|
|
invoker.pfield.special1 = duration;
|
|
if ( !FindInventory("ParryDamageChecker") )
|
|
GiveInventory("ParryDamageChecker",1); // need this so parried projectiles deal extra damage
|
|
}
|
|
private action bool TryMelee( double angle, int dmg, String hitsound = "", double rangemul = 1. )
|
|
{
|
|
FTranslatedLineTarget t;
|
|
double slope = AimLineAttack(angle,1.5*DEFMELEERANGE*rangemul,t,0.,ALF_CHECK3D);
|
|
FLineTraceData d;
|
|
LineTrace(angle,1.5*DEFMELEERANGE*rangemul,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.);
|
|
SWWMUtility.DoKnockback(d.HitActor,d.HitDir+(0,0,.2),dmg*2000);
|
|
if ( raging )
|
|
{
|
|
invoker.bEXTREMEDEATH = true;
|
|
invoker.bNOEXTREMEDEATH = false;
|
|
}
|
|
else
|
|
{
|
|
invoker.bEXTREMEDEATH = false;
|
|
invoker.bNOEXTREMEDEATH = true;
|
|
}
|
|
if ( !d.HitActor.bDORMANT ) // lol oops
|
|
d.HitActor.DaggerAlert(self);
|
|
int flg = DMG_USEANGLE|DMG_THRUSTLESS;
|
|
if ( raging ) flg |= DMG_FOILINVUL;
|
|
dmg = d.HitActor.DamageMobj(invoker,self,dmg,'Melee',flg,atan2(d.HitDir.y,d.HitDir.x));
|
|
invoker.bEXTREMEDEATH = invoker.default.bEXTREMEDEATH;
|
|
invoker.bNOEXTREMEDEATH = invoker.default.bEXTREMEDEATH;
|
|
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.bDORMANT && (raging || !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
|
|
{
|
|
let p = Spawn(raging?"BigPunchImpact":"PunchImpact",d.HitLocation);
|
|
p.angle = atan2(-d.HitDir.y,-d.HitDir.x);
|
|
}
|
|
if ( raging )
|
|
{
|
|
let ps = Spawn("BigPunchSplash",d.HitLocation);
|
|
ps.target = self;
|
|
ps.special1 = dmg;
|
|
}
|
|
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((hitsound!="")?hitsound:bloodless?"demolitionist/punch":"demolitionist/punchf",CHAN_WEAPON,CHANF_OVERLAP);
|
|
A_AlertMonsters(swwm_uncapalert?0:300);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
action void A_Melee( int dmg = 40, String hitsound = "", double rangemul = 1. )
|
|
{
|
|
// check for usables
|
|
let ut = new("UseLineTracer");
|
|
ut.uses.Clear();
|
|
ut.glass.Clear();
|
|
ut.Trace(Vec3Offset(0,0,player.viewheight),CurSector,(cos(angle)*cos(pitch),sin(angle)*cos(pitch),sin(-pitch)),DEFMELEERANGE*rangemul,0);
|
|
invoker.wallponch = true;
|
|
for ( int i=0; i<ut.uses.Size(); i++ )
|
|
{
|
|
if ( ut.uses[i].hitactor )
|
|
{
|
|
// punching is not greeting/patting (that'd be weird)
|
|
if ( (ut.uses[i].hitactor == self) || (ut.uses[i].hitactor is 'Demolitionist')
|
|
|| (ut.uses[i].hitactor is 'HeadpatTracker') ) continue;
|
|
if ( ut.uses[i].hitactor.Used(self) ) break;
|
|
}
|
|
else if ( ut.uses[i].hitline && UseLineTracer.TangibleLine(ut.uses[i]) )
|
|
{
|
|
int locknum = SWWMUtility.GetLineLock(ut.uses[i].hitline);
|
|
if ( !locknum || CheckKeys(locknum,false,true) )
|
|
ut.uses[i].hitline.RemoteActivate(self,ut.uses[i].hitside,SPAC_Use,ut.uses[i].pos);
|
|
if ( !(ut.uses[i].hitline.activation&SPAC_UseThrough) ) break;
|
|
}
|
|
}
|
|
invoker.wallponch = false;
|
|
// fuck glass
|
|
for ( int i=0; i<ut.glass.Size(); i++ )
|
|
ut.glass[i].Activate(self,0,SPAC_Impact);
|
|
let raging = RagekitPower(FindInventory("RagekitPower"));
|
|
int maxang = raging?18:12;
|
|
for ( int i=0; i<maxang; i++ )
|
|
{
|
|
if ( TryMelee(angle+i*(45./16),dmg,hitsound,rangemul) || TryMelee(angle-i*(45./16),dmg,hitsound,rangemul) )
|
|
return;
|
|
}
|
|
// check for walls instead
|
|
FTranslatedLineTarget t;
|
|
double slope = AimLineAttack(angle,DEFMELEERANGE*rangemul,t,0.,ALF_CHECK3D);
|
|
FLineTraceData d;
|
|
LineTrace(angle,DEFMELEERANGE*rangemul,slope,TRF_THRUACTORS,player.viewheight,data:d);
|
|
if ( d.HitType == TRACE_HitNone ) return;
|
|
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);
|
|
if ( raging )
|
|
{
|
|
let ps = Spawn("BigPunchSplash",d.HitLocation+HitNormal*4);
|
|
ps.target = self;
|
|
ps.special1 = dmg;
|
|
}
|
|
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":(hitsound!="")?hitsound:"demolitionist/punch",CHAN_WEAPON,CHANF_OVERLAP);
|
|
A_AlertMonsters(swwm_uncapalert?0:100);
|
|
if ( raging ) raging.DoHitFX();
|
|
if ( swwm_omnibust ) BusterWall.BustLinetrace(d,raging?(dmg*8):dmg,self,d.HitDir,d.HitLocation.z);
|
|
}
|
|
override void PlayUpSound( Actor origin )
|
|
{
|
|
if ( UpSound ) origin.A_StartSound(UpSound,CHAN_WEAPON,CHANF_OVERLAP);
|
|
}
|
|
action void A_SWWMFlash( StateLabel flashlabel = null )
|
|
{
|
|
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,flashstate);
|
|
A_OverlayFlags(PSP_FLASH,PSPF_RENDERSTYLE|PSPF_FORCESTYLE,true);
|
|
A_OverlayRenderStyle(PSP_FLASH,STYLE_Add);
|
|
}
|
|
// tells the SWWM HUD that this weapon has ammo available
|
|
virtual clearscope bool ReportHUDAmmo()
|
|
{
|
|
return (!Ammo1||(Ammo1.Amount>0)||(Ammo2&&(Ammo2.Amount>0)));
|
|
}
|
|
// tells the Embiggener that this weapon uses the specified ammo type
|
|
// even if it is not its primary one
|
|
virtual clearscope bool UsesAmmo( Class<Ammo> kind )
|
|
{
|
|
return (AmmoType1&&(kind is AmmoType1))||(AmmoType2&&(kind is AmmoType2));
|
|
}
|
|
override void ModifyDropAmount( int dropamount )
|
|
{
|
|
Super.ModifyDropAmount(dropamount);
|
|
if ( (AmmoGive1 <= 0) && (default.AmmoGive1 > 0) )
|
|
AmmoGive1 = 1;
|
|
if ( (AmmoGive2 <= 0) && (default.AmmoGive2 > 0) )
|
|
AmmoGive2 = 1;
|
|
}
|
|
override bool SpecialDropAction( Actor dropper )
|
|
{
|
|
if ( swwm_enemydrops > 0 ) return false;
|
|
else if ( swwm_enemydrops == 0 )
|
|
{
|
|
// drop our corresponding ammo
|
|
if ( !DropAmmoType ) return true;
|
|
let a = Inventory(Spawn(DropAmmoType,pos,ALLOW_REPLACE));
|
|
if ( !a ) return true;
|
|
a.bDROPPED = true;
|
|
a.bNOGRAVITY = false;
|
|
if ( !(level.compatflags&COMPATF_NOTOSSDROPS) )
|
|
a.TossItem();
|
|
if ( a is 'Ammo' )
|
|
a.ModifyDropAmount(Ammo(a).DropAmount);
|
|
a.bTOSSED = true;
|
|
if ( a.SpecialDropAction(dropper) )
|
|
a.Destroy();
|
|
return true;
|
|
}
|
|
// no weapon drops from enemies
|
|
return true;
|
|
}
|
|
override Inventory CreateTossable( int amt )
|
|
{
|
|
// disallow dropping if weapon isn't ready for switching
|
|
if ( (Owner.player.ReadyWeapon == self) && (!(Owner.player.WeaponState&WF_WEAPONSWITCHOK) || (Owner.player.WeaponState&WF_DISABLESWITCH)) )
|
|
return null;
|
|
return Super.CreateTossable(amt);
|
|
}
|
|
Default
|
|
{
|
|
Weapon.BobStyle "Alpha";
|
|
Weapon.BobSpeed 3.0;
|
|
Weapon.BobRangeX 0.5;
|
|
Weapon.BobRangeY 0.2;
|
|
Weapon.YAdjust 0;
|
|
Weapon.SlotPriority 1.;
|
|
Inventory.RestrictedTo "Demolitionist";
|
|
Inventory.PickupFlash "SWWMRedPickupFlash";
|
|
+INVENTORY.IGNORESKILL;
|
|
+WEAPON.NOALERT;
|
|
+WEAPON.NODEATHINPUT;
|
|
+FLOATBOB;
|
|
FloatBobStrength 0.25;
|
|
}
|
|
}
|