572 lines
18 KiB
Text
572 lines
18 KiB
Text
// melee/parrying stuff
|
|
|
|
Class ParriedBuff : Inventory
|
|
{
|
|
Vector3 oldvel;
|
|
|
|
Default
|
|
{
|
|
+INVENTORY.UNDROPPABLE;
|
|
+INVENTORY.UNTOSSABLE;
|
|
Inventory.Amount 1;
|
|
Inventory.MaxAmount 1;
|
|
}
|
|
override void DoEffect()
|
|
{
|
|
Super.DoEffect();
|
|
if ( Owner.bBLASTED ) oldvel = Owner.vel;
|
|
if ( !Owner.bMISSILE && !Owner.bSKULLFLY )
|
|
{
|
|
// lost soul is no longer attacking
|
|
// remove blasted flag just in case
|
|
Owner.bBLASTED = false;
|
|
Destroy();
|
|
return;
|
|
}
|
|
if ( special1 <= 0 ) return;
|
|
// smoke trail
|
|
Actor s;
|
|
if ( special1&1 )
|
|
{
|
|
s = Spawn("SWWMHalfSmoke",Owner.pos);
|
|
s.vel = Owner.vel*.3+SWWMUtility.Vec3FromAngles(FRandom[Ponch](0,360),FRandom[Ponch](-90,90))*FRandom[Ponch](.1,.6);
|
|
s.scale *= 1.2;
|
|
s.alpha *= .3;
|
|
}
|
|
if ( special1 > 1 )
|
|
{
|
|
s = Spawn("SWWMHalfSmoke",Owner.pos);
|
|
s.vel = Owner.vel*.3+SWWMUtility.Vec3FromAngles(FRandom[Ponch](0,360),FRandom[Ponch](-90,90))*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));
|
|
}
|
|
}
|
|
override void ModifyDamage( int damage, Name damageType, out int newdamage, bool passive, Actor inflictor, Actor source, int flags )
|
|
{
|
|
// increase blast damage (way too tiny normally for lost souls)
|
|
if ( Owner.bBLASTED && (damageType == 'Melee') && !inflictor && !source )
|
|
{
|
|
newdamage = 20;
|
|
if ( special1&1 ) newdamage *= 2;
|
|
if ( special1 > 1 ) newdamage *= 8;
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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 : SWWMNonInteractiveActor
|
|
{
|
|
Default
|
|
{
|
|
RenderStyle "Add";
|
|
Scale .1;
|
|
Alpha .3;
|
|
+FORCEXYBILLBOARD;
|
|
}
|
|
States
|
|
{
|
|
Spawn:
|
|
XRG4 ABCDEFGHIJKLMNOPQRSTUVWX 1 A_SetScale(scale.x*(1+specialf1));
|
|
Stop;
|
|
}
|
|
}
|
|
|
|
Class ParryField : SWWMNonInteractiveActor
|
|
{
|
|
bool critsnd;
|
|
Array<Actor> justparried;
|
|
|
|
override void Tick()
|
|
{
|
|
if ( !master )
|
|
{
|
|
Destroy();
|
|
return;
|
|
}
|
|
Vector3 x, y, z, origin;
|
|
[x, y, z] = SWWMUtility.GetAxes(master.angle,master.pitch,master.roll);
|
|
origin = level.Vec3Offset(master.Vec2OffsetZ(0,0,master.player.viewz),x*20);
|
|
SetOrigin(origin,false);
|
|
let raging = RagekitPower(master.FindInventory("RagekitPower"));
|
|
let s = Demolitionist(master).mystats;
|
|
// check for projectiles to deflect
|
|
let ti = ThinkerIterator.Create("Actor");
|
|
Actor a;
|
|
while ( a = Actor(ti.Next()) )
|
|
{
|
|
if ( (justparried.Find(a) < justparried.Size()) || !(SWWMUtility.ValidProjectile(a) || a.bSKULLFLY) || a.bTHRUACTORS || (level.Vec3Diff(a.pos,pos).length() > 80) ) continue;
|
|
if ( a is 'Whirlwind' ) SWWMUtility.MarkAchievement("tornado",master.player);
|
|
justparried.Push(a);
|
|
Vector3 vdir = a.vel;
|
|
Vector3 dir = level.Vec3Diff(master.Vec2OffsetZ(0,0,pos.z),a.pos).unit();
|
|
Vector3 hdir = dir;
|
|
Actor oldtarget = a.target;
|
|
if ( (a.target != master) && (a.bMISSILE || (a is 'HolySpirit')) ) // special wraithverge handling
|
|
{
|
|
// deflect directly to target
|
|
if ( a.target )
|
|
{
|
|
hdir = level.Vec3Diff(a.pos,a.target.Vec3Offset(0,0,a.target.height/2)).unit();
|
|
double theta = max(FRandom[Parry](0.,1.)**2.,.1);
|
|
dir = SWWMUtility.LerpVector3(dir,hdir,theta);
|
|
}
|
|
// push away
|
|
if ( a.bSEEKERMISSILE ) a.tracer = a.target;
|
|
a.target = master;
|
|
}
|
|
if ( a.bSKULLFLY ) a.bBLASTED = true; // blast lost souls
|
|
let buff = a.FindInventory("ParriedBuff");
|
|
if ( !buff )
|
|
{
|
|
buff = Inventory(Spawn("ParriedBuff"));
|
|
buff.AttachToOwner(a);
|
|
buff.tracer = oldtarget;
|
|
}
|
|
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);
|
|
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|QF_3D,rollIntensity:.2);
|
|
A_StartSound("demolitionist/parry",CHAN_WEAPON);
|
|
if ( special1 >= special2 ) // perfect parry
|
|
{
|
|
// increased homing
|
|
dir = dir*.4+hdir*.6;
|
|
nspeed = min(100,nspeed*1.5);
|
|
a.vel = dir*nspeed;
|
|
for ( int i=1; i<6; i++ )
|
|
{
|
|
let r = Spawn("ParryRing",a.pos);
|
|
r.specialf1 = i*.04;
|
|
}
|
|
buff.special1 |= 1;
|
|
if ( !critsnd )
|
|
{
|
|
A_StartSound("misc/soulsparry",CHAN_ITEM,CHANF_OVERLAP,1.,.5);
|
|
if ( s ) s.pparries++;
|
|
}
|
|
critsnd = true;
|
|
if ( (a is 'LostSoul') && (master.player.ReadyWeapon is 'SilverBullet') )
|
|
SWWMUtility.MarkAchievement("baseball",master.player);
|
|
}
|
|
if ( s ) s.parries++;
|
|
SWWMUtility.AchievementProgressInc("parry",1,master.player);
|
|
}
|
|
if ( --special1 <= 0 ) Destroy();
|
|
}
|
|
}
|
|
|
|
Class UseList
|
|
{
|
|
Line hitline;
|
|
int hitside, hitpart;
|
|
Actor hitactor;
|
|
Vector3 pos;
|
|
}
|
|
|
|
Class UseLineTracer : LineTracer
|
|
{
|
|
Array<UseList> uses;
|
|
|
|
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;
|
|
return TRACE_Skip;
|
|
}
|
|
}
|
|
return TRACE_Stop;
|
|
}
|
|
}
|
|
|
|
Class MHitList
|
|
{
|
|
Actor a;
|
|
Vector3 dir, pos;
|
|
}
|
|
|
|
Class MeleeTracer : LineTracer
|
|
{
|
|
Actor ignoreme;
|
|
Array<MHitList> hits;
|
|
Vector3 x; // used to get closest hit to center
|
|
bool dorip;
|
|
|
|
override ETraceStatus TraceCallback()
|
|
{
|
|
if ( Results.HitType == TRACE_HitActor )
|
|
{
|
|
if ( !Results.HitActor.bSHOOTABLE
|
|
|| (Results.HitActor == ignoreme)
|
|
|| Results.HitActor.FindInventory("ParriedBuff") ) return TRACE_Skip;
|
|
bool addme = true;
|
|
foreach ( hit:hits )
|
|
{
|
|
if ( hit.a != Results.HitActor ) continue;
|
|
if ( (hit.dir dot x) < (Results.HitVector dot x) )
|
|
{
|
|
// closer to centerpoint
|
|
hit.dir = Results.HitVector;
|
|
hit.pos = Results.HitPos;
|
|
}
|
|
addme = false;
|
|
break;
|
|
}
|
|
if ( addme )
|
|
{
|
|
let h = new("MHitList");
|
|
h.a = Results.HitActor;
|
|
h.dir = Results.HitVector;
|
|
h.pos = Results.HitPos;
|
|
hits.Push(h);
|
|
}
|
|
return dorip?TRACE_Skip:TRACE_Stop;
|
|
}
|
|
if ( Results.HitType == TRACE_HitWall )
|
|
{
|
|
if ( 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;
|
|
}
|
|
|
|
}
|
|
|
|
extend Class SWWMWeapon
|
|
{
|
|
Actor pfield; // instance of parry field for current melee attack
|
|
bool wallponch; // is punching a wall (for activation checks)
|
|
transient MeleeTracer mt;
|
|
transient UseLineTracer ut;
|
|
|
|
// melee attack flags
|
|
enum EMeleeFlags
|
|
{
|
|
MELEE_Rip = 1, // trace will continue instead of stopping at the first hit (useful for swords)
|
|
MELEE_ForceSound = 2, // force the use of the provided hit sound while raging
|
|
MELEE_FleshSound = 4, // hit sound has a "flesh" variant (will append "f" suffix to hitsound string)
|
|
MELEE_ForceBust = 8, // bust walls even without Omnibusting enabled
|
|
MELEE_Vertical = 16, // ring is widened vertically, rather than horizontally
|
|
MELEE_Wider = 32, // ring is widened by 2.5x rather than 1.5x
|
|
MELEE_ExtraWide = 64, // if Wider is also specified, widen by 5x, otherwise widen by 3x
|
|
MELEE_HammerHit = 128, // is hammer melee (gibbing counts for "HAHA DAB" achievement)
|
|
MELEE_NoRage = 256, // unaffected by ragekit
|
|
MELEE_NoUse = 512 // do not pass use actions to this melee
|
|
};
|
|
|
|
action void A_Parry( int duration )
|
|
{
|
|
Vector3 x, y, z, origin;
|
|
[x, y, z] = SWWMUtility.GetAxes(angle,pitch,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;
|
|
invoker.pfield.special2 = duration;
|
|
if ( !FindInventory("ParryDamageChecker") )
|
|
GiveInventory("ParryDamageChecker",1); // need this so parried projectiles deal extra damage
|
|
}
|
|
// multi-hit cone rather than the usual one-hit arc, more fun
|
|
private action bool TryMelee( double spread, int dmg, String hitsound, double rangemul, double kickmul, int flags, Class<Actor> impactclass )
|
|
{
|
|
Vector3 x, y, z, dir;
|
|
[x, y, z] = SWWMUtility.GetAxes(angle,pitch,roll);
|
|
Vector3 origin = Vec2OffsetZ(0,0,player.viewz);
|
|
if ( !invoker.mt ) invoker.mt = new("MeleeTracer");
|
|
let mt = invoker.mt; // for convenience
|
|
mt.ignoreme = self;
|
|
mt.hits.Clear();
|
|
mt.dorip = !!(flags&MELEE_Rip);
|
|
int rings = 1;
|
|
double step = spread/20.;
|
|
double range = 1.5*DEFMELEERANGE*rangemul;
|
|
bool raging = (flags&MELEE_NoRage)?false:CountInv("RagekitPower");
|
|
double widemul = (flags&MELEE_ExtraWide)?(flags&MELEE_Wider)?5:3:(flags&MELEE_Wider)?2.5:1.5;
|
|
for ( double i=0; i<spread; i+=step )
|
|
{
|
|
for ( int j=0; j<360; j+=(360/rings) )
|
|
{
|
|
// wide ring
|
|
if ( flags&MELEE_Vertical ) dir = SWWMUtility.ConeSpread(x,y,z*widemul,j,i);
|
|
else dir = SWWMUtility.ConeSpread(x,y*widemul,z,j,i);
|
|
mt.Trace(origin,level.PointInSector(origin.xy),dir,range,0);
|
|
}
|
|
rings += 5;
|
|
}
|
|
// no targets
|
|
if ( mt.hits.Size() <= 0 ) return false;
|
|
bool blooded = false;
|
|
bool bloodless = false;
|
|
int flg = DMG_THRUSTLESS|DMG_INFLICTOR_IS_PUFF;
|
|
if ( raging ) flg |= DMG_FOILINVUL;
|
|
int quakin = raging?8:2;
|
|
double diff = 0.;
|
|
bool hdoomgal = false;
|
|
foreach ( hit:mt.hits )
|
|
{
|
|
if ( !hit.a ) continue;
|
|
diff += deltaangle(self.angle,AngleTo(hit.a));
|
|
SWWMUtility.DoKnockback(hit.a,hit.dir,dmg*2000*kickmul);
|
|
// lol oops
|
|
if ( !hit.a.bDORMANT ) hit.a.DaggerAlert(self);
|
|
if ( !hit.a.bNOBLOOD && !hit.a.bDORMANT && (raging || !hit.a.bINVULNERABLE) ) blooded = true;
|
|
else bloodless = true;
|
|
if ( SWWMHDoomHandler.IsCuteGirl(hit.a) ) hdoomgal = true;
|
|
let p = SWWMPuff.Setup(hit.pos,hit.dir,invoker,self,hit.a);
|
|
int newdmg = hit.a.DamageMobj(p,self,dmg,'Melee',flg);
|
|
// things can instantly cease to exist after taking damage (wow)
|
|
if ( hit.a )
|
|
{
|
|
if ( hit.a.player ) hit.a.A_QuakeEx(quakin,quakin,quakin,6,0,1,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,rollIntensity:.125*quakin);
|
|
if ( (newdmg > 0) && !hit.a.bNOBLOOD && !hit.a.bDORMANT && (raging || !hit.a.bINVULNERABLE) )
|
|
{
|
|
hit.a.TraceBleed(newdmg,invoker);
|
|
hit.a.SpawnBlood(hit.pos,atan2(hit.dir.y,hit.dir.x)+180,newdmg);
|
|
}
|
|
else if ( impactclass )
|
|
{
|
|
let p = Spawn(raging?(Class<Actor>)("BigPunchImpact"):impactclass,hit.pos);
|
|
p.angle = atan2(hit.dir.y,hit.dir.x);
|
|
}
|
|
if ( (flags&MELEE_HammerHit) && (hit.a.Health <= hit.a.GetGibHealth()) )
|
|
SWWMUtility.AchievementProgressInc("dab",1,player);
|
|
}
|
|
if ( raging )
|
|
{
|
|
let ps = Spawn("BigPunchSplash",hit.pos);
|
|
ps.target = self;
|
|
ps.special1 = dmg;
|
|
}
|
|
}
|
|
self.angle += clamp(diff/mt.hits.Size(),-5.,5.); // averaged reorient
|
|
A_QuakeEx(quakin/2,quakin/2,quakin/2,3,0,1,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D,rollIntensity:0.06*quakin);
|
|
if ( flags&MELEE_HammerHit )
|
|
{
|
|
if ( hdoomgal )
|
|
{
|
|
flags |= MELEE_ForceSound;
|
|
flags &= ~MELEE_FleshSound;
|
|
hitsound = "misc/clonk6";
|
|
}
|
|
else if ( swwm_bonkhammer )
|
|
{
|
|
flags |= MELEE_ForceSound;
|
|
flags &= ~MELEE_FleshSound;
|
|
hitsound = "misc/clonk"..Random[Hammer](1,11);
|
|
}
|
|
}
|
|
if ( raging && !(flags&MELEE_ForceSound) )
|
|
{
|
|
if ( blooded ) A_StartSound("demolitionist/xpunchf",CHAN_WEAPON,CHANF_OVERLAP);
|
|
if ( bloodless ) A_StartSound("demolitionist/xpunch",CHAN_WEAPON,CHANF_OVERLAP);
|
|
}
|
|
else if ( hitsound == "" )
|
|
{
|
|
if ( blooded ) A_StartSound("demolitionist/punchf",CHAN_WEAPON,CHANF_OVERLAP);
|
|
if ( bloodless ) A_StartSound("demolitionist/punch",CHAN_WEAPON,CHANF_OVERLAP);
|
|
}
|
|
else if ( flags&MELEE_FleshSound )
|
|
{
|
|
if ( blooded ) A_StartSound(hitsound.."f",CHAN_WEAPON,CHANF_OVERLAP);
|
|
if ( bloodless ) A_StartSound(hitsound,CHAN_WEAPON,CHANF_OVERLAP);
|
|
}
|
|
else A_StartSound(hitsound,CHAN_WEAPON,CHANF_OVERLAP);
|
|
A_AlertMonsters(swwm_uncapalert?0:300);
|
|
A_BumpFOV(.96);
|
|
return true;
|
|
}
|
|
action bool A_Melee( int dmg = 40, String hitsound = "", double rangemul = 1., double spreadmul = 1., double kickmul = 1., int flags = 0, Class<Actor> impactclass = "PunchImpact" )
|
|
{
|
|
let raging = RagekitPower(FindInventory("RagekitPower"));
|
|
if ( flags&MELEE_NoRage ) raging = null;
|
|
if ( raging ) rangemul += .2;
|
|
Vector3 origin = Vec3Offset(0,0,player.viewheight);
|
|
Vector3 dir = SWWMUtility.Vec3FromAngles(angle,pitch);
|
|
// check for usables
|
|
if ( !(flags&MELEE_NoUse) )
|
|
{
|
|
if ( !invoker.ut ) invoker.ut = new("UseLineTracer");
|
|
let ut = invoker.ut; // for convenience
|
|
ut.uses.Clear();
|
|
ut.Trace(origin,level.PointInSector(origin.xy),dir,DEFMELEERANGE*rangemul,0);
|
|
invoker.wallponch = true;
|
|
foreach ( u:ut.uses )
|
|
{
|
|
if ( u.hitactor )
|
|
{
|
|
// punching is not greeting/patting (that'd be weird)
|
|
if ( (u.hitactor == self) || (u.hitactor is 'Demolitionist')
|
|
|| (u.hitactor is 'HeadpatTracker')
|
|
|| (u.hitactor is 'FroggyChair') ) continue;
|
|
if ( u.hitactor.Used(self) ) break;
|
|
}
|
|
else if ( u.hitline && UseLineTracer.TangibleLine(u) )
|
|
{
|
|
int locknum = SWWMUtility.GetLineLock(u.hitline);
|
|
if ( !locknum || CheckKeys(locknum,false,true) )
|
|
u.hitline.RemoteActivate(self,u.hitside,SPAC_Use,u.pos);
|
|
if ( !(u.hitline.activation&SPAC_UseThrough) ) break;
|
|
}
|
|
}
|
|
invoker.wallponch = false;
|
|
}
|
|
// check for shootables
|
|
SWWMBulletTrail.DoTrail(self,origin,dir,DEFMELEERANGE*rangemul,0);
|
|
bool res = TryMelee((raging?.3:.2)*spreadmul,dmg,hitsound,rangemul,kickmul,flags,impactclass);
|
|
if ( res && !(flags&MELEE_Rip) ) return res;
|
|
// 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 res;
|
|
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);
|
|
}
|
|
if ( impactclass )
|
|
{
|
|
let p = Spawn(raging?(Class<Actor>)("BigPunchImpact"):impactclass,d.HitLocation+HitNormal*4);
|
|
p.angle = atan2(HitNormal.y,HitNormal.x);
|
|
p.pitch = asin(-HitNormal.z);
|
|
if ( d.HitType == TRACE_HitFloor ) p.CheckSplash(40);
|
|
}
|
|
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|QF_3D,rollIntensity:0.12*quakin);
|
|
A_BumpFOV(.98);
|
|
A_StartSound((raging&&!(flags&MELEE_ForceSound))?"demolitionist/xpunch":(hitsound!="")?hitsound:"demolitionist/punch",CHAN_WEAPON,CHANF_OVERLAP);
|
|
A_AlertMonsters(swwm_uncapalert?0:100);
|
|
if ( raging ) raging.DoHitFX();
|
|
if ( (flags&MELEE_ForceBust) || swwm_omnibust )
|
|
{
|
|
if ( flags&MELEE_HammerHit ) dmg = dmg*2+200; // extra bust damage if it's the hammer
|
|
BusterWall.BustLinetrace(d,raging?(dmg*8):dmg,self,d.HitDir,d.HitLocation.z);
|
|
}
|
|
return true;
|
|
}
|
|
}
|