From 258ec184ab3436ffdfd092426ac49ca5b484958d Mon Sep 17 00:00:00 2001 From: Petr Ohlidal Date: Sun, 31 Mar 2024 23:33:23 +0200 Subject: [PATCH] :bug: Fixed paused physics resuming after switching vehicle. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The problem was introduced by myself in 9431e1b508575c40498ed9becd8a0385a3b95463 - I cleaned up duplicate syncing code but oversimplified the logic to reset unlinked actors. As result, the physics paused state and skeletonview state could only be active on player's actor and connected actor, not on any free standing one. This is quite a significant remake of actor-linking code; a direct follow-up to d36c4ecdd72d751f934b3a76b256578563bfb9d6 which introduced ✉️ MSG_SIM_ACTOR_LINKING_REQUESTED. A remake was needed because there was no single spot to correctly reset the skeleton/physicspause on actor unlink - there was simply no concept of "actors were just linked/unlinked". While researching how to add it, I realized scripts may want to know when that happens, so I added a script event `SE_GENERIC_TRUCK_LINKING_CHANGED` for it. Keep in mind there can be multiple interlinking beams at the same time, so not every added/removed beam means linking changes. The new event also helps navigate the codebase and documents how stuff works. Note that only code changed; all data remained the same - code handling hooks/ties/ropes/slidenodes got only cosmetic edits. Code changes: * ScriptEvents.h - added SE_GENERIC_TRUCK_LINKING_CHANGED ~ args: #1 action (1=linked, 0=unlinked), #2 link type `ActorLinkingRequestType` (will be INVALID on savegame load or forced unlink when deleting actor) #3 master ActorInstanceID_t, #4 slave ActorInstanceID_t * Actor.h/cpp - Removed functions `Add/RemoveInterActorbeam()` - using new ones in ActorManager. * ActorManager.h/cpp - Added newly implemented functions `Add/RemoveInterActorBeam()`. --- doc/angelscript/Script2Game/globals.h | 4 +- source/main/gameplay/ScriptEvents.h | 2 + source/main/physics/Actor.cpp | 96 +++----- source/main/physics/Actor.h | 7 +- source/main/physics/ActorManager.cpp | 211 +++++++++++++++--- source/main/physics/ActorManager.h | 6 +- source/main/physics/Savegame.cpp | 2 +- .../bindings/ScriptEventsAngelscript.cpp | 2 + 8 files changed, 228 insertions(+), 102 deletions(-) diff --git a/doc/angelscript/Script2Game/globals.h b/doc/angelscript/Script2Game/globals.h index 37ba26aa55..61716035ff 100644 --- a/doc/angelscript/Script2Game/globals.h +++ b/doc/angelscript/Script2Game/globals.h @@ -75,7 +75,9 @@ void print(const string message); SE_GENERIC_MESSAGEBOX_CLICK //!< triggered when the user clicks on a message box button, the argument refers to the button pressed SE_GENERIC_EXCEPTION_CAUGHT //!< Triggered when C++ exception (usually Ogre::Exception) is thrown; #1 ScriptUnitID, #5 originFuncName, #6 type, #7 message. - SE_GENERIC_MODCACHE_ACTIVITY //!< Triggered when status of modcache changes, args: #1 type, #2 entry number, for other args see `RoR::modCacheActivityType` + SE_GENERIC_MODCACHE_ACTIVITY //!< Triggered when status of modcache changes, args: #1 type, #2 entry number, for other args see `RoR::modCacheActivityType` + + SE_GENERIC_TRUCK_LINKING_CHANGED //!< Triggered when 2 actors become linked or unlinked via ties/hooks/ropes/slidenodes; args: #1 action (1=linked, 0=unlinked), #2 link type `ActorLinkingRequestType` (will be INVALID on savegame load or forced unlink when deleting actor) #3 master ActorInstanceID_t, #4 slave ActorInstanceID_t SE_ALL_EVENTS = 0xffffffff, SE_NO_EVENTS = 0 diff --git a/source/main/gameplay/ScriptEvents.h b/source/main/gameplay/ScriptEvents.h index f454673e31..3ead62f508 100644 --- a/source/main/gameplay/ScriptEvents.h +++ b/source/main/gameplay/ScriptEvents.h @@ -61,6 +61,8 @@ enum scriptEvents SE_GENERIC_EXCEPTION_CAUGHT = BITMASK(24), //!< Triggered when C++ exception (usually Ogre::Exception) is thrown; #1 ScriptUnitID, #5 originFuncName, #6 type, #7 message. SE_GENERIC_MODCACHE_ACTIVITY = BITMASK(25), //!< Triggered when status of modcache changes, args: #1 type, #2 entry number, for other args see `RoR::modCacheActivityType` + SE_GENERIC_TRUCK_LINKING_CHANGED = BITMASK(26), //!< Triggered when 2 actors become linked or unlinked via ties/hooks/ropes/slidenodes; args: #1 action (1=linked, 0=unlinked), #2 link type `ActorLinkingRequestType` (will be INVALID on savegame load or forced unlink when deleting actor) #3 master ActorInstanceID_t, #4 slave ActorInstanceID_t + SE_ALL_EVENTS = 0xffffffff, SE_NO_EVENTS = 0 diff --git a/source/main/physics/Actor.cpp b/source/main/physics/Actor.cpp index 35ba39ec60..df272b216f 100644 --- a/source/main/physics/Actor.cpp +++ b/source/main/physics/Actor.cpp @@ -87,9 +87,12 @@ Actor::~Actor() void Actor::dispose() { + // Handler for `MSG_SIM_DELETE_ACTOR_REQUESTED` message - should not be invoked otherwise. + // -------------------------------------------------------------------------------------- + ROR_ASSERT(ar_state != ActorState::DISPOSED); - this->DisjoinInterActorBeams(); + this->DisjoinInterActorBeams(); // OK to be invoked here - processing `MSG_SIM_DELETE_ACTOR_REQUESTED`. ar_hooks.clear(); ar_ties.clear(); ar_node_to_beam_connections.clear(); @@ -1594,7 +1597,7 @@ void Actor::SyncReset(bool reset_position) this->applyNodeBeamScales(); - this->DisjoinInterActorBeams(); + // Remove inter-actor beams (links from this actor to other): for (auto& h : ar_hooks) { @@ -1605,7 +1608,7 @@ void Actor::SyncReset(bool reset_position) h.hk_beam->bm_disabled = true; h.hk_beam->bm_inter_actor = false; h.hk_beam->L = (ar_nodes[0].AbsPosition - h.hk_hook_node->AbsPosition).length(); - this->RemoveInterActorBeam(h.hk_beam); + App::GetGameContext()->GetActorManager()->RemoveInterActorBeam(h.hk_beam, ActorLinkingRequestType::HOOK_ACTION); // OK to be invoked here - SyncReset() - `processing MSG_SIM_MODIFY_ACTOR_REQUESTED` } for (auto& r : ar_ropes) @@ -1613,7 +1616,7 @@ void Actor::SyncReset(bool reset_position) r.rp_locked = UNLOCKED; r.rp_locked_ropable = nullptr; r.rp_locked_actor = nullptr; - this->RemoveInterActorBeam(r.rp_beam); + App::GetGameContext()->GetActorManager()->RemoveInterActorBeam(r.rp_beam, ActorLinkingRequestType::ROPE_ACTION); // OK to be invoked here - SyncReset() - `processing MSG_SIM_MODIFY_ACTOR_REQUESTED` } for (auto& t : ar_ties) @@ -1625,9 +1628,13 @@ void Actor::SyncReset(bool reset_position) t.ti_beam->p2 = &ar_nodes[0]; t.ti_beam->bm_disabled = true; t.ti_beam->bm_inter_actor = false; - this->RemoveInterActorBeam(t.ti_beam); + App::GetGameContext()->GetActorManager()->RemoveInterActorBeam(t.ti_beam, ActorLinkingRequestType::TIE_ACTION); // OK to be invoked here - SyncReset() - `processing MSG_SIM_MODIFY_ACTOR_REQUESTED` } + // Remove any possible links from other actors to this actor. + + this->DisjoinInterActorBeams(); // OK to be invoked here - SyncReset() - `processing MSG_SIM_MODIFY_ACTOR_REQUESTED` + for (auto& r : ar_ropables) { r.attached_ties = 0; @@ -3282,54 +3289,18 @@ void Actor::updateVisual(float dt) ar_hydro_elevator_command = autoelevator; } -void Actor::AddInterActorBeam(beam_t* beam, ActorPtr a, ActorPtr b) -{ - beam->bm_locked_actor = b; - - auto pos = std::find(ar_inter_beams.begin(), ar_inter_beams.end(), beam); - if (pos == ar_inter_beams.end()) - { - ar_inter_beams.push_back(beam); - } - - std::pair actor_pair(a, b); - App::GetGameContext()->GetActorManager()->inter_actor_links[beam] = actor_pair; - - a->DetermineLinkedActors(); - for (ActorPtr& actor : a->ar_linked_actors) - actor->DetermineLinkedActors(); - - b->DetermineLinkedActors(); - for (ActorPtr& actor : b->ar_linked_actors) - actor->DetermineLinkedActors(); -} - -void Actor::RemoveInterActorBeam(beam_t* beam) +void Actor::DisjoinInterActorBeams() { - auto pos = std::find(ar_inter_beams.begin(), ar_inter_beams.end(), beam); - if (pos != ar_inter_beams.end()) + for (auto inter_actor_link: App::GetGameContext()->GetActorManager()->inter_actor_links) { - ar_inter_beams.erase(pos); - } - - auto it = App::GetGameContext()->GetActorManager()->inter_actor_links.find(beam); - if (it != App::GetGameContext()->GetActorManager()->inter_actor_links.end()) - { - auto actor_pair = it->second; - App::GetGameContext()->GetActorManager()->inter_actor_links.erase(it); - - actor_pair.first->DetermineLinkedActors(); - for (ActorPtr& actor : actor_pair.first->ar_linked_actors) - actor->DetermineLinkedActors(); - - actor_pair.second->DetermineLinkedActors(); - for (ActorPtr& actor : actor_pair.second->ar_linked_actors) - actor->DetermineLinkedActors(); + beam_t* beam = inter_actor_link.first; + auto actor_pair = inter_actor_link.second; + if (actor_pair.first == this || actor_pair.second == this) + { + App::GetGameContext()->GetActorManager()->RemoveInterActorBeam(beam, ActorLinkingRequestType::INVALID); // OK to invoke synchronously; already handling `MSG_SIM_{MODIFY/DELETE}_ACTOR_REQUESTED` + } } -} -void Actor::DisjoinInterActorBeams() -{ ar_inter_beams.clear(); auto inter_actor_links = &App::GetGameContext()->GetActorManager()->inter_actor_links; for (auto it = inter_actor_links->begin(); it != inter_actor_links->end();) @@ -3386,8 +3357,7 @@ void Actor::tieToggle(int group) it->ti_beam->bm_disabled = true; if (it->ti_locked_actor != this) { - this->RemoveInterActorBeam(it->ti_beam); - // NOTE: updating skeletonview on the tied actors is now done in `SyncLinkedActors()` + App::GetGameContext()->GetActorManager()->RemoveInterActorBeam(it->ti_beam, ActorLinkingRequestType::TIE_ACTION); // OK to invoke here - tieToggle() - processing `MSG_SIM_ACTOR_LINKING_REQUESTED` } it->ti_locked_actor = nullptr; } @@ -3457,7 +3427,7 @@ void Actor::tieToggle(int group) it->ti_locked_ropable->attached_ties++; if (it->ti_beam->bm_inter_actor) { - AddInterActorBeam(it->ti_beam, this, nearest_actor); + App::GetGameContext()->GetActorManager()->AddInterActorBeam(it->ti_beam, this, nearest_actor, ActorLinkingRequestType::TIE_ACTION); // OK to invoke here - tieToggle() - processing `MSG_SIM_ACTOR_LINKING_REQUESTED` // NOTE: updating skeletonview on the tied actors is now done in `SyncLinkedActors()` } } @@ -3489,8 +3459,7 @@ void Actor::ropeToggle(int group) it->rp_locked_ropable->attached_ropes--; if (it->rp_locked_actor != this) { - this->RemoveInterActorBeam(it->rp_beam); - // NOTE: updating skeletonview on the unroped actors is now done in `SyncLinkedActors()` + App::GetGameContext()->GetActorManager()->RemoveInterActorBeam(it->rp_beam, ActorLinkingRequestType::ROPE_ACTION); // OK to invoke here - ropeToggle() - processing `MSG_SIM_ACTOR_LINKING_REQUESTED` } it->rp_locked_actor = nullptr; it->rp_locked_ropable = nullptr; @@ -3534,7 +3503,7 @@ void Actor::ropeToggle(int group) it->rp_locked_ropable->attached_ropes++; if (nearest_actor != this) { - AddInterActorBeam(it->rp_beam, this, nearest_actor); + App::GetGameContext()->GetActorManager()->AddInterActorBeam(it->rp_beam, this, nearest_actor, ActorLinkingRequestType::ROPE_ACTION); // OK to invoke here - ropeToggle() - processing `MSG_SIM_ACTOR_LINKING_REQUESTED` // NOTE: updating skeletonview on the roped up actor is now done in `SyncLinkedActors()` } } @@ -3643,7 +3612,8 @@ void Actor::hookToggle(int group, HookAction mode, NodeNum_t mousenode /*=NODENU it->hk_beam->bm_inter_actor = (it->hk_locked_actor != nullptr); it->hk_beam->L = (it->hk_hook_node->AbsPosition - it->hk_lock_node->AbsPosition).length(); it->hk_beam->bm_disabled = false; - this->AddInterActorBeam(it->hk_beam, this, it->hk_locked_actor); + App::GetGameContext()->GetActorManager()->AddInterActorBeam(it->hk_beam, this, it->hk_locked_actor, ActorLinkingRequestType::HOOK_ACTION); // OK to invoke here - hookToggle() - processing `MSG_SIM_ACTOR_LINKING_REQUESTED` + // NOTE: updating skeletonview on the hooked actor is done in `SyncLinkedActors()` } } } @@ -3653,7 +3623,7 @@ void Actor::hookToggle(int group, HookAction mode, NodeNum_t mousenode /*=NODENU { // we unlock ropes immediatelly it->hk_locked = UNLOCKED; - this->RemoveInterActorBeam(it->hk_beam); + App::GetGameContext()->GetActorManager()->RemoveInterActorBeam(it->hk_beam, ActorLinkingRequestType::HOOK_ACTION); // OK to invoke here - hookToggle() - processing `MSG_SIM_ACTOR_LINKING_REQUESTED` if (it->hk_group <= -2) { it->hk_timer = it->hk_timer_preset; //timer reset for autolock nodes @@ -3666,8 +3636,6 @@ void Actor::hookToggle(int group, HookAction mode, NodeNum_t mousenode /*=NODENU it->hk_beam->L = (ar_nodes[0].AbsPosition - it->hk_hook_node->AbsPosition).length(); it->hk_beam->bm_disabled = true; } - - // NOTE: updating skeletonview on the (un)hooked actor is now done in `SyncLinkedActors()` } } @@ -4621,3 +4589,13 @@ void Actor::removeWorkingTuneupDef() { m_working_tuneup_def = nullptr; } + +bool Actor::ownsBeam(beam_t* beam) +{ + for (int i = 0; i < ar_num_beams; i++) + { + if (&ar_beams[i] == beam) + return true; + } + return false; +} diff --git a/source/main/physics/Actor.h b/source/main/physics/Actor.h index 752bae5c20..edfb885212 100644 --- a/source/main/physics/Actor.h +++ b/source/main/physics/Actor.h @@ -263,6 +263,7 @@ class Actor : public RefCountingObject Ogre::Real getMinimalCameraRadius(); float GetFFbHydroForces() const { return m_force_sensors.out_hydros_forces; } void UpdatePropAnimInputEvents(); + bool ownsBeam(beam_t* beam); // -------------------- Public data -------------------- // @@ -302,7 +303,6 @@ class Actor : public RefCountingObject std::vector ar_initial_node_positions; std::vector> ar_initial_beam_defaults; std::vector ar_wheeldetachers; - ActorPtrVec ar_linked_actors; //!< Sim state; other actors linked using 'hooks' std::vector> ar_node_to_node_connections; std::vector> ar_node_to_beam_connections; std::vector ar_collision_bounding_boxes; //!< smart bounding boxes, used for determining the state of an actor (every box surrounds only a subset of nodes) @@ -436,6 +436,7 @@ class Actor : public RefCountingObject ActorState ar_state = ActorState::LOCAL_SIMULATED; bool ar_physics_paused = false; //!< Valid with `ActorState::LOCAL_SIMULATED`; triggered by `EV_TRUCK_TOGGLE_PHYSICS` bool ar_ongoing_reset = false; //!< Hack to prevent position/rotation creep during LiveRepair (a.k.a. interactive truck reset) + ActorPtrVec ar_linked_actors; //!< Other actors linked using 'hooks/ties/ropes/slidenodes'; see `MSG_SIM_ACTOR_LINKING_REQUESTED` // Repair state Ogre::Vector3 m_rotation_request_center = Ogre::Vector3::ZERO; @@ -506,9 +507,7 @@ class Actor : public RefCountingObject void DetermineLinkedActors(); void RecalculateNodeMasses(Ogre::Real total); //!< Previously 'calc_masses2()' void calcNodeConnectivityGraph(); - void AddInterActorBeam(beam_t* beam, ActorPtr a, ActorPtr b); - void RemoveInterActorBeam(beam_t* beam); - void DisjoinInterActorBeams(); //!< Destroys all inter-actor beams which are connected with this actor + void DisjoinInterActorBeams(); //!< Helper for `MSG_` handlers, do not invoke by hand. void autoBlinkReset(); //!< Resets the turn signal when the steering wheel is turned back. void ResetAngle(float rot); void calculateLocalGForces(); //!< Derive the truck local g-forces from the global ones diff --git a/source/main/physics/ActorManager.cpp b/source/main/physics/ActorManager.cpp index 08c5c7afe6..d8be0bcc39 100644 --- a/source/main/physics/ActorManager.cpp +++ b/source/main/physics/ActorManager.cpp @@ -721,58 +721,199 @@ void ActorManager::ForwardCommands(ActorPtr source_actor) } } -// Internal helper for future extensions -void SyncSlaveActorWithMasterActor(const ActorPtr& slave, const ActorPtr& master) +void ActorManager::SyncLinkedActors() { - // Always sync the paused state - slave->ar_physics_paused = master->ar_physics_paused; + // Sync shared state (debugviews, pause, repair...) between linked actors + // ------------------------------------------------------------------- + + for (auto& entry: inter_actor_links) + { + auto& actor_pair = entry.second; + const ActorPtr& master = actor_pair.first; + const ActorPtr& slave = actor_pair.second; + + // sync the paused state + slave->ar_physics_paused = master->ar_physics_paused; - // Always sync skeletonview - slave->GetGfxActor()->SetDebugView(master->GetGfxActor()->GetDebugView()); + // sync skeletonview + slave->GetGfxActor()->SetDebugView(master->GetGfxActor()->GetDebugView()); + + // Sync live repair movement if requested + if (App::sim_soft_reset_mode->getBool()) + { + slave->requestRotation(master->m_rotation_request, master->m_rotation_request_center); + slave->requestTranslation(master->m_translation_request); + slave->requestAngleSnap(master->m_anglesnap_request); + } + } +} - // Sync live repair movement if requested - if (App::sim_soft_reset_mode->getBool()) +bool ActorManager::AreActorsLinked(const ActorPtr& a1, const ActorPtr& a2) +{ + for (auto& entry: inter_actor_links) { - slave->requestRotation(master->m_rotation_request, master->m_rotation_request_center); - slave->requestTranslation(master->m_translation_request); - slave->requestAngleSnap(master->m_anglesnap_request); + auto& actor_pair = entry.second; + if ((actor_pair.first == a1 && actor_pair.second == a2) || + (actor_pair.first == a2 && actor_pair.second == a1)) + { + return true; + } } + return false; } -// internal helper for future extensions -void ResetUnlinkedActor(const ActorPtr& loner) +void ActorManager::AddInterActorBeam(beam_t* beam, ActorPtr a, ActorPtr b, ActorLinkingRequestType type) { - // Always unpause - loner->ar_physics_paused = false; + // Handler of `MSG_SIM_ACTOR_LINKING_REQUESTED` (main thread); should not be invoked otherwise + // Actor A is actively connecting, Actor B is passively connected + // ------------------------------------------------------------------------------------------ + + // CHECKS: ~ if anything doesn't add up, just do nothing and let users report a problem. + + // Sanity check: beam must belong to Actor A + const bool a_owns_beam = a->ownsBeam(beam); + ROR_ASSERT(a_owns_beam); + if (!a_owns_beam) + { + return; + } + + // Sanity check: beam must not be locked to any actor + ROR_ASSERT(beam->bm_locked_actor == nullptr); + if (beam->bm_locked_actor != nullptr) + { + return; + } + + // Integrity check: Make sure the beam isn't already in the *global* linkage list + const auto existing_link_itor = inter_actor_links.find(beam); + ROR_ASSERT(existing_link_itor == inter_actor_links.end()); + if (existing_link_itor != inter_actor_links.end()) + { + return; + } + + // Integrity check: Make sure the beam isn't already in the *actor's* linkage list + const auto existing_interbeam_itor = std::find(a->ar_inter_beams.begin(), a->ar_inter_beams.end(), beam); + ROR_ASSERT(existing_interbeam_itor == a->ar_inter_beams.end()); + if (existing_interbeam_itor != a->ar_inter_beams.end()) + { + return; + } + + bool linked_before = this->AreActorsLinked(a, b); + + // BEAM LINKING: + + // Add record to the global linkage list + std::pair actor_pair(a, b); + App::GetGameContext()->GetActorManager()->inter_actor_links[beam] = actor_pair; + + // Add record to interbeams list + a->ar_inter_beams.push_back(beam); + beam->bm_locked_actor = b; + + // ACTOR LINKING: + + // Record the new linkage ~ NOTE syncing is done per-frame by `SyncLinkedActors()` + if (!linked_before) + { + // Update actor A (the connecting one) + a->ar_linked_actors.push_back(b); + + // Update actor B (the connected one) + b->ar_linked_actors.push_back(a); - // Always disable skeletonview - loner->GetGfxActor()->SetDebugView(DebugViewType::DEBUGVIEW_NONE); + // Let scripts know + TRIGGER_EVENT_ASYNC(SE_GENERIC_TRUCK_LINKING_CHANGED, 1, (int)type, a->ar_instance_id, b->ar_instance_id); + } } -void ActorManager::SyncLinkedActors() +void ActorManager::RemoveInterActorBeam(beam_t* beam, ActorLinkingRequestType type) { - // Sync shared state (debugviews, pause, repair) between linked actors - // Reset state of non-linked actors - // ------------------------------------------------------------------- + // Handler of `MSG_SIM_ACTOR_LINKING_REQUESTED` (main thread); should not be invoked otherwise + // ------------------------------------------------------------------------------------------ - // For now, we only sync state of player-driven actor with all it's linked actors - // TBD: Maintain a graph of master->slave relations so AI vehicles sync too. - if (App::GetGameContext()->GetPlayerActor()) + // CHECKS: ~ if anything doesn't add up, just do nothing and let users report a problem. + + // Sanity check: the beam must be linked to actor + ROR_ASSERT(beam->bm_locked_actor != nullptr); + if (beam->bm_locked_actor == nullptr) { - for (const ActorPtr& actor : App::GetGameContext()->GetPlayerActor()->ar_linked_actors) - { - SyncSlaveActorWithMasterActor(actor, App::GetGameContext()->GetPlayerActor()); - } + return; } - // Reset unlinked actors, except the player actor - for (const ActorPtr& actor: m_actors) + // Integrity check: find the beam in the *global* linkage list + const auto existing_link_itor = inter_actor_links.find(beam); + ROR_ASSERT(existing_link_itor == inter_actor_links.end()); + if (existing_link_itor != inter_actor_links.end()) { - if (actor->ar_linked_actors.size() == 0 - && actor != App::GetGameContext()->GetPlayerActor()) - { - ResetUnlinkedActor(actor); - } + return; + } + + // Integrity check: check actor A is in sync with the linkage list + const ActorPtr& a = existing_link_itor->second.first; + const bool a_owns_beam = a->ownsBeam(beam); + ROR_ASSERT(a_owns_beam); + if (!a_owns_beam) + { + return; + } + + // Integrity check: check actor B is in sync with the linkage list + const ActorPtr& b = existing_link_itor->second.second; + ROR_ASSERT(beam->bm_locked_actor == b); + if (beam->bm_locked_actor != b) + { + return; + } + + // Integrity check: find the interbeam record + const auto existing_interbeam_itor = std::find(a->ar_inter_beams.begin(), a->ar_inter_beams.end(), beam); + ROR_ASSERT(existing_interbeam_itor != a->ar_inter_beams.end()); + if (existing_interbeam_itor == a->ar_inter_beams.end()) + { + return; + } + + // Integrity check: check actor A knows about actor B + const auto actor_a_linkage_itor = std::find(a->ar_linked_actors.begin(), a->ar_linked_actors.end(), b); + ROR_ASSERT(actor_a_linkage_itor != a->ar_linked_actors.end()); + if (actor_a_linkage_itor == a->ar_linked_actors.end()) + { + return; + } + + // Integrity check: check actor B knows about actor A + const auto actor_b_linkage_itor = std::find(b->ar_linked_actors.begin(), b->ar_linked_actors.end(), a); + ROR_ASSERT(actor_b_linkage_itor != b->ar_linked_actors.end()); + if (actor_b_linkage_itor == b->ar_linked_actors.end()) + { + return; + } + + // BEAM UNLINKING: + + // Remove record from the *global* linkage list + inter_actor_links.erase(existing_link_itor); + + // Remove record from interbeams list + a->ar_inter_beams.erase(existing_interbeam_itor); + beam->bm_locked_actor = nullptr; + + // ACTOR UNLINKING: + + // If not linked anymore, remove *local* linkage records + if (!this->AreActorsLinked(a, b)) + { + // Update actor A (the connecting one) + a->ar_linked_actors.erase(actor_a_linkage_itor); + + // Update actor B (the connected one) + b->ar_linked_actors.erase(actor_b_linkage_itor); + + // Let scripts know + TRIGGER_EVENT_ASYNC(SE_GENERIC_TRUCK_LINKING_CHANGED, 0, (int)type, a->ar_instance_id, b->ar_instance_id); } } diff --git a/source/main/physics/ActorManager.h b/source/main/physics/ActorManager.h index d7913a47c3..8dee49b22e 100644 --- a/source/main/physics/ActorManager.h +++ b/source/main/physics/ActorManager.h @@ -111,9 +111,11 @@ class ActorManager /// @name Actor grouping /// @{ - // A list of all beams interconnecting two actors - std::map> inter_actor_links; + std::map> inter_actor_links; //!< Actor pair is { Master(first) -> Slave(second) } + bool AreActorsLinked(const ActorPtr& a1, const ActorPtr& a2); void SyncLinkedActors(); + void AddInterActorBeam(beam_t* beam, ActorPtr a, ActorPtr b, ActorLinkingRequestType type); //!< Do not call directly - use `MSG_SIM_ACTOR_LINKING_REQUESTED` + void RemoveInterActorBeam(beam_t* beam, ActorLinkingRequestType type); //!< Do not call directly - use `MSG_SIM_ACTOR_LINKING_REQUESTED` /// @} static const ActorPtr ACTORPTR_NULL; // Dummy value to be returned as const reference. diff --git a/source/main/physics/Savegame.cpp b/source/main/physics/Savegame.cpp index 8ef53219a8..145054ddac 100644 --- a/source/main/physics/Savegame.cpp +++ b/source/main/physics/Savegame.cpp @@ -959,7 +959,7 @@ void ActorManager::RestoreSavedState(ActorPtr actor, rapidjson::Value const& j_e locked_actor < (int)actors.size() && actors[locked_actor] != nullptr) { - actor->AddInterActorBeam(&actor->ar_beams[i], actor, actors[locked_actor]); + App::GetGameContext()->GetActorManager()->AddInterActorBeam(&actor->ar_beams[i], actor, actors[locked_actor], ActorLinkingRequestType::INVALID); // OK to be invoked here - RestoreSavedState() - processing MSG_SIM_MODIFY_ACTOR_REQUESTED } } diff --git a/source/main/scripting/bindings/ScriptEventsAngelscript.cpp b/source/main/scripting/bindings/ScriptEventsAngelscript.cpp index db7ee6691e..117b22976d 100644 --- a/source/main/scripting/bindings/ScriptEventsAngelscript.cpp +++ b/source/main/scripting/bindings/ScriptEventsAngelscript.cpp @@ -65,6 +65,8 @@ void RoR::RegisterScriptEvents(asIScriptEngine *engine) result = engine->RegisterEnumValue("scriptEvents", "SE_GENERIC_EXCEPTION_CAUGHT", SE_GENERIC_EXCEPTION_CAUGHT); ROR_ASSERT(result>=0); result = engine->RegisterEnumValue("scriptEvents", "SE_GENERIC_MODCACHE_ACTIVITY", SE_GENERIC_MODCACHE_ACTIVITY); ROR_ASSERT(result>=0); + result = engine->RegisterEnumValue("scriptEvents", "SE_GENERIC_TRUCK_LINKING_CHANGED", SE_GENERIC_TRUCK_LINKING_CHANGED); ROR_ASSERT(result>=0); + result = engine->RegisterEnumValue("scriptEvents", "SE_ALL_EVENTS", SE_ALL_EVENTS); ROR_ASSERT(result>=0); result = engine->RegisterEnumValue("scriptEvents", "SE_NO_EVENTS", SE_NO_EVENTS); ROR_ASSERT(result>=0);