feat(plugins): Add a plugin hook framework for observer overlays - #585
Draft
doopey655 wants to merge 1 commit into
Draft
feat(plugins): Add a plugin hook framework for observer overlays#585doopey655 wants to merge 1 commit into
doopey655 wants to merge 1 commit into
Conversation
Adds a host-side plugin loader and a C ABI so an observer overlay can live outside the engine as a DLL. The engine loads every *.goplugin.dll under plugins\ at startup; each plugin opts into the hook categories it wants and receives events through an ABI that never exposes engine types. With no plugin present nothing changes: every call site is a guarded call whose condition is an inlined !empty() on a static vector. PluginABI.h is the host/plugin contract - pure C, no STL and no engine types, so a plugin compiles against that one header. It is versioned (GO_PLUGIN_ABI_VERSION 5) with a structSize guard, and a mismatch fails the load rather than risk misreading the table. Callbacks carry information about other players, so the whole framework is gated on the local player being an observer or dead - the same condition InGameUI::drawObserverStats uses, and one that also holds during replay playback. While that gate is closed no callback is delivered and GO_Plugin_Tick is not called, so a match participant cannot gain an information advantage from a plugin. No existing engine function is restructured. Every touch point is an inserted guarded call, so this diff removes no upstream line. Two additions are worth a reviewer's attention: - View::worldToScreenTriReturnAllowFarClip(), a new virtual with a non-pure default so existing View subclasses are unaffected. worldToScreenTriReturn rejects anything past the far clip plane, but CameraClass::Project has already written a valid perspective-divided position by then - only OUTSIDE_NEAR_CLIP zeroes its output - and an off-screen edge indicator needs it. It is deliberately standalone rather than sharing a body with the existing function, which is therefore untouched. A downcast from the plugin framework was rejected because ViewDummy is installed as TheTacticalView in headless builds. - InGameUI::drawPluginText2D and drawPluginText2DScaled, on InGameUI only because m_messageFont, m_messagePointSize and m_messageBold are protected with no getters. The rect and line primitives, which only forward to TheDisplay, live in the plugin framework instead. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
Description
Adds a host-side plugin loader and a C ABI so an observer overlay can live outside the engine as a
DLL. The engine loads every
*.goplugin.dllunderplugins\at startup; each plugin opts into thehook categories it wants and receives events through an ABI that never exposes engine types.
With no plugin present nothing changes. Every call site is a guarded call whose condition is an
inlined
!empty()on a static vector.What it adds
PluginABI.h— the host/plugin contract. Pure C (POD structs, function pointers,primitives), no STL and no engine types, so a plugin compiles against this one header. Versioned
(
GO_PLUGIN_ABI_VERSION 5) with astructSizeguard; a mismatch fails the load rather than riskmisreading the table. Exports
GO_Plugin_GetInfo/_Initialize/_Shutdown, plus an optional_Tick.GOPluginManager— loads one folder per plugin, validates the ABI version, dispatches events.A plugin that fails to load is logged and skipped, never fatal.
power triggered, object damaged and healed. Payloads carry template names as strings and object
ids as
uint32, never engine pointers.channel that never consumes or gates the engine's own input handling.
screen bounds, logic frame and FPS, the player roster with colours, general-power recharge state,
viewport teleport, the per-user data path, and logging.
Observer-only gate
Callbacks carry information about other players, so the whole framework is gated on
IsLocalPlayerObserver()— local player is an observer or dead, which is also true during replayplayback. While that gate is closed no callback is delivered and
GO_Plugin_Tickis not called, soa match participant cannot gain an information advantage from a plugin. Same condition
InGameUI::drawObserverStatsalready uses.Engine impact
17 files, ~2460 insertions, 0 deletions. No existing engine function is restructured — every
touch point is an inserted guarded call, so
git diff main -U0 | grep '^-[^-]'is empty.Three quarters of the diff is the two new self-contained files. The rest:
PluginABI.h,PluginManager.h/.cppProductionUpdate.cppWindowXlat.cppInGameUI.cpp/.hpostWindowDraw(), plus two text primitivesActiveBody.cppView.h,W3DView.h/.cppworldToScreenTriReturnAllowFarClip(), belowCommandXlat.cppSpecialPowerModule.cppW3DDisplay.cppGameEngine.cppinit(), tick inupdate(), unload in the destructorCMakeLists.txtTwo additions worth a reviewer's attention:
View::worldToScreenTriReturnAllowFarClip()— a new virtual with a non-pure default, soexisting
Viewsubclasses are unaffected.worldToScreenTriReturnrejects anything past the farclip plane, but
CameraClass::Projecthas already written a valid perspective-divided position bythen (only
OUTSIDE_NEAR_CLIPzeroes its output), and an off-screen edge indicator needs it. Itis deliberately standalone rather than sharing a body with the existing function, which is
therefore untouched. A downcast from
PluginManagerwas rejected:ViewDummyis installed asTheTacticalViewin headless builds.InGameUI::drawPluginText2D[Scaled]— onInGameUIonly becausem_messageFont,m_messagePointSizeandm_messageBoldare protected with no getters. The rect and lineprimitives, which only forward to
TheDisplay, live in the plugin framework instead.