phkb wrote: ↑Wed Jul 01, 2020 10:24 pm
The trouble is, in all my testing, the shipDied
event is too late. I tried the code you suggested, launched from the station with an escape pod, and then deliberately ran into the nav buoy. Game over. All the debug events show up in the log, but nothing happened. The abandonShip
call fails to eject the player to safety.
The log shows:
19:03:03.813 [ShipEntity.abandonShip.notPossible] -[ShipEntity abandonShip]: Ship <PlayerEntity 0x3cc2b20>{"Cobra Mark IV" position: (13431.1, -82547.2, 356805) scanClass: CLASS_PLAYER status: STATUS_DEAD} cannot be abandoned at this time.
Here's why shipTakingDamage works and shipDied doesn't:
First, when shipTakingDamage is called, the status is not STATUS_DEAD, so abandonShip works. When shipDied is called, the status is STATUS_DEAD, so abandonShip doesn't work (because launchEscapeCapsule from PlayerEntity.m returns nil).
Second, launchEscapeCapsule in PlayerEntity.m sets energy = 25 (see line 6562). So calling abandonShip, which calls launchEscapeCapsule, prevents the ship from being destroyed (instead, the shipEnergyIsLow handler is called). PlayerEntity.m's version of launchEscapeCapsule also sets the status to STATUS_ESCAPE_SEQUENCE, which disables collisions (line 2441 of PlayerEntity.m), disables taking energy damage (line 6222 of PlayerEntity.m) and disables heat damage (line 2872 of PlayerEntity.m). So it would be difficult to kill yourself at that point before the escape sequence finishes and docks you.
Third, becomeExplosion or becomeLargeExplosion (which happens after the shipDied event) sets STATUS_DEAD, which interrupts STATUS_ESCAPE_SEQUENCE.
Fourth, getDestroyedBy in PlayerEntity.m calls playGameOver (the sound effects).
Fifth, getDestroyedBy in PlayerEntity.m calls showGameOver. (The fourth and fifth are cosmetic; STATUS_DEAD is what tells PlayerEntityControls.m to watch for spacebar down and trigger a restart, although after 30 seconds it happens automatically [the actual restart occurs in PlayerEntity.m's performDeadUpdates method].)
Sixth, as a consequence of the player ship avoiding destruction when using shipTakingDamage, a removeAllEquipment call at line 6916 in getDestroyedBy is avoided, so post-docking, player.ship.equipment remains intact. This is an idiosyncracy of the core game too (without Ship Repurchase) -- if you buy an escape pod and hit ESC-ESC to eject (whether or not your ship is destroyed just after), when you arrive at the dock you will have your fully equipped ship (although if it had equipment damage, that will be retained), other than the escape pod equipment itself (which is removed when you eject). Cargo does get removed, though (line 6560).
That removeAllEquipment poses a problem for your script, which relies on player.ship.equipment being the full list (unless it was interstellar).
Considering how escape pods are handled currently by the game, I think it's valid to allow OXPs to call abandonShip in the shipDied handler to have an escape pod launch happen "just in time" and from that point onwards basically follow the same logic as before, i.e., don't actually destroy the ship, and don't remove the equipment, so it will be restored (minus cargo) when you arrive at the station. If the behavior of escape pods in general is changed to be more realistic (not giving you all the equipment if your ship actually gets vaporized after you launch in the pod), that change then could be extended to the shipDied variation, and you would need to change your script to use the interstellar solution for the general case.
Here are some changes (in diff form) that allow the shipDied method to call abandonShip and disable the problematic calls noted above:
Code: Select all
diff --git a/src/Core/Entities/PlayerEntity.m b/src/Core/Entities/PlayerEntity.m
index f6727c44..7eda0a50 100644
--- a/src/Core/Entities/PlayerEntity.m
+++ b/src/Core/Entities/PlayerEntity.m
@@ -6890,22 +6890,40 @@ NSComparisonResult marketSorterByMassUnit(id a, id b, void *market);
// Let scripts know the player died.
[self noteKilledBy:whom damageType:type]; // called before exploding, consistant with npc ships.
-
- [self becomeLargeExplosion:4.0]; // also sets STATUS_DEAD
- [self moveForward:100.0];
-
- flightSpeed = 160.0f;
- velocity = kZeroVector;
- flightRoll = 0.0;
- flightPitch = 0.0;
- flightYaw = 0.0;
- [[UNIVERSE messageGUI] clear]; // No messages for the dead.
+
+ if ([roleSet hasRole:@"abandoning_ship"])
+ {
+ [self abandonShip]; // if OXPs call abandonShip from shipDied worldscript event handlers, we queue the action by adding a role, wait for them all to finish, then call it once here
+ }
+ else // only explode and suppress messages if we didn't escape in time; becomeLargeExplosion would override the STATUS_ESCAPE_SEQUENCE set in launchEscapeCapsule (called by abandonShip)
+ {
+ [self becomeLargeExplosion:4.0]; // also sets STATUS_DEAD
+ [self moveForward:100.0];
+
+ flightSpeed = 160.0f;
+ velocity = kZeroVector;
+ flightRoll = 0.0;
+ flightPitch = 0.0;
+ flightYaw = 0.0;
+
+ [[UNIVERSE messageGUI] clear]; // No messages for the dead.
+ }
+
[self suppressTargetLost]; // No target lost messages when dead.
- [self playGameOver];
- [UNIVERSE setBlockJSPlayerShipProps:YES]; // Treat JS player as stale entity.
- [self removeAllEquipment]; // No scooping / equipment damage when dead.
+
+ if (![roleSet hasRole:@"abandoning_ship"]) // only play game over sounds and remove equipment if we failed to launch an escape pod in time; setBlockJSPlayerShipProps:YES is redundant (launchEscapeCapsule sets it too)
+ {
+ [self playGameOver];
+ [UNIVERSE setBlockJSPlayerShipProps:YES]; // Treat JS player as stale entity.
+ [self removeAllEquipment]; // No scooping / equipment damage when dead.
+ }
+
[self loseTargetStatus];
- [self showGameOver];
+
+ if (![roleSet hasRole:@"abandoning_ship"]) // only call showGameOver if the game actually is over
+ {
+ [self showGameOver];
+ }
}
diff --git a/src/Core/Entities/ShipEntity.m b/src/Core/Entities/ShipEntity.m
index 5ba69113..2f589486 100644
--- a/src/Core/Entities/ShipEntity.m
+++ b/src/Core/Entities/ShipEntity.m
@@ -9019,6 +9019,7 @@ NSComparisonResult ComparePlanetsBySurfaceDistance(id i1, id i2, void* context)
{
[self noteKilledBy:whom damageType:type];
[self abortDocking];
+ if ([roleSet hasRole:@"abandoning_ship"]) [self abandonShip]; // if OXPs call abandonShip from shipDied event handlers, wait for them all to finish, then call it again once here
[self becomeExplosion];
}
@@ -13349,6 +13350,11 @@ Vector positionOffsetForShipInRotationToAlignment(ShipEntity* ship, Quaternion q
}
}
+ else if ([self status] == STATUS_DEAD)
+ {
+ // probably an OXP calling abandonShip from a shipDied event handler - add a role to signal getDestroyedBy method to call abandonShip again after all shipDied event handlers are finished
+ [self addRole:@"abandoning_ship"];
+ }
else
{
// this shouldn't happen any more!
The method I picked (using addRole and hasRole to signal that abandonShip needs to be called later) might not be the best, but it avoided needing to add new properties and there is some precedent with addRole and hasRole for other things (e.g., asteroids and boulders).
I also noticed (as a purely aesthetic issue) that there is something not right with how the escape pod ejection sequence looks
visually when Ship Repurchase initiates the abandonShip call from shipDied (after ship energy hits zero) in a
collision specifically. Instead of launching backwards and up and viewing your ship, the ship disappears and the camera continues forwards. Hitting esc-esc with or without Ship Repurchase gives the usual launch up and backwards and shows the ship flying away from you, and if Ship Repurchase auto-ejects due to
energy damage, it also shows the launch up and backwards. I'm not sure why it doesn't happen for scrape/collision damage. I haven't tried heat damage either.
And, unfortunately, the "time until rescue" seems to be hard-coded (line 6470 in PlayerEntity.m), and clock.addSeconds doesn't accept negative values. I'd like to change that line to read the values from somewhere that an OXP could override.