HDSnekTechDiscountPartyPack/zscript/jumpercables.zs
2023-09-03 16:33:10 -07:00

672 lines
19 KiB
Plaintext

// ----------------------------------------------------------------------
// Wiring Bypass Kit
// ----------------------------------------------------------------------
//
// This is just a pile of electronics maintenance equipment. The usual
// stuff is here, like electrical tape, wire cutters, etc.
//
// There's also a meter that looks like it was made out of some beat
// up piece of equipment, and held together with duct tape.
//
const HDLD_KIRI_JUMPERCABLES = "jmp";
const jumperCablesRaycastRange = 48; // Same range as the DERP.
const jumperCablesFixRange = 48; // Maximum distance to a broken linedef to fix, from the raycast hit.
class JumperCablesMapLoader : EventHandler
{
Map<int, int> originalLinedefSpecials2;
Array<JumperCablesDeployed> deployedCables;
bool worldScanCompleted;
static JumperCablesMapLoader Get()
{
JumperCablesMapLoader loader;
loader = JumperCablesMapLoader(EventHandler.Find("JumperCablesMapLoader"));
return loader;
}
override void WorldUnloaded(WorldEvent e)
{
JumperCablesMapLoader loader = Get();
// Clear out anything from the last level.
deployedCables.Clear();
loader.originalLinedefSpecials2.Clear();
worldScanCompleted = false;
}
override void WorldTick()
{
JumperCablesMapLoader loader = Get();
if(!loader.worldScanCompleted) {
// Go through all the linedefs and make note of what they're
// supposed to do.
for(int k = 0; k < level.Lines.size(); k++) {
Line line = level.Lines[k];
if(line.special != 0) {
loader.originalLinedefSpecials2.Insert(line.index(), line.special);
}
}
loader.worldScanCompleted = true;
}
}
override void WorldLineActivated(WorldEvent e)
{
// Is this line affected by a battery?
for(int cableIndex = 0; cableIndex < deployedCables.size(); cableIndex++) {
if(deployedCables[cableIndex].lineDefIndex == e.ActivatedLine.index()) {
// Yes. Deplete the battery.
deployedCables[cableIndex].DecrementBattery();
break;
}
}
}
void RegisterCables(JumperCablesDeployed cableActor)
{
if(deployedCables.Find(cableActor) == deployedCables.size()) {
deployedCables.Push(cableActor);
}
}
void UnregisterCables(JumperCablesDeployed cableActor)
{
int index = deployedCables.find(cableActor);
if(index != deployedCables.size()) {
deployedCables.delete(index);
}
}
}
class JumperCablesDeployed : HDUPK
{
int lineDefIndex;
int charges;
default
{
radius 2;
height 4;
+SpriteAngle;
Scale 0.4;
}
states
{
spawn:
JMPR CABD -1;
stop;
}
override void PostBeginPlay()
{
bNoGravity = true;
// Register us with the global event handler so we can
// respond to the linedef getting used.
JumperCablesMapLoader loader = JumperCablesMapLoader.Get();
loader.RegisterCables(self);
UpdateSprite();
}
void SetWallSprite(Vector2 lineDir)
{
bFlatSprite = false;
bWallSprite = true;
A_SetAngle(atan2(lineDir) + 90);
}
void SetFlatSprite(float angle)
{
bFlatSprite = true;
bWallSprite = false;
A_SetAngle(angle);
}
void UpdateSprite()
{
// Charge levels correspond to battery sprite charge levels.
if(charges > 13) {
frame = 0;
} else if(charges > 6) {
frame = 1;
} else if(charges > 0) {
frame = 2;
} else {
frame = 3;
}
}
void DecrementBattery()
{
charges -= 1;
// Console.printf("battery used, now: %d", charges);
if(charges <= 0) {
charges = 0;
DespawnToBattery();
}
UpdateSprite();
}
void DespawnToBattery()
{
// Re-break the linedef.
level.Lines[lineDefIndex].special = 0;
// Spawn a battery, with the same level of charge.
HDMagAmmo.SpawnMag(self, "HDBattery", self.charges);
// Spawn some smoke.
int smokeCount = random(1, 4);
float smokeVel = 20.0;
for(int k = 0; k < smokeCount; k++) {
A_SpawnItemEx(
"HDSmokeChunk",
0.0, 0.0, 0.0,
frandom(-smokeVel, smokeVel),
frandom(-smokeVel, smokeVel),
frandom(-smokeVel, smokeVel));
}
// Spawn a bunch of sparks.
int sparkAmount = random(3,6);
for(int k = 0; k < sparkAmount; k++) {
SpawnSpark();
}
JumperCablesMapLoader loader = JumperCablesMapLoader.Get();
loader.UnregisterCables(self);
Destroy();
}
override bool OnGrab(Actor grabber)
{
DespawnToBattery();
return false;
}
void SpawnSpark()
{
float sparkSpeed = 5.0;
float sparkOffset = 2.0;
A_StartSound("misc/arccrackle", CHAN_AUTO);
A_SpawnParticle(
"white",
SPF_RELATIVE | SPF_FULLBRIGHT,
random(1, 10), // lifetime
frandom(1, 7), // size
0, // angle
// offset...
frandom(-sparkOffset, sparkOffset),
frandom(-sparkOffset, sparkOffset),
frandom(-sparkOffset, sparkOffset),
// vel...
frandom(-sparkSpeed, sparkSpeed),
frandom(-sparkSpeed, sparkSpeed),
frandom(-sparkSpeed, sparkSpeed));
}
override void Tick()
{
if(frandom(0.0, 1.0) < 0.02) {
SpawnSpark();
}
// Despawn to battery if the sector gets re-destroyed.
if(level.lines[lineDefIndex].special == 0)
{
DespawnToBattery();
}
}
}
class JumperCablesUsable : HDWeapon
{
int lastIndicatorStatus;
default
{
+weapon.wimpy_weapon;
+inventory.invbar;
+hdweapon.droptranslation;
+hdweapon.fitsinbackpack;
hdweapon.barrelsize 0,0,0;
weapon.selectionorder 1014;
scale 0.6;
inventory.icon "JMPKA0";
inventory.pickupmessage "Picked up a wiring bypass kit.";
inventory.pickupsound "derp/crawl";
translation 0;
tag "Wiring Bypass Kit";
hdweapon.refid HDLD_KIRI_JUMPERCABLES;
}
override double WeaponBulk()
{
return 7;
}
override bool AddSpareWeapon(actor newowner)
{
return AddSpareWeaponRegular(newowner);
}
override hdweapon GetSpareWeapon(actor newowner,bool reverse,bool doselect)
{
return GetSpareWeaponRegular(newowner,reverse,doselect);
}
override void DrawSightPicture(
HDStatusBar statusBar,
HDWeapon weapon,
HDPlayerPawn playerPawn,
bool sightbob,
Vector2 bob,
double fov,
bool scopeview,
Actor hpc,
String whichdot)
{
// Switch reticle colors depending on the LED status.
String filename = String.Format(
"graphics/jumpercables/jmpr_reticle%d.png",
lastIndicatorStatus + 1);
statusBar.drawImage(
filename,
(0, 0) + bob,
statusBar.DI_SCREEN_CENTER | statusBar.DI_ITEM_TOP,
scale : (0.5, 0.5));
}
states
{
spawn:
JMPP # 0 offset(0, -32);
JMPK A -1;
stop;
select0:
JMPP A 1;
goto select0big;
deselect0:
JMPP A 1;
goto deselect0big;
// LCD overlay states.
ledd_left_indicator:
JMIL A 1 offset(0, -32); wait;
ledd_right_indicator:
JMIR A 1 offset(0, -32); wait;
ready:
JMPP # 1 {
if(PressingFire()) {
SetWeaponState("deploy");
}
A_WeaponReady(WRF_NOFIRE | WRF_ALLOWUSER3); // USER3 = MagManager.
// Line trace to see what we might be inspecting.
FLineTraceData linetraceData;
DoLineTrace(HDPlayerPawn(invoker.owner), linetraceData);
// Overlay setup.
A_Overlay(355, "ledd_left_indicator");
A_Overlay(356, "ledd_right_indicator");
// Update displays.
UpdateVisualsForLineTrace(
HDPlayerPawn(invoker.owner),
linetraceData);
}
goto readyend;
readyend:
#### # 0 A_ReadyEnd();
#### # 0 A_Jump(256,"ready");
deploy:
// Lower it down...
JMPP A 1 offset(0, 16);
JMPP A 1 offset(0, 32);
JMPP A 1 offset(0, 48);
JMPP A 1 offset(0, 64);
JMPP A 1 offset(0, 72);
JMPP A 1 offset(0, 96);
JMPP A 1 offset(0, 128);
// Play some sounds.
JMPP A 0 A_JumpIf(!pressingfire(),"ready");
JMPP A 4 A_StartSound("weapons/pismagclick",CHAN_WEAPON);
JMPP A 2 A_StartSound("derp/crawl",CHAN_WEAPON,CHANF_OVERLAP);
// Actual cable attachment happens here.
JMPP A 1 {
AttachCables(HDPlayerPawn(invoker.owner));
}
// Raise it back up...
JMPP A 1 offset(0, 128);
JMPP A 1 offset(0, 96);
JMPP A 1 offset(0, 72);
JMPP A 1 offset(0, 64);
JMPP A 1 offset(0, 48);
JMPP A 1 offset(0, 32);
JMPP A 1 offset(0, 16);
JMPP A 1 offset(0, 0);
JMPP A 1 {
A_WeaponReady(WRF_NOFIRE | WRF_ALLOWRELOAD | WRF_ALLOWUSER4);
}
goto ready;
user3:
#### # 0 A_MagManager("HDBattery");
goto ready;
}
action void UpdateVisualsForLineTrace(HDPlayerPawn pawn, FLineTraceData linetraceData)
{
Line hitLine = linetraceData.hitLine;
String particleColor = "darkred";
Line brokenLine = LineTraceFindbrokenLine(linetraceData);
// Set main frame (LEDs) based on raytrace status.
if(brokenLine) {
// Found somethine we can fix!
particleColor = "green";
player.getpsprite(PSP_WEAPON).frame = 1;
} else {
if(linetraceData.hitType == TRACE_HitNone) {
// Didn't hit anything (yellow light).
player.getpsprite(PSP_WEAPON).frame = 0;
} else{
// Hit something, but it's not broken (green light).
player.getpsprite(PSP_WEAPON).frame = 2;
}
}
// Set display overlay frames.
// Default to random numbers.
int frame_left = random(0, 15);
int frame_right = random(0, 15);
Line lineForDisplay = brokenLine;
if(!lineForDisplay) {
lineForDisplay = hitLine;
}
if(lineForDisplay) {
JumperCablesMapLoader loader = JumperCablesMapLoader.Get();
int originalSpecial = loader.originalLinedefSpecials2.Get(lineForDisplay.index());
frame_right = originalSpecial % 16;
frame_left = originalSpecial / 16;
}
player.getpsprite(355).frame = frame_left;
player.getpsprite(356).frame = frame_right;
// Play a sound if we just discovered a broken line.
if(invoker.lastIndicatorStatus != player.getpsprite(PSP_WEAPON).frame) {
if(invoker.lastIndicatorStatus != 1 && player.getpsprite(PSP_WEAPON).frame == 1) {
A_StartSound("herp/beep", CHAN_WEAPON);
}
}
// Set indicator status fo the HUD.
invoker.lastIndicatorStatus = player.getpsprite(PSP_WEAPON).frame;
// Debug particle display for the linetrace.
if(hd_debug > 1) {
if(linetraceData.hitType != TRACE_HitNone) {
Vector3 hitPos = linetraceData.hitLocation - Vec3Offset(0, 0, 0);
A_SpawnParticle(
particleColor,
0, 10, 2, 0,
hitPos.x,
hitPos.y,
hitPos.z);
}
}
}
action float Vec2Mag(Vector2 v)
{
return sqrt(v.x * v.x + v.y * v.y);
}
action float GetDistanceToLine(Line testLine, Vector3 position)
{
Vector2 pos2d;
pos2d.x = position.x;
pos2d.y = position.y;
Vector2 origin = testLine.v1.p;
// Get the normalized direction from v1 to v2.
Vector2 direction = testLine.v2.p - origin;
float direction_magnitude = Vec2Mag(direction);
direction = direction / direction_magnitude;
// Get the position relative to the origin.
pos2d -= origin;
// Project pos2d onto direction, to get a position along the line.
double t = direction dot pos2d;
// Clamp position along line to start/end.
if(t < 0.0) {
t = 0;
}
if(t > direction_magnitude) {
t = direction_magnitude;
}
// Closest point on the line (as an offset from origin).
Vector2 position_on_line = direction * t;
Vector2 delta_to_line = position_on_line - pos2d;
float final_distance = Vec2Mag(delta_to_line);
return final_distance;
}
action void DoLineTrace(HDPlayerPawn pawn, FLineTraceData linetraceData)
{
pawn.LineTrace(
pawn.angle,
48,
pawn.pitch,
flags : TRF_THRUACTORS | TRF_ABSOFFSET,
offsetz : pawn.gunPos.z - 1,
offsetforward : pawn.gunPos.x,
offsetside : pawn.gunPos.y,
data : linetraceData);
}
action Line LineTraceFindbrokenLine(FLineTraceData linetraceData)
{
JumperCablesMapLoader loader = JumperCablesMapLoader.Get();
// If we're placing this on a wall, then we'll just look at the wall's special.
if(linetraceData.hitType == TRACE_HitWall) {
if(linetraceData.hitLine) {
Line hitLine = linetraceData.hitLine;
if(loader.originalLinedefSpecials2.CheckKey(hitLine.index())) {
int originalSpecial = loader.originalLinedefSpecials2.Get(hitLine.index());
int currentSpecial = hitLine.special;
if(originalSpecial != currentSpecial)
{
return hitLine;
}
}
}
}
// If we hit a sector *or* got close enough to another linedef
// attached to the sector that we're in, then let's find the
// closest linedef within range that we know is damaged.
if(linetraceData.hitType == TRACE_HitFloor ||
linetraceData.hitType == TRACE_HitCeiling ||
linetraceData.hitType == TRACE_HitWall)
{
Sector sec = linetraceData.hitSector;
Line closestLineInRange = null;
float closestLineDistance = jumperCablesFixRange;
// Iterate through all linedefs connected to the sector.
for(int secLineIndex = 0; secLineIndex < sec.lines.size(); secLineIndex++) {
Line secLine = sec.lines[secLineIndex];
// If this is currently special, we don't care about
// it (it's working fine, presumably).
if(secLine.special == 0) {
// If this *used* to be special, we DO care about it!
if(loader.originalLinedefSpecials2.CheckKey(secLine.index())) {
// Make sure it's actually changed.
int originalSpecial = loader.originalLinedefSpecials2.Get(secLine.index());
int currentSpecial = secLine.special;
if(originalSpecial != currentSpecial) {
// See if this is the closest one we've found so far.
float distanceToLine = GetDistanceToLine(
secLine, linetraceData.hitLocation);
if(distanceToLine <= closestLineDistance) {
closestLineInRange = secLine;
closestLineDistance = distanceToLine;
}
}
}
}
}
// If we found something broken, then return the closest
// thing we found.
if(closestLineInRange) {
return closestLineInRange;
}
}
return null;
}
action void AttachCables(HDPlayerPawn pawn)
{
// Do we have enough ammo?
HDMagAmmo batteryAmmo = HDMagAmmo(FindInventory("HDBattery"));
if(!batteryAmmo || batteryAmmo.amount < 1) {
invoker.owner.A_Log("You need a battery to use this.");
return;
}
JumperCablesMapLoader loader = JumperCablesMapLoader.Get();
FLineTraceData linetraceData;
DoLineTrace(pawn, linetraceData);
// Are we even close enough to a line?
Line brokenLine = LineTraceFindbrokenLine(linetraceData);
if(!brokenLine) {
invoker.owner.A_Log("Not close enough to repair.");
return;
}
if(!loader.originalLinedefSpecials2.CheckKey(brokenLine.index())) {
invoker.owner.A_Log("No mechanism present to repair.");
return;
}
// Check to see if it's something that actually needs fixing.
int originalSpecial = loader.originalLinedefSpecials2.Get(brokenLine.index());
int currentSpecial = brokenLine.special;
if(originalSpecial == currentSpecial) {
invoker.owner.A_Log("This is already functioning as normal.");
return;
}
// Remove battery from inventory, but make sure we're actually
// getting a charged one.
int batteryCharges = batteryAmmo.TakeMag(true);
if(batteryCharges < 1) {
invoker.owner.A_Log("No charged batteries available to use.");
batteryAmmo.AddAMag(batteryCharges); // Just give it back.
return;
}
// Actually spawn the deployed item.
JumperCablesDeployed deployed = JumperCablesDeployed(
Spawn(
"JumperCablesDeployed",
linetraceData.hitLocation - linetraceData.hitDir * 4));
deployed.lineDefIndex = brokenLine.index();
deployed.bNoGravity = true;
deployed.charges = batteryCharges;
// Use a different kind of flat sprite and angle depending on
// if we're attaching to wall or floor/ceiling.
if(linetraceData.hitType == TRACE_HitFloor || linetraceData.hitType == TRACE_HitCeiling) {
deployed.SetFlatSprite(invoker.owner.angle);
} else {
Vector2 lineDir = linetraceData.hitLine.v2.p - linetraceData.hitLine.v1.p;
deployed.SetWallSprite(lineDir);
}
// Restore the damn line special already.
brokenLine.special = originalSpecial;
}
override void DrawHUDStuff(HDStatusBar statusBar, HDWeapon weapon, HDPlayerPawn pawn)
{
// Copied and modified from ThunderBuster display.
if(statusBar.hudlevel == 1) {
statusBar.DrawBattery(
-54, -4,
statusBar.DI_SCREEN_CENTER_BOTTOM,
reloadorder : true);
statusBar.DrawNum(
pawn.CountInv("HDBattery"),
-46, -8,
statusBar.DI_SCREEN_CENTER_BOTTOM);
}
}
override String GetHelpText()
{
return WEPHELP_FIRE.." Deploy\n";
}
}