version "4.10" 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 originalLinedefSpecials2; Array 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 WorldLoaded(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; Console.printf("JMP: World scan completed."); } } override void WorldLineActivated(WorldEvent e) { // TODO: Deplete batteries. //Console.printf("Line activated: %d %d\n", e.ActivatedLine.index(), e.ActivatedLine.special); for(int cableIndex = 0; cableIndex < deployedCables.size(); cableIndex++) { if(deployedCables[cableIndex].lineDefIndex == e.ActivatedLine.index()) { deployedCables[cableIndex].DecrementBattery(); break; } } } void RegisterCables(JumperCablesDeployed cableActor) { // Console.printf("REGISTERING %d\n", deployedCables.Find(cableActor)); if(deployedCables.Find(cableActor) == deployedCables.size()) { deployedCables.Push(cableActor); // FIXME: Remove this. // Console.printf("Cables registered %d\n", deployedCables.size()); } } void UnregisterCables(JumperCablesDeployed cableActor) { // Console.printf("UNREGISTERING %d\n", deployedCables.Find(cableActor)); int index = deployedCables.find(cableActor); if(index != deployedCables.size()) { deployedCables.delete(index); // FIXME: Remove this. // Console.printf("Cables unregistered %d\n", deployedCables.size()); } } } 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() { // Console.printf("IS THIS EVEN GETTING CALLED!?\n"); 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(); } } // TODO: Tick function (or whatever) where we set the sprite to indicate the charge level. // TODO: Despawn if the sector gets broken *again*? } 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) { // int cx,cy,cw,ch; // [cx,cy,cw,ch]=screen.GetClipRect(); // statusBar.SetClipRect(cx,cy,cw,ch); String filename = String.Format("graphics/jmpr_reticle%d.png", lastIndicatorStatus + 1); // console.printf("blep, %s %d\n", filename, frame); 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; select: JMPP A 0; goto super::select; 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. FLineTraceData linetraceData; DoLineTrace(HDPlayerPawn(invoker.owner), linetraceData); Vector3 hitPos; if(linetraceData.hitType != TRACE_HitNone) { hitPos = linetraceData.hitLocation - Vec3Offset(0, 0, 0); } A_Overlay(355, "ledd_left_indicator"); A_Overlay(356, "ledd_right_indicator"); SpawnParticleForLineTrace( HDPlayerPawn(invoker.owner), linetraceData); // console.printf("blep, %d\n", frame); //player.getpsprite(PSP_WEAPON).frame = randompick(0, 1, 2); // invoker.lastIndicatorStatus += 1; // invoker.lastIndicatorStatus %= 3; } goto readyend; readyend: #### # 0 A_ReadyEnd(); #### # 0 A_Jump(256,"ready"); deploy: 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); 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); JMPP A 1 { // invoker.owner.A_Log("Blep"); A_WeaponReady(WRF_NOFIRE | WRF_ALLOWRELOAD | WRF_ALLOWUSER4); AttachCables(HDPlayerPawn(invoker.owner)); } 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 SpawnParticleForLineTrace(HDPlayerPawn pawn, FLineTraceData linetraceData) { Line hitLine = linetraceData.hitLine; String particleColor = "darkred"; // if(linetraceData.hitType == TRACE_HitWall) { // JumperCablesMapLoader loader = JumperCablesMapLoader.Get(); // int originalSpecial = loader.originalLinedefSpecials2.Get(hitLine.index()); // int thisLineSpecial = hitLine.special; // if(originalSpecial != thisLineSpecial) { // particleColor = "green"; // } // } Line brokenLine = LineTraceFindbrokenLine(linetraceData); if(brokenLine) { particleColor = "green"; // Found somethine we can fix! 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; } } 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()); console.printf("Original special: %d", originalSpecial); frame_right = originalSpecial % 16; frame_left = originalSpecial / 16; } player.getpsprite(355).frame = frame_left; player.getpsprite(356).frame = frame_right; invoker.lastIndicatorStatus = player.getpsprite(PSP_WEAPON).frame; if(linetraceData.hitType != TRACE_HitNone) { Vector3 hitPos = linetraceData.hitLocation - Vec3Offset(0, 0, 0); // console.printf("hit pos: %f %f %f\n", hitPos.x, hitPos.y, hitPos.z); 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, // * 0.8, 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) { // Console.printf( // "Dist to line: %f\n", // GetDistanceToLine( // closestLineInRange, linetraceData.hitLocation)); return closestLineInRange; } } return null; } action void AttachCables(HDPlayerPawn pawn) { 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); Line brokenLine = LineTraceFindbrokenLine(linetraceData); if(brokenLine) { if(loader.originalLinedefSpecials2.CheckKey(brokenLine.index())) { // pawn.A_Log(string.Format("Blep3 %d", // loader.originalLinedefSpecials2.Get(hitLine.index()))); int originalSpecial = loader.originalLinedefSpecials2.Get(brokenLine.index()); int currentSpecial = brokenLine.special; if(originalSpecial != currentSpecial) { // Remove battery from inventory. int batteryCharges = batteryAmmo.TakeMag(true); // Console.printf("batteryCharges: %d", batteryCharges); if(batteryCharges < 1) { // Handle getting an empty battery. Just give it back. invoker.owner.A_Log("No charged batteries available to use."); batteryAmmo.AddAMag(batteryCharges); } else { JumperCablesDeployed deployed = JumperCablesDeployed( Spawn( "JumperCablesDeployed", linetraceData.hitLocation - linetraceData.hitDir * 4)); deployed.lineDefIndex = brokenLine.index(); deployed.bNoGravity = true; deployed.charges = batteryCharges; 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); } 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"; } }