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; static JumperCablesMapLoader Get() { JumperCablesMapLoader loader; loader = JumperCablesMapLoader(EventHandler.Find("JumperCablesMapLoader")); return loader; } override void WorldLoaded(WorldEvent e) { JumperCablesMapLoader loader = Get(); // Clear out anything from the last level. deployedCables.Clear(); loader.originalLinedefSpecials2.Clear(); // 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); } } } 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) { 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() { 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(-1.0, 1.0), frandom(-1.0, 1.0), frandom(-1.0, 1.0), // vel... frandom(-1.0, 1.0), frandom(-1.0, 1.0), frandom(-1.0, 1.0)); } 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 { default { +weapon.wimpy_weapon; +inventory.invbar; +hdweapon.droptranslation; +hdweapon.fitsinbackpack; hdweapon.barrelsize 0,0,0; weapon.selectionorder 1014; scale 0.6; inventory.icon "DERPEX"; inventory.pickupmessage "Picked up a wiring bypass kit."; inventory.pickupsound "derp/crawl"; translation 0; tag "Wiring Bypass Kit"; hdweapon.refid HDLD_KIRI_JUMPERCABLES; } override bool AddSpareWeapon(actor newowner) { return AddSpareWeaponRegular(newowner); } override hdweapon GetSpareWeapon(actor newowner,bool reverse,bool doselect) { return GetSpareWeaponRegular(newowner,reverse,doselect); } states { spawn: DERP A -1; stop; select: TNT1 A 0; goto super::select; ready: TNT1 A 1 { if(PressingFire()) { SetWeaponState("deploy"); } A_WeaponReady(WRF_NOFIRE | WRF_ALLOWRELOAD | WRF_ALLOWUSER4); FLineTraceData linetraceData; DoLineTrace(HDPlayerPawn(invoker.owner), linetraceData); if(linetraceData.hitType != TRACE_HitNone) { Vector3 hitPos = linetraceData.hitLocation - Vec3Offset(0, 0, 0); SpawnParticleForLineTrace( HDPlayerPawn(invoker.owner), linetraceData); } } goto readyend; deploy: TNT1 AA 1; TNT1 AAAA 1; TNT1 AAAA 1; TNT1 A 0 A_JumpIf(!pressingfire(),"ready"); TNT1 A 4 A_StartSound("weapons/pismagclick",CHAN_WEAPON); TNT1 A 2 A_StartSound("derp/crawl",CHAN_WEAPON,CHANF_OVERLAP); TNT1 A 1 { invoker.owner.A_Log("Blep"); A_WeaponReady(WRF_NOFIRE | WRF_ALLOWRELOAD | WRF_ALLOWUSER4); AttachCables(HDPlayerPawn(invoker.owner)); } 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"; // } // } if(LineTraceFindbrokenLine(linetraceData)) { particleColor = "green"; } 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, offsetz : pawn.height * 0.8, 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. if(secLine.special == 0) { // If this *used* to be special, we DO care about it! if(loader.originalLinedefSpecials2.CheckKey(secLine.index())) { // See if thise 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); 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; } } } } }