swwmgz_m/zscript/weapons/swwm_baseweapon_melee.zsc
2021-05-15 12:55:06 +02:00

503 lines
15 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+(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));
}
}
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 ( special2 > 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 : 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
{
bool critsnd;
Default
{
Radius .1;
Height 0.;
+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);
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 ( !((SWWMUtility.ValidProjectile(a) && (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();
Vector3 hdir = dir;
if ( a.bMISSILE )
{
// 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 = dir*(1.-theta)+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);
}
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,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++;
if ( !critsnd )
{
A_StartSound("misc/soulsparry",CHAN_ITEM,CHANF_OVERLAP,1.,.5);
if ( s ) s.pparries++;
}
critsnd = true;
}
if ( s ) s.parries++;
SWWMUtility.AchievementProgressInc('swwm_progress_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;
}
extend Class SWWMWeapon
{
Actor pfield; // instance of parry field for current melee attack
bool wallponch; // is punching a wall (for activation checks)
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;
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 = 1., double kickmul = 1. )
{
Vector3 x, y, z, dir;
[x, y, z] = swwm_CoordUtil.GetAxes(pitch,angle,roll);
Vector3 origin = Vec2OffsetZ(0,0,player.viewz);
Array<MHitList> hits;
hits.Clear();
FLineTraceData d;
int rings = 1;
double step = spread/20.;
double range = 1.5*DEFMELEERANGE*rangemul;
bool raging = CountInv("RagekitPower");
for ( double i=0; i<spread; i+=step )
{
for ( int j=0; j<360; j+=(360/rings) )
{
dir = (x+y*cos(j)*1.5*i+z*sin(j)*i).unit(); // wide ring
LineTrace(atan2(dir.y,dir.x),range,asin(-dir.z),TRF_ABSPOSITION,origin.z,origin.x,origin.y,d);
if ( d.HitType != TRACE_HitActor ) continue;
if ( d.HitActor.FindInventory("ParriedBuff") ) continue;
bool addme = true;
for ( int k=0; k<hits.Size(); k++ )
{
if ( hits[k].a != d.HitActor ) continue;
if ( (hits[k].dir dot x) < (dir dot x) )
{
// closer to centerpoint
hits[k].dir = dir;
hits[k].pos = d.HitLocation;
}
addme = false;
break;
}
if ( !addme ) continue;
let nhit = new("MHitList");
nhit.a = d.HitActor;
nhit.dir = dir;
nhit.pos = d.HitLocation;
hits.Push(nhit);
}
rings += 5;
}
// no targets
if ( hits.Size() <= 0 ) return false;
bool blooded = false;
bool bloodless = false;
if ( raging )
{
invoker.bEXTREMEDEATH = true;
invoker.bNOEXTREMEDEATH = false;
}
else
{
invoker.bEXTREMEDEATH = false;
invoker.bNOEXTREMEDEATH = true;
}
int flg = DMG_USEANGLE|DMG_THRUSTLESS;
if ( raging ) flg |= DMG_FOILINVUL;
int quakin = raging?8:2;
double diff = 0.;
for ( int i=0; i<hits.Size(); i++ )
{
diff += deltaangle(self.angle,AngleTo(hits[i].a));
SWWMUtility.DoKnockback(hits[i].a,hits[i].dir,dmg*2000*kickmul);
// lol oops
if ( !hits[i].a.bDORMANT ) hits[i].a.DaggerAlert(self);
if ( !hits[i].a.bNOBLOOD && !hits[i].a.bDORMANT && (raging || !hits[i].a.bINVULNERABLE) ) blooded = true;
else bloodless = true;
int newdmg = hits[i].a.DamageMobj(invoker,self,dmg,'Melee',flg,atan2(hits[i].dir.y,hits[i].dir.x));
// things can instantly cease to exist after taking damage (wow)
if ( hits[i].a )
{
if ( hits[i].a.player ) hits[i].a.A_QuakeEx(quakin,quakin,quakin,6,0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:0.125*quakin);
if ( !hits[i].a.bNOBLOOD && !hits[i].a.bDORMANT && (raging || !hits[i].a.bINVULNERABLE) )
{
hits[i].a.TraceBleed(newdmg,invoker);
hits[i].a.SpawnBlood(hits[i].pos,atan2(hits[i].dir.y,hits[i].dir.x)+180,newdmg);
}
else
{
let p = Spawn(raging?"BigPunchImpact":"PunchImpact",hits[i].pos);
p.angle = atan2(hits[i].dir.y,hits[i].dir.x);
}
}
if ( raging )
{
let ps = Spawn("BigPunchSplash",hits[i].pos);
ps.target = self;
ps.special1 = dmg;
}
}
self.angle += clamp(diff/hits.Size(),-5.,5.); // averaged reorient
invoker.bEXTREMEDEATH = invoker.default.bEXTREMEDEATH;
invoker.bNOEXTREMEDEATH = invoker.default.bEXTREMEDEATH;
A_QuakeEx(quakin/2,quakin/2,quakin/2,3,0,1,"",QF_RELATIVE|QF_SCALEDOWN,rollIntensity:0.06*quakin);
if ( raging )
{
if ( blooded ) A_StartSound("pusher/altmeat",CHAN_WEAPON,CHANF_OVERLAP);
if ( bloodless ) A_StartSound("pusher/althit",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 A_StartSound(hitsound,CHAN_WEAPON,CHANF_OVERLAP);
A_AlertMonsters(swwm_uncapalert?0:300);
A_BumpFOV(.96);
return true;
}
action void A_Melee( int dmg = 40, String hitsound = "", double rangemul = 1., double spreadmul = 1., double kickmul = 1. )
{
let raging = RagekitPower(FindInventory("RagekitPower"));
if ( raging ) rangemul += .2;
Vector3 origin = Vec3Offset(0,0,player.viewheight);
Vector3 dir = (cos(angle)*cos(pitch),sin(angle)*cos(pitch),sin(-pitch));
// check for usables
let ut = new("UseLineTracer");
ut.uses.Clear();
ut.Trace(origin,CurSector,dir,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')
|| (ut.uses[i].hitactor is 'FroggyChair') ) 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;
// check for shootables
SWWMBulletTrail.DoTrail(self,origin,dir,DEFMELEERANGE*rangemul,0);
if ( TryMelee((raging?.3:.2)*spreadmul,dmg,hitsound,rangemul,kickmul) )
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 ( 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,rollIntensity:0.12*quakin);
A_BumpFOV(.98);
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);
}
}