setObjectScale: add optional scaleCollision argument to match collision to visual scale (collision scale)#4985
Open
TheCrazy17 wants to merge 12 commits into
Open
Conversation
Builds a new CColModel by rescaling a model's collision geometry (spheres, boxes, vertices, suspension lines/disks) and feeding it through the engine's own COL3 parser, so memory ownership ends up identical to a normal custom .col load. Non-uniform scale gets rejected when the source has collision spheres, disks or lines, since those only carry a single radius and can't be scaled correctly per axis. Also adds CModelInfoSA::GetColModelInterface() to read a model's current collision before scaling it.
AcquireScaledCollisionModel clones a base model into a free custom model ID with scaled collision, sharing that clone with any other caller asking for the same model and scale instead of duplicating it. ReleaseScaledCollisionModel drops a reference and frees the clone's collision and model slot once nobody's using it anymore. Releasing detaches the collision first, same order engineReplaceCOL already uses, then destroys it and frees the model slot. Freeing the slot first would make CModelInfoSA::Remove skip the actual unload while it still thinks a custom col model is assigned.
SetScale now takes a bScaleCollision flag, default false so nothing existing changes. When it's on, the object switches to a scaled collision clone from CClientModelManager; turning it off (or destroying the object) switches back to the real model and releases the clone. The real base model is kept separately so re-scaling or disabling it later always starts from the original, not a clone. The clone only gets released after Destroy()/SetModel() already dropped this object's own reference to it, not before, otherwise its model info could get freed while still in use.
setObjectScale(object, scale[, scaleY, scaleZ, scaleCollision=false]), defaulting to false so every existing script keeps its current visual-only scaling behaviour.
CObject now stores the scaleCollision flag alongside its scale and passes it through setObjectScale on the server's Lua API. It's synced to clients both on live changes (the SET_OBJECT_SCALE RPC) and on initial entity creation, so players who join later still apply the same collision scaling as everyone else.
CClientObject::Create() applied scale through the full CClientObject::SetScale(), which can acquire or release a scaled collision clone and call SetModel(). That destroys and recursively re-creates the very object currently being constructed, and if the resulting model needs to stream in asynchronously, m_pObject is left null for the rest of Create(), crashing on the next access to it (for example SetAreaCode). By the time Create() runs, the collision clone bookkeeping is already settled, since it's what's streaming m_usModel in, so only the visual scale needs to be applied here. Skip CClientObject::SetScale() and call m_pObject->SetScale() directly instead.
setObjectScale(obj, x, y, z) without the scaleCollision argument defaulted it to false, which meant calling it again to tweak the visual scale alone would turn off any collision scaling that was already active, on both the client and the server. scaleCollision is now optional end to end (Lua argument, static function definitions, and CClientObject::SetScale). Leaving it unspecified preserves whatever collision-scaling state the object already has, instead of resetting it. Explicitly passing true or false still works as before.
…llision CClientManager's destructor deletes and nulls each manager in a fixed order (pickups, then objects, among others). Releasing a scaled collision clone in CClientObject's destructor can cascade into CClientModel::RestoreDFF, which restores every entity type that could be using the model, including pickups and buildings, with no null checks on those manager pointers. During full client shutdown, the pickup manager (and potentially others) is already gone by the time the object manager destroys its objects, so this crashed. RestoreDFF now skips each restore step whose manager isn't available instead of assuming it always is.
Both CClientObject::Create() and SetScale() applied the visual scale after ProcessCollision()/UpdateVisibility() had already run (or, for SetScale(), after a model swap that re-runs Create() with the old scale, since m_vecScale was only updated at the very end of the function). That registers the object's collision and visibility bounds at its old, usually unscaled, size, so a scaled object could flicker in and out of view at some camera angles even after a single scale call. m_vecScale is now updated before SetModel() can trigger a recreation, and Create() applies the scale before processing collision and visibility, so both always reflect the object's real size from the moment it's registered.
The scaled-collision feature gives a scaled object its own cloned model whose collision is a scaled copy of the base model's. Several issues kept that scaled collision from actually being used: - Root cause: CModelInfoSA::SetColModel() early-returned whenever the requested col model was already recorded as the custom one (m_pCustomColModel == pColModel). MakeCustomModel() re-invokes SetColModel() right after a model streams in, precisely to re-apply the custom collision over whatever the reload reset the interface's pColModel back to. The early-return turned that re-apply into a no-op, so a freshly streamed clone kept the original disk collision and ignored the scaled one. It only "fixed itself" on a second setObjectScale call because of leftover interface state. The guard now also checks the live interface still has the custom col actually applied, so the re-apply runs when (and only when) it's needed. - CreateScaledColModel rejected any non-uniform scale on collisions with spheres/disks/lines, even though the rest of the function already approximates those radii with the largest scale axis (as it does for the bounding sphere). Removed the rejection so the approximation is actually used instead of silently producing no scaled collision. - Force a blocking load of the base model's collision before cloning it, so scaling immediately after createObject() doesn't race the model's own streaming and find no collision to clone. - Force a blocking load of the clone model before switching to it, so Create() runs synchronously and applies the scaled collision on the first call instead of waiting for an async callback that never fired. - Clear the scaled-collision clone cache in RemoveAll(), so reconnecting can't hand back a cache entry whose clone no longer exists (which left objects visually scaled but with unscaled collision after a reconnect).
When scaling an object in the same frame it is created, its collision slot may not have been streamed in yet, causing m_data to be nullptr. Previously, this was incorrectly treated as a "no collision" case (visual/LOD model), silently leaving the object unscaled. Fix: request and force-load the collision stream slot via the engine's streaming system before giving up. Only return nullptr if data is still absent after the force-load, which is the genuine "no collision" case.
Contributor
|
This is in the Top 10 of most wanted MTA features in the last decade :) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds an optional
scaleCollisionboolean tosetObjectScale(client and server-side). Whentrue, the collision model is cloned and scaled to match the visual.Defaults to false for full backward compatibility, so existing scripts are unaffected.
Motivation
Closes #3775.
Objects scaled visually kept their original collision size, causing an obvious mismatch (e.g. a giant object you can walk through). The argument is opt-in rather than automatic to avoid breaking existing scripts that rely on the old behavior, for example, some DM maps scale objects to make them non-solid intentionally.
Open to feedback and improvements on the implementation approach.
Test plan
setObjectScale(obj, 3, 4, 1, true) -> collision matches the enlarged visual (stand on/collide with it).
setObjectScale(obj, 3, 4, 1) (omitted) -> collision unchanged, backward compatible.
Scale an object the same frame it is created -> works correctly.
Disconnect and reconnect with a scaled object -> collision is restored.
Server sets scale with scaleCollision = true -> clients receive and apply it.
Existing scripts using setObjectScale without the new argument, behavior identical to before.
Checklist