swwmgz_m/zscript/swwm_gesture_fx.zsc
Marisa the Magician acc4341206 Revert "We should not manipulate the FRIENDLY flag directly, apparently."
This reverts commit 68704316e7.

Turns out this wasn't really necessary here, as I was already doing the "bookkeeping" myself.
2024-10-04 23:00:50 +02:00

580 lines
16 KiB
Text

// gesture effects
Class LoveHeartTrail : SWWMNonInteractiveActor
{
Default
{
RenderStyle "Add";
Alpha .1;
+FORCEXYBILLBOARD;
}
override void Tick()
{
if ( freezetics > 0 )
{
freezetics--;
return;
}
if ( isFrozen() ) return;
A_FadeOut(.01);
scale *= .95;
}
States
{
Spawn:
DOKI A -1 Bright;
Stop;
}
}
Class LoveHeartSparkle : SWWMNonInteractiveActor
{
Default
{
Scale .03;
+FORCEXYBILLBOARD;
}
override void PostBeginPlay()
{
Scale *= FRandom[ExploS](.75,1.5);
specialf1 = FRandom[ExploS](.95,.98);
specialf2 = FRandom[ExploS](.01,.03);
vel = SWWMUtility.Vec3FromAngles(angle,pitch)*FRandom[ExploS](2,8);
}
override void Tick()
{
if ( freezetics > 0 )
{
freezetics--;
return;
}
if ( isFrozen() ) return;
A_SetScale(scale.x*specialf1);
A_FadeOut(specialf2);
Vector3 dir = vel;
double magvel = dir.length();
magvel *= .99;
if ( magvel > 0. )
{
dir /= magvel;
dir += .2*SWWMUtility.Vec3FromAngles(FRandom[ExploS](0,360),FRandom[ExploS](-90,90));
vel = dir.unit()*magvel;
}
SetOrigin(level.Vec3Offset(pos,vel),true);
}
States
{
Spawn:
DOKI A -1 Bright;
Stop;
}
}
Class LoveHeartBurstLight : PaletteLight
{
Default
{
Tag "LovePal";
ReactionTime 15;
Args 0,0,0,150;
}
}
Class LoveHeart : Actor
{
Default
{
Obituary "$O_DOKIDOKI";
DamageType 'Love';
DamageFunction (clamp(special2,5,15));
Radius 4;
Height 4;
Speed 10;
Scale .2;
PROJECTILE;
+BLOODLESSIMPACT;
+FORCEXYBILLBOARD;
+SEEKERMISSILE;
+FOILINVUL;
+PAINLESS;
+NODAMAGETHRUST;
+NOFRICTION;
+SKYEXPLODE;
}
override int DoSpecialDamage( Actor target, int damage, Name damagetype )
{
bEXTREMEDEATH = false;
bNOEXTREMEDEATH = false;
SWWMStats s = null;
if ( Demolitionist(self.target) )
{
s = Demolitionist(self.target).mystats;
// guaranteed on first smooch
if ( !Demolitionist(self.target).lastkiss || !Random[DemoLines](0,2) )
{
if ( gametic > Demolitionist(self.target).lastkiss )
SWWMHandler.AddOneliner("smooch",2,20);
Demolitionist(self.target).lastkiss = gametic+140;
}
}
if ( s ) s.smooch++;
let raging = RagekitPower(self.target.FindInventory("RagekitPower"));
if ( (target is 'WolfensteinSS') || (target.Species == 'WolfensteinSS') ) target.bFRIENDLY = false;
if ( target.IsFriend(self.target) || SWWMUtility.IdentifyingDog(target) )
{
int healamt = clamp(special2,5,15);
if ( raging )
{
healamt *= 8;
raging.DoHitFX();
}
if ( target.GiveBody(healamt,target.GetSpawnHealth()) )
SWWMHandler.DoFlash(target,Color(32,224,128,255),10);
if ( SWWMUtility.IdentifyingDog(target) )
{
// befriend good doggo
if ( target.bCOUNTKILL )
{
target.bCOUNTKILL = false;
level.total_monsters--;
}
if ( !target.bFRIENDLY && s )
s.befriend++;
target.bFRIENDLY = true;
if ( deathmatch )
target.SetFriendPlayer(self.target.player);
}
return 0;
}
Vector3 dirto = level.Vec3Diff(pos,target.Vec3Offset(0,0,target.Height/2)).unit();
SWWMUtility.DoKnockback(target,dirto,1500.*damage);
let bread = target.FindState("Pain");
if ( bread ) target.SetState(bread);
if ( raging )
{
damage *= 8;
raging.DoHitFX();
}
if ( (target is 'WolfensteinSS') || (target.Species == 'WolfensteinSS') )
{
damage = int.max;
bEXTREMEDEATH = true;
}
else if ( target is 'SWWMHangingKeen' )
damage = max(target.Health,damage); // rescued by love :3
return damage;
}
override int SpecialMissileHit( Actor victim )
{
if ( !victim.bSHOOTABLE && (victim != tracer) ) return MHIT_PASS;
if ( tracer && (victim != tracer) ) return MHIT_PASS;
return MHIT_DEFAULT;
}
void A_HeartTick()
{
special1++;
if ( !(special1%3) && (special2 > 0) ) special2--;
A_SetScale(.2+.02*sin(special1*.25*GameTicRate));
double magvel = vel.length();
if ( magvel > 0 )
{
Vector3 dir = vel/magvel;
vel = dir*min(30,magvel*1.1);
}
double steppy = vel.length()/4.;
for ( int i=2; i<6; i++ )
{
Vector3 dir2 = vel.unit();
let t = Spawn("LoveHeartTrail",level.Vec3Offset(pos,-dir2*steppy*i));
t.scale = scale;
}
int numpt = Random[ExploS](1,3);
for ( int i=0; i<numpt; i++ )
{
let s = Spawn("LoveHeartSparkle",pos);
s.angle = FRandom[ExploS](0,360);
s.pitch = FRandom[ExploS](-90,90);
}
if ( !tracer || (tracer.Health <= 0) ) return;
double mag = vel.length();
vel = mag*(level.Vec3Diff(pos,tracer.Vec3Offset(0,0,tracer.height/2)).unit()*mag*6./GameTicRate+vel).unit();
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
A_StartSound("misc/heart",CHAN_WEAPON);
A_AttachLight('LOVELIGHT',DynamicLight.PointLight,Color(255,176,208),80,80,DYNAMICLIGHT.LF_ATTENUATE);
special2 = 25;
}
void CheckDefaceTexture()
{
TextureID HitTexture;
Line HitLine;
int LineSide, LinePart;
Sector HitSector;
F3DFloor Hit3DFloor;
bool HitCeiling;
if ( BlockingLine )
{
HitLine = BlockingLine;
// which side and part we hit?
LineSide = Level.PointOnLineSide(pos.xy,BlockingLine);
double fl, cl;
if ( BlockingLine.sidedef[1] )
{
fl = max(BlockingLine.frontsector.floorplane.ZAtPoint(pos.xy),BlockingLine.backsector.floorplane.ZAtPoint(pos.xy));
cl = min(BlockingLine.frontsector.ceilingplane.ZAtPoint(pos.xy),BlockingLine.backsector.ceilingplane.ZAtPoint(pos.xy));
if ( max(floorz,pos.z) < fl ) LinePart = 2;
else if ( min(ceilingz,pos.z+height) > cl ) LinePart = 0;
else LinePart = 1;
}
else
{
LinePart = 1; // always middle
LineSide = 0;
}
if ( Blocking3DFloor )
{
Sector sec = LineSide?BlockingLine.frontsector:BlockingLine.backsector;
for ( int i=0; i<sec.Get3DFloorCount(); i++ )
{
F3DFloor ff = sec.Get3DFloor(i);
if ( ff.model != Blocking3DFloor ) continue;
Hit3DFloor = ff;
break;
}
if ( Hit3DFloor )
{
if ( Hit3DFloor.flags&F3DFloor.FF_UPPERTEXTURE ) HitTexture = HitLine.sidedef[LineSide].GetTexture(0);
else if ( Hit3DFloor.flags&F3DFloor.FF_LOWERTEXTURE ) HitTexture = HitLine.sidedef[LineSide].GetTexture(2);
}
}
else HitTexture = HitLine.sidedef[LineSide].GetTexture(LinePart);
}
else if ( BlockingCeiling )
{
if ( Blocking3DFloor )
{
for ( int i=0; i<BlockingCeiling.Get3DFloorCount(); i++ )
{
F3DFloor ff = BlockingCeiling.Get3DFloor(i);
if ( ff.model != Blocking3DFloor ) continue;
Hit3DFloor = ff;
break;
}
}
HitSector = BlockingCeiling;
HitCeiling = true;
HitTexture = ceilingpic;
}
else if ( BlockingFloor )
{
if ( Blocking3DFloor )
{
for ( int i=0; i<BlockingFloor.Get3DFloorCount(); i++ )
{
F3DFloor ff = BlockingFloor.Get3DFloor(i);
if ( ff.model != Blocking3DFloor ) continue;
Hit3DFloor = ff;
break;
}
}
HitSector = BlockingFloor;
HitCeiling = false;
HitTexture = floorpic;
}
let [remove, replacewith] = SWWMUtility.DefaceTexture(HitTexture);
if ( !remove ) return;
A_StartSound("bestsound",CHAN_ITEMEXTRA,CHANF_OVERLAP);
if ( target && target.player )
{
int scr = (TexMan.GetName(HitTexture).Left(6)~=="ZZWOLF")?200:20;
if ( scr == 20 ) SWWMUtility.AchievementProgressInc("doodle",1,player);
SWWMCredits.Give(target.player,scr);
if ( target.player == players[consoleplayer] ) SWWMScoreObj.Spawn(scr,pos);
}
if ( HitLine )
{
if ( Hit3DFloor )
{
// TODO connected textures for upper/lower
if ( Hit3DFloor.flags&F3DFloor.FF_UPPERTEXTURE ) HitLine.sidedef[LineSide].SetTexture(0,replacewith);
else if ( Hit3DFloor.flags&F3DFloor.FF_LOWERTEXTURE ) HitLine.sidedef[LineSide].SetTexture(2,replacewith);
else Hit3DFloor.master.sidedef[0].SetTexture(1,replacewith);
}
else
{
// find connected sidedefs with the same texture
Array<Line> con;
con.Clear();
con.Push(HitLine);
Sector s = LineSide?HitLine.backsector:HitLine.frontsector;
int found = 0;
do
{
found = 0;
foreach ( l:s.Lines )
{
if ( !l.sidedef[LineSide] || (l.sidedef[LineSide].GetTexture(LinePart) != HitTexture) )
continue;
if ( con.Find(l) < con.Size() ) continue;
bool notmatched = true;
foreach ( c:con )
{
if ( (l.v1 != c.v1) && (l.v2 != c.v2) && (l.v1 != c.v2) && (l.v2 != c.v1) )
continue;
notmatched = false;
break;
}
if ( notmatched ) continue;
con.Push(l);
found++;
}
}
while ( found > 0 );
foreach ( c:con )
c.sidedef[LineSide].SetTexture(LinePart,replacewith);
}
}
else if ( HitSector && HitCeiling )
{
if ( Hit3DFloor )
{
if ( Hit3DFloor.flags&F3DFloor.FF_INVERTSECTOR ) Hit3DFloor.model.SetTexture(1,replacewith);
else Hit3DFloor.model.SetTexture(0,replacewith);
}
else
{
// find connected sectors with the same ceiling texture (THIS IS VERY UGLY CODE)
Array<Sector> con;
con.Clear();
con.Push(HitSector);
int found;
do
{
found = 0;
foreach ( s:con )
{
foreach ( l:s.Lines )
{
// only check two-sided
if ( !l.sidedef[1] ) continue;
// don't check if there's a height difference
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)) )
continue;
if ( (l.frontsector.GetTexture(1) == HitTexture) && (con.Find(l.frontsector) >= con.Size()) )
{
found++;
con.Push(l.frontsector);
}
if ( (l.backsector.GetTexture(1) == HitTexture) && (con.Find(l.backsector) >= con.Size()) )
{
found++;
con.Push(l.backsector);
}
}
}
}
while ( found > 0 );
foreach ( s:con )
s.SetTexture(1,replacewith);
}
}
else if ( HitSector && !HitCeiling )
{
if ( Hit3DFloor )
{
if ( Hit3DFloor.flags&F3DFloor.FF_INVERTSECTOR ) Hit3DFloor.model.SetTexture(0,replacewith);
else Hit3DFloor.model.SetTexture(1,replacewith);
}
else
{
// find connected sectors with the same floor texture (THIS IS VERY UGLY CODE)
Array<Sector> con;
con.Clear();
con.Push(HitSector);
int found;
do
{
found = 0;
foreach ( s:con )
{
foreach ( l:s.Lines )
{
// only check two-sided
if ( !l.sidedef[1] ) continue;
// don't check if there's a height difference
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)) )
continue;
if ( (l.frontsector.GetTexture(0) == HitTexture) && (con.Find(l.frontsector) >= con.Size()) )
{
found++;
con.Push(l.frontsector);
}
if ( (l.backsector.GetTexture(0) == HitTexture) && (con.Find(l.backsector) >= con.Size()) )
{
found++;
con.Push(l.backsector);
}
}
}
}
while ( found > 0 );
foreach ( s:con )
s.SetTexture(0,replacewith);
}
}
}
void A_HeartBurst()
{
CheckDefaceTexture();
// use line
if ( BlockingLine )
{
int s = Level.PointOnLineSide(pos.xy,BlockingLine);
int locknum = SWWMUtility.GetLineLock(BlockingLine);
if ( !locknum || (target && target.CheckKeys(locknum,false,true)) )
BlockingLine.RemoteActivate(target,s,SPAC_Use,pos);
}
if ( swwm_omnibust )
{
int dmg = GetMissileDamage(0,0);
let raging = RagekitPower(self.target.FindInventory("RagekitPower"));
if ( raging ) dmg *= 8;
if ( BusterWall.ProjectileBust(self,dmg,SWWMUtility.Vec3FromAngles(angle,pitch)) )
raging.DoHitFX();
}
A_SetRenderStyle(1.,STYLE_Add);
// RemoveLight causes heavy performance issues, just overwrite with a "blank light"
A_AttachLight('LOVELIGHT',DynamicLight.PointLight,0,0,0);
//A_RemoveLight('LOVELIGHT');
CheckSplash(40);
A_QuakeEx(1.5,1.5,1.5,8,0,300,"",QF_RELATIVE|QF_SCALEDOWN|QF_3D);
A_SprayDecal("HeartyGlow",64);
A_StartSound("bestsound",CHAN_VOICE);
Spawn("LoveHeartBurstLight",pos);
int numpt = Random[ExploS](10,15);
for ( int i=0; i<numpt; i++ )
{
Vector3 pvel = SWWMUtility.Vec3FromAngles(FRandom[ExploS](0,360),FRandom[ExploS](-90,90))*FRandom[ExploS](.5,4);
let s = Spawn("SWWMSmoke",pos);
s.vel = pvel;
s.SetShade(Color(10,8,9)*Random[ExploS](20,25));
s.special1 = Random[ExploS](1,3);
s.scale *= 2.;
s.alpha *= .6;
}
numpt = Random[ExploS](40,50);
for ( int i=0; i<numpt; i++ )
{
let s = Spawn("LoveHeartSparkle",pos);
s.angle = FRandom[ExploS](0,360);
s.pitch = FRandom[ExploS](-90,90);
s.scale *= RandomPick[ExploS](1,3);
s.alpha *= 2;
}
}
void A_HeartDie()
{
scale *= 1.2;
A_FadeOut();
}
States
{
Spawn:
DOKI A 1 Bright A_HeartTick();
Wait;
Death:
DOKI A 0 Bright A_HeartBurst();
DOKI A 1 Bright A_HeartDie();
Wait;
}
}
Class HeadpatTracker : Actor
{
State patstate; // state the target will jump to on headpat
// if null, will simply resume current state
State deathstate; // if the actor enters this state, we can't headpat no more
int oldtargettics; // previous tics left in target's pre-pat state
bool patting; // currently in pat
Actor patter; // who's patting
double heightfix; // these two are fixes for certain monsters where their head
double angfix; // isn't positioned where one would expect
bool lethalpat; // ending headpat immediately drops enemy health to 0
bool dvacationarghack; // hackfix for some girls in doom vacation, disallows headpats if args[0] is 1
default
{
+NOGRAVITY;
+NOTELEPORT;
+DONTSPLASH;
}
override void Tick()
{
if ( !target || (target.Health <= 0) || (deathstate && target.InStateSequence(target.CurState,deathstate)) )
{
Destroy();
return;
}
if ( patting )
{
// keep bolted in here
target.vel *= 0;
patter.vel *= 0;
patter.player.vel *= 0;
target.SetOrigin(pos,true);
// keep aim
double delta = deltaangle(target.angle,target.AngleTo(patter));
if ( abs(delta) < 1. ) target.angle = target.AngleTo(patter);
else target.angle += .3*delta;
delta = deltaangle(patter.angle,patter.AngleTo(target)+angfix);
if ( abs(delta) < 1. ) patter.A_SetAngle(patter.AngleTo(target)+angfix,SPF_INTERPOLATE);
else patter.A_SetAngle(patter.angle+.3*delta,SPF_INTERPOLATE);
double hfact = 1.2-heightfix;
delta = deltaangle(patter.pitch,SWWMUtility.PitchTo(patter,target,hfact));
if ( abs(delta) < 1. ) patter.A_SetPitch(SWWMUtility.PitchTo(patter,target,hfact),SPF_INTERPOLATE);
else patter.A_SetPitch(patter.pitch+.3*delta,SPF_INTERPOLATE);
return;
}
if ( (radius != target.radius+8) || (height != target.height+8) )
A_SetSize(target.radius+8,target.height+8);
if ( pos != target.pos ) SetOrigin(target.pos,false);
}
override bool Used( Actor user )
{
if ( !target ) return false;
if ( patting ) return false; // already on it
if ( user.player.crouchdir == -1 ) return false; // need to be standing up
if ( !user.player.onground ) return false; // need to be on solid ground
if ( dvacationarghack && (target.args[0] == 1) ) return false; // can't pat at the moment
if ( !(user.player.WeaponState&WF_WEAPONSWITCHOK) || (user.player.WeaponState&WF_DISABLESWITCH) ) return false; // weapon needs to be ready for switching
// check use range
Vector3 diff = level.Vec3Diff(user.Vec2OffsetZ(0,0,user.player.viewz),Vec3Offset(0,0,target.Height));
if ( abs(diff.z) > PlayerPawn(user.player.mo).UseRange ) return false;
if ( user is 'Demolitionist' )
{
patter = user;
let g = SWWMGesture.SetGesture(Demolitionist(patter),GS_Headpat);
if ( !g ) return false; // can't headpat at the moment
patting = true;
g.pats = self;
oldtargettics = target.tics;
target.tics = -1;
patter.player.cheats |= CF_TOTALLYFROZEN;
Demolitionist(patter).scriptedinvul = true;
target.bDORMANT = true;
if ( SWWMUtility.IdentifyingDog(target) ) SWWMUtility.MarkAchievement("mbf",user.player);
else if ( SWWMUtility.IdentifyingCaco(target) ) SWWMHandler.AddOneliner("petcaco",2,20);
return true;
}
return false;
}
}