Skip to content

Let scripts remove, restore and edit the map's own occluders - #5379

Draft
Flashmyname wants to merge 6 commits into
multitheftauto:masterfrom
Flashmyname:pr/editable-occluders
Draft

Flashmyname wants to merge 6 commits into
multitheftauto:masterfrom
Flashmyname:pr/editable-occluders

Conversation

@Flashmyname

@Flashmyname Flashmyname commented Sep 12, 2026 •

Copy link
Copy Markdown
Contributor

Summary

The other half of #4079, on top of #5378. That one adds occluders, this one lets scripts remove, restore and edit the ones the map loads.

table engineGetOccluders ( [ bool interior = false ] )
bool  engineRestoreOccluder ( int id )
int   engineRemoveOccluders ( float x, float y, float z, float radius [, bool interior = false ] )
int   engineRestoreOccluders ( float x, float y, float z, float radius [, bool interior = false ] )
-- free the occlusion of the district you are replacing, then spend the slots on your own
local freed = engineRemoveOccluders(2400, 2400, 20, 300)
engineAddOccluder(2400, 2400, 20, 80, 2, 30)

-- or pull the police station's own occluder over the building you put in its place
local zones = engineGetOccluders()
engineSetOccluder(zones[1].id, 2400, 2400, 20, 60, 4, 40)
engineRestoreOccluder(zones[1].id)   -- back to what the map loaded

engineRemoveOccluder and engineSetOccluder take a map occluder id as well, and engineGetOccluderCapacity counts a removed one as free.

Neither a removal nor an edit touches the loaded entry, so a restore gives back the geometry the IPL loader wrote. Both end when the resource that asked for them stops.

Ids are per client and follow map load order, so read them from engineGetOccluders instead of hardcoding them.

Motivation

On a stock install the map already uses 995 of the 1000 exterior slots, so #5378 has five to work with. The ones in use still cull the buildings the map shipped with, even where those have been removed.

Test plan

Test resource: occedit.zip

53 checks run on join and print to the server log. The commands cover the whole API:

/occfind [radius] [interior]                      list what is near you, nearest first
/occdel  id                                       engineRemoveOccluder
/occback id                                       engineRestoreOccluder
/occwipe [radius] [interior]                      engineRemoveOccluders
/occundo [radius] [interior]                      engineRestoreOccluders
/occmod  id x y z [sx] [sy] [sz] [rx] [ry] [rz]   engineSetOccluder on a map occluder
/occpull id                                       drag one to where you stand
/occdraw [radius]                                 outlines, green still in, red held out
/occcap  [interior]                               engineGetOccluderCapacity
on off

Checklist

  • Your code should follow the coding guidelines.
  • Smaller pull requests are easier to review. If your pull request is beefy, your pull request should be reviewable commit-by-commit.

@Flashmyname
Flashmyname marked this pull request as draft September 12, 2026 13:12
@Flashmyname Flashmyname changed the title Let scripts remove and restore the map's own occluders Let scripts remove, restore and edit the map's own occluders Sep 13, 2026
@Flashmyname

Flashmyname commented Sep 13, 2026 •

Copy link
Copy Markdown
Contributor Author

Updated along with #5378. engineSetOccluder now takes a map occluder id as well, so the map's own occlusion can be moved rather than only removed:

local zones = engineGetOccluders()
engineSetOccluder(zones[1].id, 2400, 2400, 20, 60, 4, 40)
engineRestoreOccluder(zones[1].id)   -- back to what the map loaded

The original entry stays as the IPL loader wrote it, so that is what a restore gives back. Only one resource can hold an edit, and when it stops the occluder goes back to normal.

The test resource covers the whole API from commands now, listed in the description.

GTA only loads occluders from the map files, so nothing built from scripted objects could hide what it covers. Boxes go through the game's own COcclusion::AddOne, a removal replays the rest over a snapshot of the map's entries, and a resource's boxes go with it when it stops.
Removing and re-adding a box gives its slot up in between, so the add can fail on a full array and the script ends up with a new id or no box at all. The entry is rewritten in place, with the rebuild a removal already does as the fallback.
@FileEX FileEX added the enhancement New feature or request label Sep 23, 2026
Copilot AI balanced review requested due to automatic review settings October 1, 2026 10:31
@Flashmyname
Flashmyname force-pushed the pr/editable-occluders branch from 98a211a to ebe8daf Compare October 1, 2026 10:31

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🔵 Needs a closer look

Fixed-address native memory manipulation and occluder-list rebuilding require final human review and runtime validation.

Review effort: Balanced
Findings: 1 Low severity

Open (1)
What changed in this PR

Adds Lua APIs for querying, editing, removing, and restoring GTA map occluders with resource-scoped cleanup.

Changes:

  • Exposes occluder management APIs to Lua.
  • Snapshots and safely rebuilds native occluder pools.
  • Restores resource-owned changes during cleanup.

Commit messages should retain the motivation, implementation reasoning, and testing details.

File Description
Client/​sdk/​game/​CWorld.h Defines occluder data and world APIs.
Client/​mods/​deathmatch/​logic/​luadefs/​CLuaEngineDefs.h Declares Lua-facing functions.
Client/​mods/​deathmatch/​logic/​luadefs/​CLuaEngineDefs.cpp Implements validation and Lua bindings.
Client/​mods/​deathmatch/​logic/​CResource.cpp Cleans up resource-owned changes.
Client/​mods/​deathmatch/​logic/​CClientGame.cpp Resets occluders on game initialization.
Client/​game_sa/​CWorldSA.h Defines native pool structures and state.
Client/​game_sa/​CWorldSA.cpp Implements capture, rebuild, editing, and restoration.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread Client/mods/deathmatch/logic/CResource.cpp Outdated
The byte snapshot becomes one record per loaded entry with an id and the resources holding it out, so a rebuild can leave it out and its slot counts as free. Ids follow the client's IPL load order and are read through GetOccluders.
An edit is carried beside the loaded bytes and replaces the geometry until the resource that made it restores the occluder or stops, so a restore still hands back what the IPL loader wrote. One resource owns an edit at a time.
engineRemoveOccluder and engineSetOccluder already accept a map occluder
id through the existing entry points; these four functions list the
occluders with their ids and lift or apply removals by id and by radius.
Ids follow the client's IPL load order, so a script reads them from
engineGetOccluders rather than hardcoding them.
@Flashmyname
Flashmyname force-pushed the pr/editable-occluders branch from ebe8daf to 17b6d2b Compare October 1, 2026 10:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants