swwmgz_m/zscript/swwm_common.zsc
Marisa Kirisame aabc9de051 0.9.10b release (oh boy where do I start):
- New fun options implemented (omnibusting, unlimited fuel, party time)
 - Biospark Carbine gets a requested nerf
 - Candygun combo fire has been buffed (watch out for that splash damage)
 - All powerup effects are additive (stacc 'em)
 - Automap hud respects gzdoom's cvars for toggling certain elements
 - Automap hud shows stats and times in gold when 100% / under par
 - Weapons and ammo are no longer affected by skill amount modifiers, for balance (and to avoid any weird glitches)
 - Sorting improvements for menu (weapons by slot, ammo by weapons, other items by value, etc.)
 - Grilled Cheese Sandwich now saves you from lethal falls properly
 - Blown kisses instakill nazis
 - Added non-violent Keen replacement (based on "Less mean-spirited Keen replacement" by SiFi270)
 - Added gib deaths for hell nobles, pinkies, cacos, revs and viles (sprites by Amuscaria and Ryan Cordell)
 - Blown kisses can activate use switches
 - Gestures can be chained by pressing a gesture button while another is playing
 - Fixed Grilled Cheese Sandwich not avoiding telefrags properly (now also works with voodoo dolls)
 - More precise weapon kill tracking (fixes some ragekit quirks)
 - Merge both DLC weaponsets, removing redundant weapons (see FuturePlans.md)
 - Discarded some collectables for the next updates, to save time
 - Preparation work for collectables update, including some (partial) lore files
 - Remove ammo fabricators from store, makes no sense to have them when you can just buy ammo directly
 - Cosmetic Boss Brain sprite replacements, just for fun
 - 10 more intermission tips, because yes
 - Added option to reduce distance at which enemy healthbars are picked
 - Various minor bugfixes and adjustments (and also some tiny typo fixes)
 - Ragekit now heals over time and with each hit (so it's more rewarding to go wild)
 - PNG optimization pass (again lol)
 - Fix crouched gestures having no facial animation
2020-10-05 23:29:28 +02:00

1274 lines
29 KiB
Text

// common code goes here
enum ESWWMGZChannels
{
CHAN_YOUDONEFUCKEDUP = 63200, // exception handler
CHAN_DEMOVOICE = 63201, // demolitionist voices
CHAN_FOOTSTEP = 63202, // footstep sounds and others
CHAN_WEAPONEXTRA = 63203, // additional weapon sounds (usually loops)
CHAN_POWERUP = 63204, // powerup sounds
CHAN_POWERUPEXTRA = 63205, // additional powerup sounds
CHAN_JETPACK = 63206, // jetpack sound
CHAN_ITEMEXTRA = 63207, // additional item sounds
CHAN_WEAPONEXTRA2 = 63208, // additional weapon sound slot
CHAN_WEAPONEXTRA3 = 63209, // additional weapon sound slot (again)
CHAN_DAMAGE = 63210, // used for impact/hit sounds
CHAN_AMBEXTRA = 63211 // player ambience when submerged
};
// Future planning, will be filled out with AI stuff and whatnot someday
Class SWWMMonster : Actor
{
// integrated fun tags
virtual clearscope String GetFunTag( String defstr = "" )
{
return GetTag(defstr);
}
// the function that should be overriden in subclasses
virtual int HandleLocationalDamage( Actor inflictor, Actor source, int damage, Name mod, Vector3 HitLocation, int flags = 0, double angle = 0 )
{
return damage;
}
// locational damage support, akin to UE1, but hitlocation will be treated as a relative offset, to make things easier
// this one should be called directly by everything in this mod, when possible
int LocationalDamageMobj( Actor inflictor, Actor source, int damage, Name mod, Vector3 HitLocation, int flags = 0, double angle = 0 )
{
damage = HandleLocationalDamage(inflictor,source,damage,mod,HitLocation,flags,angle);
return Super.DamageMobj(inflictor,source,damage,mod,flags,angle);
}
// "estimated" locational damage for the vanilla DamageMobj
override int DamageMobj( Actor inflictor, Actor source, int damage, Name mod, int flags, double angle )
{
Vector3 guesspos = (0,0,Height/2.);
// use inflictor if available, as it may be a projectile or hitscan puff
// if damage comes from an item, use owner
// all of this could be done better (or implemented as an engine feature), but whatever
Actor whomst = inflictor?inflictor:source;
if ( whomst is 'Inventory' ) whomst = Inventory(whomst).Owner;
if ( whomst )
{
if ( whomst.bMISSILE || (flags&DMG_INFLICTOR_IS_PUFF) )
guesspos = level.Vec3Diff(pos,whomst.pos);
else guesspos = level.Vec3Diff(pos,whomst.Vec3Offset(0,0,whomst.Height/2));
guesspos.x = clamp(guesspos.x,-radius,radius);
guesspos.y = clamp(guesspos.y,-radius,radius);
guesspos.z = clamp(guesspos.z,0,height);
}
return LocationalDamageMobj(inflictor,source,damage,mod,guesspos,flags,angle);
}
}
// Less mean-spirited Keen
Class SWWMHangingKeen : Actor
{
action void A_DropKeen()
{
Spawn("SWWMDroppedKeen",Vec3Offset(0,0,8));
}
Default
{
Tag "$FN_KEEN";
Health 100;
Radius 10;
Height 54;
Mass int.max;
PainChance 256;
+SOLID;
+SPAWNCEILING;
+NOGRAVITY;
+SHOOTABLE;
+NOICEDEATH;
+DONTFALL;
+NOBLOOD;
+DONTTHRUST;
PainSound "keen/pain";
DeathSound "keen/death";
}
States
{
Spawn:
KEE2 A -1;
Stop;
Death:
KEE2 A 6;
KEE2 B 6 A_DropKeen();
KEE2 C 6 A_Scream();
KEE2 DE 6;
KEE2 F 30;
KEE2 F -1 A_KeenDie();
Stop;
Pain:
KEE2 G 4;
KEE2 G 8 A_Pain();
Goto Spawn;
}
}
Class SWWMDroppedKeen : Actor
{
Default
{
Radius 10;
Height 32;
Gravity .5;
+NOBLOCKMAP;
}
States
{
Spawn:
KEE3 A 1 A_JumpIf(pos.z<=floorz,1);
Wait;
KEE3 B 1 { vel.z = 4; }
KEE3 B 1 A_JumpIf(pos.z<=floorz,1);
Wait;
KEE3 B 1 { vel.z = 2; }
KEE3 B 1 A_JumpIf(pos.z<=floorz,1);
Wait;
KEE3 B 12;
TNT1 A 1 { Spawn("TeleportFog",pos,ALLOW_REPLACE); }
Stop;
}
}
// imitates UE1 light type LT_TexturePaletteOnce/LT_TexturePaletteLoop
Class PaletteLight : DynamicLight
{
Color pal[256];
bool IsLooping;
Default
{
Tag "Explosion";
DynamicLight.Type "Point";
Args 0,0,0,80;
ReactionTime 15;
}
private void UpdateLight()
{
int index = clamp(255-((255*ReactionTime)/abs(default.ReactionTime)),0,255);
args[LIGHT_RED] = pal[index].r;
args[LIGHT_GREEN] = pal[index].g;
args[LIGHT_BLUE] = pal[index].b;
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
int lump = Wads.CheckNumForFullname(String.Format("palettes/%s.pal",GetTag()));
String paldat = Wads.ReadLump(lump);
for ( int i=0; i<256; i++ )
{
pal[i].r = paldat.ByteAt(i*3);
pal[i].g = paldat.ByteAt(i*3+1);
pal[i].b = paldat.ByteAt(i*3+2);
}
if ( ReactionTime < 0 )
{
ReactionTime = -ReactionTime;
IsLooping = true;
}
UpdateLight();
}
override void Tick()
{
Super.Tick();
if ( isFrozen() ) return;
ReactionTime--;
if ( ReactionTime < 0 )
{
if ( !IsLooping )
{
Destroy();
return;
}
else ReactionTime = abs(default.ReactionTime);
}
if ( target ) SetOrigin(target.pos,true);
UpdateLight();
}
}
// Generic smoke, lightweight tick
Class SWWMSmoke : Actor
{
Default
{
RenderStyle "Shaded";
StencilColor "FFFFFF";
Radius 2;
Height 2;
+NOBLOCKMAP;
+NOGRAVITY;
+DONTSPLASH;
+FORCEXYBILLBOARD;
+ROLLSPRITE;
+ROLLCENTER;
+THRUACTORS;
+NOTELEPORT;
+NOINTERACTION;
Scale .3;
}
override void PostBeginPlay()
{
double ang, pt;
scale *= FRandom[Puff](.5,1.5);
alpha *= FRandom[Puff](.5,1.5);
ang = FRandom[Puff](0,360);
pt = FRandom[Puff](-90,90);
vel += (cos(pt)*cos(ang),cos(pt)*sin(ang),-sin(pt))*FRandom[Puff](.2,.8);
roll = Frandom[Puff](0,360);
scale.x *= RandomPick[Puff](-1,1);
scale.y *= RandomPick[Puff](-1,1);
}
override void Tick()
{
prev = pos; // for interpolation
if ( isFrozen() ) return;
vel *= .96;
vel.z += .01;
FCheckPosition tm;
// movement subdivision (these damn things are tiny)
int steps = 8;
while ( (abs(vel.x) >= radius*steps) || (abs(vel.y) >= radius*steps) || (abs(vel.z) >= height*steps) )
steps++;
bool domove = (vel!=(0,0,0));
if ( domove )
{
Vector3 steppy = vel/steps;
int changexy = steppy.X||steppy.Y;
bool readjust = false;
for ( int i=0; i<steps; i++ )
{
if ( changexy && !TryMove(Vec2Offset(steppy.x,steppy.y),1,false,tm) )
{
// hit wall, slide along it
if ( BlockingLine )
{
Vector3 normal = (-BlockingLine.delta.y,BlockingLine.delta.x,0).unit();
if ( !SWWMUtility.PointOnLineSide(pos.xy,BlockingLine) )
normal *= -1;
vel = vel-FRandom[Puff](1.,1.2)*normal*(vel dot normal);
}
readjust = true;
}
AddZ(steppy.z);
UpdateWaterLevel();
if ( pos.z <= floorz )
{
// landed on floor, slide along it
SetZ(floorz);
F3DFloor ff;
for ( int i=0; i<FloorSector.Get3DFloorCount(); i++ )
{
if ( !(FloorSector.Get3DFloor(i).flags&F3DFloor.FF_SOLID) ) continue;
if ( !(FloorSector.Get3DFloor(i).top.ZAtPoint(pos.xy) ~== floorz) ) continue;
ff = FloorSector.Get3DFloor(i);
break;
}
Vector3 normal;
if ( ff ) normal = -ff.model.ceilingplane.Normal;
else normal = FloorSector.floorplane.Normal;
vel = vel-FRandom[Puff](1.,1.2)*normal*(vel dot normal);
readjust = true;
}
if ( pos.z+height > ceilingz )
{
// hit the ceiling, slide along it
SetZ(ceilingz-height);
F3DFloor ff;
for ( int i=0; i<CeilingSector.Get3DFloorCount(); i++ )
{
if ( !(CeilingSector.Get3DFloor(i).flags&F3DFloor.FF_SOLID) ) continue;
if ( !(CeilingSector.Get3DFloor(i).top.ZAtPoint(pos.xy) ~== ceilingz) ) continue;
ff = CeilingSector.Get3DFloor(i);
break;
}
Vector3 normal;
if ( ff ) normal = -ff.model.floorplane.Normal;
else normal = CeilingSector.ceilingplane.Normal;
vel = vel-FRandom[Puff](1.,1.2)*normal*(vel dot normal);
readjust = true;
}
CheckPortalTransition();
if ( readjust ) break; // movement changed, can't keep stepping
}
}
if ( (waterlevel > 0) && !bAMBUSH )
{
let b = Spawn("SWWMBubble",pos);
b.scale *= abs(scale.x);
b.vel = vel;
Destroy();
return;
}
if ( tics > 0 ) tics--;
while ( !tics )
{
if ( !SetState(CurState.NextState) )
return;
}
}
States
{
Spawn:
XSMK ABCDEFGHIJKLMNOPQRST 1 A_SetTics(1+special1);
Stop;
}
}
// strictly non-interacting smoke, much lighter tick, used for heavier effects
Class SWWMHalfSmoke : Actor
{
Default
{
RenderStyle "Shaded";
StencilColor "FFFFFF";
Radius 0.1;
Height 0;
+NOBLOCKMAP;
+NOGRAVITY;
+DONTSPLASH;
+FORCEXYBILLBOARD;
+ROLLSPRITE;
+ROLLCENTER;
+NOTELEPORT;
+NOINTERACTION;
Scale 0.3;
}
override void PostBeginPlay()
{
double ang, pt;
scale *= FRandom[Puff](0.5,1.5);
alpha *= FRandom[Puff](0.5,1.5);
ang = FRandom[Puff](0,360);
pt = FRandom[Puff](-90,90);
vel += (cos(pt)*cos(ang),cos(pt)*sin(ang),-sin(pt))*FRandom[Puff](0.2,0.8);
roll = Frandom[Puff](0,360);
scale.x *= RandomPick[Puff](-1,1);
scale.y *= RandomPick[Puff](-1,1);
}
override void Tick()
{
prev = pos; // for interpolation
if ( isFrozen() ) return;
vel *= 0.96;
vel.z += 0.01;
SetOrigin(level.Vec3Offset(pos,vel),true);
UpdateWaterLevel();
if ( (waterlevel > 0) && !bAMBUSH )
{
let b = Spawn("SWWMBubble",pos);
b.scale *= abs(scale.x);
b.vel = vel;
Destroy();
return;
}
if ( tics > 0 ) tics--;
while ( !tics )
{
if ( !SetState(CurState.NextState) )
return;
}
}
States
{
Spawn:
XSMK ABCDEFGHIJKLMNOPQRST 1 A_SetTics(1+special1);
Stop;
}
}
Class SWWMSmallSmoke : SWWMHalfSmoke
{
override void PostBeginPlay()
{
double ang, pt;
scale *= FRandom[Puff](0.1,0.3);
alpha *= FRandom[Puff](0.5,1.5);
ang = FRandom[Puff](0,360);
pt = FRandom[Puff](-90,90);
vel += (cos(pt)*cos(ang),cos(pt)*sin(ang),-sin(pt))*FRandom[Puff](0.04,0.16);
roll = Frandom[Puff](0,360);
scale.x *= RandomPick[Puff](-1,1);
scale.y *= RandomPick[Puff](-1,1);
}
States
{
Spawn:
QSM6 ABCDEFGHIJKLMNOPQR 1 A_SetTics(1+special1);
Stop;
}
}
// visual smoke, very strictly non-interacting, updates even when game is frozen
Class SWWMViewSmoke : SWWMHalfSmoke
{
Vector3 ofs, vvel;
override void PostBeginPlay()
{
double ang, pt;
scale *= FRandom[Puff](0.1,0.3);
alpha *= FRandom[Puff](0.5,1.5);
ang = FRandom[Puff](0,360);
pt = FRandom[Puff](-90,90);
vvel += (cos(pt)*cos(ang),cos(pt)*sin(ang),-sin(pt))*FRandom[Puff](0.04,0.16);
}
override void Tick()
{
prev = pos; // for interpolation
if ( !target || !target.player )
{
Destroy();
return;
}
Vector3 x, y, z;
[x, y, z] = swwm_CoordUtil.GetAxes(target.pitch,target.angle,target.roll);
Vector3 origin = level.Vec3Offset(target.Vec2OffsetZ(0,0,target.player.viewz),x*ofs.x+y*ofs.y+z*ofs.z);
SetOrigin(origin,true);
UpdateWaterLevel();
bInvisible = (players[consoleplayer].camera != target);
ofs += vvel;
vvel *= 0.96;
vvel.z += 0.01;
if ( (waterlevel > 0) && !bAMBUSH ) Destroy();
if ( tics > 0 ) tics--;
while ( !tics )
{
if ( !SetState(CurState.NextState) )
return;
}
}
}
Class SWWMBubble : Actor
{
Default
{
RenderStyle "Add";
Radius 2;
Height 2;
+NOBLOCKMAP;
+NOGRAVITY;
+DONTSPLASH;
+FORCEXYBILLBOARD;
+NOTELEPORT;
+THRUACTORS;
+NOINTERACTION;
Scale 0.5;
}
override void PostBeginPlay()
{
double ang, pt;
scale *= FRandom[Puff](0.5,1.5);
ang = FRandom[Puff](0,360);
pt = FRandom[Puff](-90,90);
vel += (cos(pt)*cos(ang),cos(pt)*sin(ang),-sin(pt))*FRandom[Puff](0.2,0.8);
if ( waterlevel <= 0 ) Destroy();
SetState(ResolveState("Spawn")+Random[Puff](0,19));
}
override void Tick()
{
prev = pos;
if ( isFrozen() ) return;
vel *= 0.96;
vel.z += 0.05;
FCheckPosition tm;
// movement subdivision (these damn things are tiny)
int steps = 8;
while ( (abs(vel.x) >= radius*steps) || (abs(vel.y) >= radius*steps) || (abs(vel.z) >= height*steps) )
steps++;
bool domove = (vel!=(0,0,0));
if ( domove )
{
Vector3 steppy = vel/steps;
int changexy = steppy.X||steppy.Y;
bool readjust = false;
for ( int i=0; i<steps; i++ )
{
if ( changexy && !TryMove(Vec2Offset(steppy.x,steppy.y),1,false,tm) )
{
// hit wall, slide along it
if ( BlockingLine )
{
Vector3 normal = (-BlockingLine.delta.y,BlockingLine.delta.x,0).unit();;
if ( !SWWMUtility.PointOnLineSide(pos.xy,BlockingLine) )
normal *= -1;
vel = vel-FRandom[Puff](1.,1.2)*normal*(vel dot normal);
}
readjust = true;
}
AddZ(steppy.z);
UpdateWaterLevel();
if ( pos.z <= floorz )
{
// landed on floor, slide along it
SetZ(floorz);
F3DFloor ff;
for ( int i=0; i<FloorSector.Get3DFloorCount(); i++ )
{
if ( !(FloorSector.Get3DFloor(i).flags&F3DFloor.FF_SOLID) ) continue;
if ( !(FloorSector.Get3DFloor(i).top.ZAtPoint(pos.xy) ~== floorz) ) continue;
ff = FloorSector.Get3DFloor(i);
break;
}
Vector3 normal;
if ( ff ) normal = -ff.model.ceilingplane.Normal;
else normal = FloorSector.floorplane.Normal;
vel = vel-FRandom[Puff](1.,1.2)*normal*(vel dot normal);
readjust = true;
}
if ( pos.z+height > ceilingz )
{
// hit the ceiling, slide along it
SetZ(ceilingz-height);
F3DFloor ff;
for ( int i=0; i<CeilingSector.Get3DFloorCount(); i++ )
{
if ( !(CeilingSector.Get3DFloor(i).flags&F3DFloor.FF_SOLID) ) continue;
if ( !(CeilingSector.Get3DFloor(i).top.ZAtPoint(pos.xy) ~== ceilingz) ) continue;
ff = CeilingSector.Get3DFloor(i);
break;
}
Vector3 normal;
if ( ff ) normal = -ff.model.floorplane.Normal;
else normal = CeilingSector.ceilingplane.Normal;
vel = vel-FRandom[Puff](1.,1.2)*normal*(vel dot normal);
readjust = true;
}
CheckPortalTransition();
if ( readjust ) break; // movement changed, can't keep stepping
}
}
UpdateWaterLevel();
if ( (waterlevel <= 0) || !Random[Puff](0,100) ) Destroy();
if ( tics > 0 ) tics--;
while ( !tics )
{
if ( !SetState(CurState.NextState) )
return;
}
}
States
{
Spawn:
XBUB ABCDEFGHIJKLMNOPQRST 1;
Loop;
}
}
Class SWWMSpark : Actor
{
bool dead;
Sector tracksector;
int trackplane;
Default
{
RenderStyle "Add";
Radius 2;
Height 2;
+NOBLOCKMAP;
+FORCEXYBILLBOARD;
+THRUACTORS;
+NOTELEPORT;
+DONTSPLASH;
+NOINTERACTION;
Gravity 0.2;
Scale 0.05;
}
override void Tick()
{
prev = pos;
if ( isFrozen() ) return;
if ( dead )
{
// do nothing but follow floor movement
if ( tracksector )
{
double trackz;
if ( trackplane ) trackz = tracksector.ceilingplane.ZAtPoint(pos.xy);
else trackz = tracksector.floorplane.ZAtPoint(pos.xy);
if ( trackz != pos.z )
{
SetZ(trackz);
UpdateWaterLevel(false);
}
}
}
else
{
vel.z -= GetGravity();
FCheckPosition tm;
// movement subdivision (these damn things are tiny)
int steps = 8;
while ( (abs(vel.x) >= radius*steps) || (abs(vel.y) >= radius*steps) || (abs(vel.z) >= height*steps) )
steps++;
bool domove = (vel!=(0,0,0));
if ( domove )
{
Vector3 steppy = vel/steps;
int changexy = steppy.X||steppy.Y;
bool readjust = false;
for ( int i=0; i<steps; i++ )
{
if ( changexy && !TryMove(Vec2Offset(steppy.x,steppy.y),1,false,tm) )
{
// hit wall, bounce on it
if ( BlockingLine )
{
Vector3 normal = (-BlockingLine.delta.y,BlockingLine.delta.x,0).unit();;
if ( !SWWMUtility.PointOnLineSide(pos.xy,BlockingLine) )
normal *= -1;
vel = .4*(vel-2.*normal*(vel dot normal));
}
readjust = true;
}
AddZ(steppy.z);
UpdateWaterLevel();
if ( pos.z <= floorz )
{
// landed on floor, bounce on it
SetZ(floorz);
F3DFloor ff;
for ( int i=0; i<FloorSector.Get3DFloorCount(); i++ )
{
if ( !(FloorSector.Get3DFloor(i).flags&F3DFloor.FF_SOLID) ) continue;
if ( !(FloorSector.Get3DFloor(i).top.ZAtPoint(pos.xy) ~== floorz) ) continue;
ff = FloorSector.Get3DFloor(i);
break;
}
Vector3 normal;
if ( ff ) normal = -ff.model.ceilingplane.Normal;
else normal = FloorSector.floorplane.Normal;
vel = .4*(vel-2.*normal*(vel dot normal));
readjust = true;
}
if ( pos.z+height > ceilingz )
{
// hit the ceiling, bounce on it
SetZ(ceilingz-height);
F3DFloor ff;
for ( int i=0; i<CeilingSector.Get3DFloorCount(); i++ )
{
if ( !(CeilingSector.Get3DFloor(i).flags&F3DFloor.FF_SOLID) ) continue;
if ( !(CeilingSector.Get3DFloor(i).top.ZAtPoint(pos.xy) ~== ceilingz) ) continue;
ff = CeilingSector.Get3DFloor(i);
break;
}
Vector3 normal;
if ( ff ) normal = -ff.model.floorplane.Normal;
else normal = CeilingSector.ceilingplane.Normal;
vel = .4*(vel-2.*normal*(vel dot normal));
readjust = true;
}
CheckPortalTransition();
if ( readjust ) break; // movement changed, can't keep stepping
}
}
if ( (max(abs(vel.x),max(abs(vel.y),abs(vel.z))) < 1.) && (pos.z <= floorz) )
{
SetZ(floorz);
F3DFloor ff;
for ( int i=0; i<FloorSector.Get3DFloorCount(); i++ )
{
if ( !(FloorSector.Get3DFloor(i).flags&F3DFloor.FF_SOLID) ) continue;
if ( !(FloorSector.Get3DFloor(i).top.ZAtPoint(pos.xy) ~== floorz) ) continue;
ff = FloorSector.Get3DFloor(i);
break;
}
if ( ff )
{
tracksector = ff.model;
trackplane = 1;
}
else
{
tracksector = FloorSector;
trackplane = 0;
}
vel = (0,0,0);
dead = true;
SetStateLabel("Death");
return;
}
}
UpdateWaterLevel();
if ( waterlevel > 0 )
{
let b = Spawn("SWWMBubble",pos);
b.vel = vel;
b.scale *= 0.3;
Destroy();
return;
}
if ( tics > 0 ) tics--;
while ( !tics )
{
if ( !SetState(CurState.NextState) )
return;
}
}
States
{
Spawn:
BLPF A 1 Bright A_FadeOut(0.01);
Wait;
Death:
BLPF A 1 Bright A_FadeOut(0.05);
Wait;
}
}
Class SWWMChip : Actor
{
SWWMChip prevchip, nextchip;
bool killme;
double anglevel, pitchvel, rollvel;
bool dead;
Sector tracksector;
int trackplane;
Default
{
Radius 2;
Height 2;
+NOBLOCKMAP;
+THRUACTORS;
+NOTELEPORT;
+DONTSPLASH;
+INTERPOLATEANGLES;
+ROLLSPRITE;
+ROLLCENTER;
+FORCEXYBILLBOARD;
+NOINTERACTION;
Gravity 0.35;
Scale 0.2;
}
override void PostBeginPlay()
{
anglevel = FRandom[Junk](10,30)*RandomPick[Junk](-1,1);
pitchvel = FRandom[Junk](10,30)*RandomPick[Junk](-1,1);
rollvel = FRandom[Junk](10,30)*RandomPick[Junk](-1,1);
frame = Random[Junk](0,7);
scale *= Frandom[Junk](0.8,1.2);
SWWMHandler.QueueChip(self);
}
override void OnDestroy()
{
SWWMHandler.DeQueueChip(self);
Super.OnDestroy();
}
override void Tick()
{
prev = pos; // for interpolation
if ( isFrozen() ) return;
if ( dead )
{
// do nothing but follow floor movement
if ( tracksector )
{
double trackz;
if ( trackplane ) trackz = tracksector.ceilingplane.ZAtPoint(pos.xy);
else trackz = tracksector.floorplane.ZAtPoint(pos.xy);
if ( trackz != pos.z )
{
SetZ(trackz);
UpdateWaterLevel(false);
}
}
}
else
{
vel.z -= GetGravity();
FCheckPosition tm;
// movement subdivision (these damn things are tiny)
int steps = 8;
while ( (abs(vel.x) >= radius*steps) || (abs(vel.y) >= radius*steps) || (abs(vel.z) >= height*steps) )
steps++;
bool domove = (vel!=(0,0,0));
if ( domove )
{
Vector3 steppy = vel/steps;
int changexy = steppy.X||steppy.Y;
bool readjust = false;
for ( int i=0; i<steps; i++ )
{
if ( changexy && !TryMove(Vec2Offset(steppy.x,steppy.y),1,false,tm) )
{
// hit wall, bounce on it
if ( BlockingLine )
{
Vector3 normal = (-BlockingLine.delta.y,BlockingLine.delta.x,0).unit();;
if ( !SWWMUtility.PointOnLineSide(pos.xy,BlockingLine) )
normal *= -1;
vel = .3*(vel-2.*normal*(vel dot normal));
}
readjust = true;
}
AddZ(steppy.z);
UpdateWaterLevel();
if ( pos.z <= floorz )
{
// landed on floor, bounce on it
SetZ(floorz);
F3DFloor ff;
for ( int i=0; i<FloorSector.Get3DFloorCount(); i++ )
{
if ( !(FloorSector.Get3DFloor(i).top.ZAtPoint(pos.xy) ~== floorz) ) continue;
ff = FloorSector.Get3DFloor(i);
break;
}
Vector3 normal;
if ( ff ) normal = -ff.model.ceilingplane.Normal;
else normal = FloorSector.floorplane.Normal;
vel = .3*(vel-2.*normal*(vel dot normal));
readjust = true;
}
if ( pos.z+height > ceilingz )
{
// hit the ceiling, bounce on it
SetZ(ceilingz-height);
F3DFloor ff;
for ( int i=0; i<CeilingSector.Get3DFloorCount(); i++ )
{
if ( !(CeilingSector.Get3DFloor(i).top.ZAtPoint(pos.xy) ~== ceilingz) ) continue;
ff = CeilingSector.Get3DFloor(i);
break;
}
Vector3 normal;
if ( ff ) normal = -ff.model.floorplane.Normal;
else normal = CeilingSector.ceilingplane.Normal;
vel = .3*(vel-2.*normal*(vel dot normal));
readjust = true;
}
CheckPortalTransition();
if ( readjust )
{
SetStateLabel("Bounce");
break; // movement changed, can't keep stepping
}
}
}
if ( (max(abs(vel.x),max(abs(vel.y),abs(vel.z))) < 1.) && (pos.z <= floorz) )
{
SetZ(floorz);
F3DFloor ff;
for ( int i=0; i<FloorSector.Get3DFloorCount(); i++ )
{
if ( !(FloorSector.Get3DFloor(i).flags&F3DFloor.FF_SOLID) ) continue;
if ( !(FloorSector.Get3DFloor(i).top.ZAtPoint(pos.xy) ~== floorz) ) continue;
ff = FloorSector.Get3DFloor(i);
break;
}
if ( ff )
{
tracksector = ff.model;
trackplane = 1;
}
else
{
tracksector = FloorSector;
trackplane = 0;
}
vel = (0,0,0);
pitch = 0;
roll = 0;
dead = true;
SetStateLabel("Death");
return;
}
}
if ( killme ) A_FadeOut(.01);
if ( waterlevel > 0 )
{
vel *= .98;
rollvel *= .98;
}
if ( !CheckNoDelay() || (tics == -1) ) return;
if ( tics > 0 ) tics--;
while ( !tics )
{
if ( !SetState(CurState.NextState) )
return;
}
}
States
{
Spawn:
XZW1 # 1
{
angle += anglevel;
pitch += pitchvel;
roll += rollvel;
}
Loop;
Bounce:
XZW1 # 0
{
anglevel = FRandom[Junk](10,30)*RandomPick[Junk](-1,1);
pitchvel = FRandom[Junk](10,30)*RandomPick[Junk](-1,1);
rollvel = FRandom[Junk](10,30)*RandomPick[Junk](-1,1);
}
Goto Spawn;
Death:
XZW2 # -1;
Stop;
}
}
Class SWWMNothing : Actor
{
States
{
Spawn:
TNT1 A 1;
Stop;
}
}
Class PoofLight : PaletteLight
{
Default
{
Tag "Yellow";
ReactionTime 5;
Args 0,0,0,60;
}
}
Class PoofLight2 : PaletteLight
{
Default
{
Tag "Yellow";
ReactionTime 20;
Args 0,0,0,90;
}
}
Class SWWMItemFog : Actor
{
Default
{
RenderStyle "Add";
+NOGRAVITY;
+NOBLOCKMAP;
+DONTSPLASH;
+ROLLSPRITE;
+ROLLCENTER;
+NOINTERACTION;
}
States
{
Spawn:
BLPF A 2 Bright NoDelay
{
// offset up
SetOrigin(Vec3Offset(0,0,16),false);
roll = FRandom[ExploS](0,360);
scale *= FRandom[ExploS](0.9,1.1);
scale.x *= RandomPick[ExploS](-1,1);
scale.y *= RandomPick[ExploS](-1,1);
int numpt = Random[ExploS](8,12);
if ( bAMBUSH ) numpt *= 2;
for ( int i=0; i<numpt; i++ )
{
Vector3 pvel = (FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1)).unit()*FRandom[ExploS](.3,8);
let s = Spawn(bAMBUSH?"SWWMSmoke":"SWWMSmallSmoke",pos);
s.vel = pvel;
s.SetShade(Color(3,2,1)*Random[ExploS](64,85));
s.A_SetRenderStyle(s.alpha,STYLE_AddShaded);
s.scale *= 3.;
s.alpha *= bAMBUSH?.4:.2;
}
Spawn(bAMBUSH?"PoofLight2":"PoofLight",pos);
}
BLPF A 1 Bright A_FadeOut(.3);
Wait;
}
}
Class TeleLight : PaletteLight
{
Default
{
Tag "ImpactWav";
ReactionTime 10;
Args 0,0,0,150;
}
}
Class SWWMTeleportSparkle : Actor
{
Default
{
RenderStyle "Add";
Scale 0.3;
Radius 0.1;
Height 0;
+NOGRAVITY;
+NOBLOCKMAP;
+DONTSPLASH;
+ROLLSPRITE;
+ROLLCENTER;
+FORCEXYBILLBOARD;
+NOINTERACTION;
}
override void Tick()
{
if ( isFrozen() ) return;
A_SetScale(scale.x*specialf1);
A_FadeOut(specialf2);
}
States
{
Spawn:
BLPF C -1 Bright;
Stop;
}
}
Class SWWMTeleportFog : Actor
{
Default
{
RenderStyle "Add";
+NOGRAVITY;
+NOBLOCKMAP;
+DONTSPLASH;
+NOINTERACTION;
+FORCEXYBILLBOARD;
Radius .1;
Height 0.;
}
override void PostBeginPlay()
{
Super.PostBeginPlay();
SetOrigin(Vec3Offset(0,0,28),false);
A_StartSound("misc/teleport",CHAN_VOICE);
Spawn("TeleLight",pos);
if ( swwm_simplefog ) SetStateLabel("Simple");
}
States
{
Spawn:
TNT1 A 1
{
int numpt = int(Random[ExploS](6,12)*alpha);
for ( int i=0; i<numpt; i++ )
{
Vector3 pvel = (FRandom[ExploS](-1,1),FRandom[ExploS](-1,1),FRandom[ExploS](-1,1)).unit()*FRandom[ExploS](.3,8)*alpha;
let s = Spawn("SWWMSmallSmoke",pos);
s.vel = pvel;
s.SetShade(Color(1,2,3)*int(Random[ExploS](64,85)*alpha));
s.A_SetRenderStyle(s.alpha,STYLE_AddShaded);
s.scale *= 3.*alpha;
s.alpha *= alpha;
}
numpt = int(Random[ExploS](4,8));
for ( int i=0; i<numpt; i++ )
{
double ang = FRandom[ExploS](0,360);
double pt = FRandom[ExploS](-90,90);
double dist = (FRandom[ExploS](5,10)+60*(1.-alpha));
if ( LineTrace(ang,dist,pt,TRF_THRUACTORS|TRF_THRUHITSCAN) ) continue;
Vector3 ofs = (cos(ang)*cos(pt),sin(ang)*cos(pt),-sin(pt))*dist;
Vector3 spos = level.Vec3Offset(pos,ofs);
let s = Spawn("SWWMTeleportSparkle",spos);
s.scale *= FRandom[ExploS](.8,1.2);
s.specialf1 = FRandom[ExploS](.93,.97);
s.specialf2 = FRandom[ExploS](.02,.04);
s.roll = FRandom[ExploS](0,360);
}
A_FadeOut(.07);
}
Wait;
Simple:
SPEX ABCDEFGHIJKLMNOPQRSTUVWXYZ[\] 1 Bright;
Stop;
}
}
// Bullet trails from DT
Class WaterHit
{
Sector sect;
Vector3 hitpos;
}
Class InvisibleSplasher : Actor
{
Default
{
Mass 100;
VSpeed -2;
Radius 2;
Radius 2;
+NOBLOCKMAP; // needed to prevent infinite loops with some 3D floor water (yes, you read that right)
}
States
{
Spawn:
TNT1 A 2;
Stop;
}
}
Class SmolInvisibleSplasher : InvisibleSplasher
{
Default
{
Mass 10;
}
}
Class SWWMBulletTrail : LineTracer
{
Array<WaterHit> WaterHitList;
Array<Line> ShootThroughList;
Actor ignoreme;
static play void DoTrail( Actor target, Vector3 pos, Vector3 dir, int dist, int bubblechance, bool smoky = false )
{
let t = new("SWWMBulletTrail");
t.ignoreme = target;
t.WaterHitList.Clear();
t.ShootThroughList.Clear();
t.Trace(pos,level.PointInSector(pos.xy),dir,dist,0);
for ( int i=0; i<t.ShootThroughList.Size(); i++ )
{
t.ShootThroughList[i].Activate(target,0,SPAC_PCross);
if ( t.ShootThroughList[i].special == GlassBreak ) // fuck glass
t.ShootThroughList[i].Activate(target,0,SPAC_Impact);
}
for ( int i=0; i<t.WaterHitList.Size(); i++ )
{
let b = Actor.Spawn("InvisibleSplasher",t.WaterHitList[i].hitpos);
b.A_CheckTerrain();
}
for ( int i=5; i<t.Results.Distance; i+=10 )
{
if ( !Random[Boolet](0,bubblechance) ) continue;
let b = Actor.Spawn(smoky?"SWWMSmallSmoke":"SWWMBubble",level.Vec3Offset(pos,dir*i));
b.Scale *= FRandom[Boolet](.4,.6);
}
t.Destroy();
}
override ETraceStatus TraceCallback()
{
// liquid splashes
if ( Results.CrossedWater )
{
let hl = new("WaterHit");
hl.sect = Results.CrossedWater;
hl.hitpos = Results.CrossedWaterPos;
WaterHitList.Push(hl);
}
else if ( Results.Crossed3DWater )
{
let hl = new("WaterHit");
hl.sect = Results.Crossed3DWater;
hl.hitpos = Results.Crossed3DWaterPos;
WaterHitList.Push(hl);
}
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;
ShootThroughList.Push(Results.HitLine);
return TRACE_Skip;
}
return TRACE_Stop;
}
}
// Elastic recoil from DT (unused)
Enum ESwingMode
{
SWING_Straight, // constant increment
SWING_Spring, // bounces back after a delay
};
Class Swinger : Thinker
{
Actor target;
Vector2 dir;
double inc, rmul;
int steps, mode, delay;
double str, tstr;
int cnt, cstate;
Enum ESwingerState
{
STATE_Initial,
STATE_Wait,
STATE_Return,
};
override void Tick()
{
if ( !target ) cstate = -1;
switch ( cstate )
{
case STATE_Initial:
target.A_SetAngle(target.angle+dir.x*str,SPF_INTERPOLATE);
target.A_SetPitch(target.pitch+dir.y*str,SPF_INTERPOLATE);
str += inc;
if ( ++cnt >= steps )
{
cnt = 0;
str = tstr/steps;
cstate = (mode==SWING_Straight)?(-1):(delay>0)?STATE_Wait:STATE_Return;
}
else tstr += str;
break;
case STATE_Wait:
if ( ++cnt >= delay )
{
cnt = 0;
cstate = STATE_Return;
}
break;
case STATE_Return:
target.A_SetAngle(target.angle-dir.x*(str/rmul),SPF_INTERPOLATE);
target.A_SetPitch(target.pitch-dir.y*(str/rmul),SPF_INTERPOLATE);
if ( ++cnt >= steps*rmul )
{
cnt = 0;
cstate = -1;
}
break;
default:
Destroy();
return;
}
}
static void DoSwing( Actor target, Vector2 dir, double initial, double inc, int steps, int mode = 0, int delay = 0, double rmul = 1.0 )
{
let s = new("Swinger");
s.ChangeStatNum(Thinker.STAT_USER);
s.target = target;
s.dir = dir;
s.inc = inc;
s.rmul = rmul;
s.steps = steps;
s.mode = mode;
s.delay = delay;
s.cnt = 0;
s.cstate = 0;
s.str = initial;
s.tstr = initial;
}
}