<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://g1r-mp.com/wiki/index.php?action=history&amp;feed=atom&amp;title=Observer_nameplates</id>
	<title>Observer nameplates - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://g1r-mp.com/wiki/index.php?action=history&amp;feed=atom&amp;title=Observer_nameplates"/>
	<link rel="alternate" type="text/html" href="https://g1r-mp.com/wiki/index.php?title=Observer_nameplates&amp;action=history"/>
	<updated>2026-09-19T06:37:19Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.46.0</generator>
	<entry>
		<id>https://g1r-mp.com/wiki/index.php?title=Observer_nameplates&amp;diff=858&amp;oldid=prev</id>
		<title>QCherry: Document universal per-observer nameplates and test resource available from update 0.1.3</title>
		<link rel="alternate" type="text/html" href="https://g1r-mp.com/wiki/index.php?title=Observer_nameplates&amp;diff=858&amp;oldid=prev"/>
		<updated>2026-09-09T03:08:52Z</updated>

		<summary type="html">&lt;p&gt;Document universal per-observer nameplates and test resource available from update 0.1.3&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;{{DISPLAYTITLE:Observer nameplates}}&lt;br /&gt;
&amp;lt;div style=&amp;quot;background:#fff8f8; border:1px solid #d13b3b; border-left:5px solid #d13b3b; color:#111; padding:0.95rem 1.1rem; margin-bottom:1rem; font-weight:bold;&amp;quot;&amp;gt;OBSERVER NAMEPLATES — AVAILABLE FROM UPDATE 0.1.3&amp;lt;/div&amp;gt;&lt;br /&gt;
&lt;br /&gt;
These &amp;#039;&amp;#039;&amp;#039;12 new server-side functions&amp;#039;&amp;#039;&amp;#039; control overhead labels for an ordered pair of connected human players: &amp;#039;&amp;#039;&amp;#039;observer → target&amp;#039;&amp;#039;&amp;#039;. They are universal presentation primitives: the gamemode owns acquaintance rules, permissions, character/account identifiers and database persistence. They do not replace [[setPlayerName]] or change the player&amp;#039;s character appearance.&lt;br /&gt;
&lt;br /&gt;
== Function reference ==&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Function !! Purpose&lt;br /&gt;
|-&lt;br /&gt;
| [[setPlayerNameplateTextFor]] / [[getPlayerNameplateTextFor]] / [[resetPlayerNameplateTextFor]] || Set, read or remove an explicit text override for one pair.&lt;br /&gt;
|-&lt;br /&gt;
| [[setPlayerNameplateVisibleFor]] / [[isPlayerNameplateVisibleFor]] / [[resetPlayerNameplateVisibleFor]] || Control the complete overhead label for one pair, including numeric ID and speaking suffix.&lt;br /&gt;
|-&lt;br /&gt;
| [[setPlayerNameplateIdVisibleFor]] / [[isPlayerNameplateIdVisibleFor]] / [[resetPlayerNameplateIdVisibleFor]] || Control only the numeric session-ID suffix for one pair.&lt;br /&gt;
|-&lt;br /&gt;
| [[getPlayerNameplateStateFor]] || Read effective policy, explicit override flags and raw values together.&lt;br /&gt;
|-&lt;br /&gt;
| [[setPlayerNameplateIdVisible]] / [[isPlayerNameplateIdVisible]] || Set/read the global numeric-ID fallback, default &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt;.&lt;br /&gt;
|}&lt;br /&gt;
All twelve functions have PascalCase aliases, for example &amp;lt;code&amp;gt;SetPlayerNameplateTextFor&amp;lt;/code&amp;gt;. The first ten take &amp;lt;code&amp;gt;observerId, targetId&amp;lt;/code&amp;gt;; both must be different connected human-player session IDs. NPC IDs, persistent character IDs, invalid IDs and self-pairs are rejected.&lt;br /&gt;
&lt;br /&gt;
== Independent fields and current defaults ==&lt;br /&gt;
Each field resolves independently: &amp;#039;&amp;#039;&amp;#039;pair override → current global field → native default&amp;#039;&amp;#039;&amp;#039;. A text override does not force visibility or suppress the numeric ID. An ID override does not hide the name or mute voice. Whole-label visibility &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt; hides text, ID and the speaking suffix; voice audio routing is unchanged.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- A sees B as a stranger; C knows B. B&amp;#039;s view of A is unchanged.&lt;br /&gt;
setPlayerNameplateTextFor(playerA, playerB, &amp;quot;Stranger&amp;quot;)&lt;br /&gt;
setPlayerNameplateTextFor(playerC, playerB, &amp;quot;Known friend&amp;quot;)&lt;br /&gt;
setPlayerNameplateIdVisibleFor(playerA, playerB, false)&lt;br /&gt;
&lt;br /&gt;
-- Reset only A&amp;#039;s text. A now uses B&amp;#039;s CURRENT global text/name,&lt;br /&gt;
-- while A&amp;#039;s explicit ID-hidden override remains.&lt;br /&gt;
resetPlayerNameplateTextFor(playerA, playerB)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Existing [[setPlayerNameplateText]], [[resetPlayerNameplateText]] and [[setPlayerNameplateVisible]] remain global fallback controls; they do not clear pair overrides. [[getPlayerNameplateText]] returns global custom text or &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt; when unset. [[isPlayerNameplateVisible]] reads the global visibility policy, not the observer-specific result. Global settings are not resource-owned and are not reverted automatically when the writing resource stops.&lt;br /&gt;
&lt;br /&gt;
== Reading explicit versus effective state ==&lt;br /&gt;
[[getPlayerNameplateTextFor]] returns &amp;#039;&amp;#039;&amp;#039;only an explicit pair text string&amp;#039;&amp;#039;&amp;#039;, otherwise &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;. It never returns inherited text. The two &amp;lt;code&amp;gt;is...For&amp;lt;/code&amp;gt; getters return the effective boolean after inheritance; &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt; can also mean an invalid pair.&lt;br /&gt;
&lt;br /&gt;
[[getPlayerNameplateStateFor]] returns this table, or &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt; for an invalid pair/unavailable service:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Field !! Meaning&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;text&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;visible&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;idVisible&amp;lt;/code&amp;gt; || Effective text string and boolean policies. Text excludes the system ID and speaking suffixes.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;textOverridden&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;visibilityOverridden&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;idVisibilityOverridden&amp;lt;/code&amp;gt; || Whether the corresponding explicit pair field exists.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;textOverride&amp;lt;/code&amp;gt; || Explicit string, or &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt; when absent.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;visibleOverride&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;idVisibleOverride&amp;lt;/code&amp;gt; || Explicit boolean, or &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt; when absent. Use the flag to distinguish absent from explicitly false.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
local state = getPlayerNameplateStateFor(observerId, targetId)&lt;br /&gt;
if state then&lt;br /&gt;
    outputDebugString(state.text)&lt;br /&gt;
    if state.visibilityOverridden and state.visibleOverride == false then&lt;br /&gt;
        outputDebugString(&amp;quot;Explicitly hidden for this observer&amp;quot;)&lt;br /&gt;
    end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The table is a &amp;#039;&amp;#039;&amp;#039;resource-global, read-only server view&amp;#039;&amp;#039;&amp;#039;: it includes fields owned by other resources. It is not an AOI test or client-render acknowledgement. Effective text may be non-empty while visibility is false. Distance, native presentation rules and asynchronous delivery still determine whether pixels are rendered.&lt;br /&gt;
&lt;br /&gt;
== Validation and exclusive resource ownership ==&lt;br /&gt;
* New boolean setters require real Lua booleans; &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt;, strings and &amp;lt;code&amp;gt;nil&amp;lt;/code&amp;gt; are not substitutes. IDs must be finite positive integers. Invalid argument counts return &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;.&lt;br /&gt;
* Text must be valid UTF-8, at most &amp;#039;&amp;#039;&amp;#039;48 Unicode code points and 96 bytes&amp;#039;&amp;#039;&amp;#039;. It is rejected, not truncated, on invalid UTF-8, controls, &amp;lt;code&amp;gt;&amp;amp;lt;&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;&amp;amp;gt;&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;&amp;amp;amp;&amp;lt;/code&amp;gt;, BOM, or the shared validator&amp;#039;s disallowed zero-width/bidirectional formatting characters. Empty text means reset.&lt;br /&gt;
* The resource that first writes a pair field owns that field exclusively. &amp;#039;&amp;#039;&amp;#039;Another resource cannot overwrite or reset it until the owner releases it.&amp;#039;&amp;#039;&amp;#039; Different fields of the same pair can have different owners.&lt;br /&gt;
* An identical owned write or reset of an absent field succeeds without a state change. A foreign write/reset fails. Always check the returned boolean; a failed mutation does not grant ownership.&lt;br /&gt;
* Reset removes only the selected owned field and follows the &amp;#039;&amp;#039;&amp;#039;current&amp;#039;&amp;#039;&amp;#039; fallback. It does not restore a captured old global value.&lt;br /&gt;
* The core does not impose an RP permission or acquaintance policy. Validate permissions and remote-event senders in your own trusted server resource.&lt;br /&gt;
&lt;br /&gt;
== Relevance, reconnect and resource stop ==&lt;br /&gt;
Accepted changes are sent reliably to the affected observer when relevant. Pair overrides remain stored outside synchronization range; the current resolved state is replayed on late join and AOI re-entry. Resource stop removes only that resource&amp;#039;s fields. Disconnect of either endpoint clears affected pairs, and session-generation checks prevent stale state from applying to a reused numeric player ID.&lt;br /&gt;
&lt;br /&gt;
These overrides are &amp;#039;&amp;#039;&amp;#039;runtime state, not persistent database relations&amp;#039;&amp;#039;&amp;#039;. After reconnect or resource restart the gamemode must restore its policy. Global nameplate state also ends when that player disconnects. A setter returning &amp;lt;code&amp;gt;true&amp;lt;/code&amp;gt; means server acceptance, not proof that another client has rendered the label.&lt;br /&gt;
&lt;br /&gt;
== Configurable bounds ==&lt;br /&gt;
Default server configuration:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;ini&amp;quot;&amp;gt;&lt;br /&gt;
[nameplates]&lt;br /&gt;
maxPairs=250000&lt;br /&gt;
maxPairsPerResource=250000&lt;br /&gt;
mutationsPerSecond=4096&lt;br /&gt;
mutationBurst=8192&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Capacity keys accept &amp;lt;code&amp;gt;1..1000000&amp;lt;/code&amp;gt;; mutation rate/burst accept &amp;lt;code&amp;gt;1..250000&amp;lt;/code&amp;gt;. A pair counts once globally and once per resource owning any of its fields. The per-resource token bucket charges changed, non-reset field writes. Identical writes, resets and cleanup do not consume tokens. A capacity/rate rejection returns &amp;lt;code&amp;gt;false&amp;lt;/code&amp;gt;; batch large restores over ticks and retry with bounded scheduling, not a tight loop.&lt;br /&gt;
&lt;br /&gt;
Defaults can hold all &amp;lt;code&amp;gt;500 × 499&amp;lt;/code&amp;gt; directed pairs. A sparse global-default-plus-known-exceptions design usually uses fewer entries and less work.&lt;br /&gt;
&lt;br /&gt;
== Building a persistent acquaintance system ==&lt;br /&gt;
The server developer supplies the database schema and adapter. Store directional relationships by stable &amp;#039;&amp;#039;&amp;#039;character IDs&amp;#039;&amp;#039;&amp;#039;, not temporary &amp;lt;code&amp;gt;playerId&amp;lt;/code&amp;gt;. Decide whether &amp;lt;code&amp;gt;A knows B&amp;lt;/code&amp;gt; implies a reciprocal relationship; the engine deliberately does not assume this.&lt;br /&gt;
&lt;br /&gt;
A possible policy:&lt;br /&gt;
# When a character becomes ready, establish its global label as &amp;lt;code&amp;gt;Stranger (&amp;amp;lt;characterId&amp;amp;gt;)&amp;lt;/code&amp;gt; and global numeric-ID policy as false. Choose one resource to own this global policy, and do so before exposing the character under your gamemode&amp;#039;s login/spawn flow.&lt;br /&gt;
# For each observer who knows that character, set a pair text override with the known name. Do not call [[setPlayerName]] to simulate a stranger: that changes the canonical name used by other systems.&lt;br /&gt;
# On new acquaintances or forgotten relations, update/reset only the relevant directional pair. Check returned booleans for resource conflicts or rate limits.&lt;br /&gt;
# On reconnect/resource restart, rebuild policy in bounded batches from your own database. Core pair cleanup does not revert global settings for you.&lt;br /&gt;
&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Guard asynchronous DB results against both endpoints changing.&amp;#039;&amp;#039;&amp;#039; Capture both session objects/generations, both stable character IDs, and your resource&amp;#039;s load epoch before starting a query. Before applying its result, verify every captured value is still current and both players are connected and character-ready. &amp;lt;code&amp;gt;isPlayerConnected&amp;lt;/code&amp;gt; alone cannot detect ID reuse or a character switch.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;lua&amp;quot;&amp;gt;&lt;br /&gt;
-- Integration sketch: sessions, loadEpoch and queryAcquaintance are YOUR&lt;br /&gt;
-- gamemode&amp;#039;s state/DB adapter, not extra G1R:MP API functions.&lt;br /&gt;
local observerSession = sessions[observerId]&lt;br /&gt;
local targetSession = sessions[targetId]&lt;br /&gt;
local requestEpoch = loadEpoch&lt;br /&gt;
local observerCharacter = observerSession.characterId&lt;br /&gt;
local targetCharacter = targetSession.characterId&lt;br /&gt;
&lt;br /&gt;
queryAcquaintance(observerCharacter, targetCharacter, function(knownName)&lt;br /&gt;
    if loadEpoch ~= requestEpoch&lt;br /&gt;
        or sessions[observerId] ~= observerSession&lt;br /&gt;
        or sessions[targetId] ~= targetSession&lt;br /&gt;
        or observerSession.characterId ~= observerCharacter&lt;br /&gt;
        or targetSession.characterId ~= targetCharacter&lt;br /&gt;
        or not observerSession.characterReady or not targetSession.characterReady&lt;br /&gt;
        or not isPlayerConnected(observerId) or not isPlayerConnected(targetId) then&lt;br /&gt;
        return&lt;br /&gt;
    end&lt;br /&gt;
    -- Adapter returns a validated name or false; handle DB failures separately.&lt;br /&gt;
    local accepted&lt;br /&gt;
    if type(knownName) == &amp;quot;string&amp;quot; then&lt;br /&gt;
        accepted = setPlayerNameplateTextFor(observerId, targetId, knownName)&lt;br /&gt;
    elseif knownName == false then&lt;br /&gt;
        accepted = resetPlayerNameplateTextFor(observerId, targetId)&lt;br /&gt;
    end&lt;br /&gt;
    if accepted == false then&lt;br /&gt;
        outputDebugString(&amp;quot;Acquaintance presentation rejected; check ownership or limits&amp;quot;)&lt;br /&gt;
    end&lt;br /&gt;
end)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Initialize and validate both session objects before entering this sketch; replace them on session/character changes and invalidate &amp;lt;code&amp;gt;loadEpoch&amp;lt;/code&amp;gt; when abandoning a restore batch.&lt;br /&gt;
&lt;br /&gt;
== Privacy boundary ==&lt;br /&gt;
&amp;#039;&amp;#039;&amp;#039;Hiding an overhead label is not network anonymity.&amp;#039;&amp;#039;&amp;#039; Canonical names still exist in identity packets, [[getPlayerName]], ordinary player-list and chat flows unless those systems independently become recipient-aware. This feature neither changes account credentials nor guarantees that a client cannot inspect canonical identity elsewhere. For consistent stranger RP, apply one server-side recipient-aware resolver to each relevant chat/roster/UI path as a separate gamemode concern.&lt;br /&gt;
&lt;br /&gt;
== Optional three-client test ==&lt;br /&gt;
The shipped &amp;lt;code&amp;gt;nameplate_test&amp;lt;/code&amp;gt; resource is &amp;#039;&amp;#039;&amp;#039;not autostarted&amp;#039;&amp;#039;&amp;#039;. Start it explicitly in the server console:&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
start nameplate_test&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The bundled GothicRP adapter requires an authenticated test operator with &amp;lt;code&amp;gt;admin.players&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;RP.Config.testTools.enabled == true&amp;lt;/code&amp;gt;. All participants must be logged in and fully spawned. An authenticated test owner can still stop the test after permissions/settings change. Other gamemodes need their own trusted server-resource adapter; do not expose the test event as remotely callable.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
/nametest start &amp;lt;A ID&amp;gt; &amp;lt;B ID&amp;gt; &amp;lt;C ID&amp;gt;&lt;br /&gt;
/nametest next&lt;br /&gt;
/nametest status&lt;br /&gt;
/nametest stop&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
Use three different connected players with no existing overrides on the six directed pairs. Only the starter controls the active sequence. English chat messages guide &amp;#039;&amp;#039;&amp;#039;10 steps&amp;#039;&amp;#039;&amp;#039;: directional text, ID hiding, complete-label hiding, visibility restore, acquaintance text, reverse direction, text reset, visibility/ID reset, AOI re-entry and final cleanup.&lt;br /&gt;
&lt;br /&gt;
Step 9 requires moving B outside the server synchronization range and back, then checking each client&amp;#039;s intended label without a stale/canonical-name flash. Optional disconnect or resource-stop checks terminate the sequence and clear test-owned fields. The test does not change globals, database records, equipment or player positions; global controls have separate C++ tests. Chat readback verifies server state, so &amp;#039;&amp;#039;&amp;#039;all three clients must still inspect actual visuals&amp;#039;&amp;#039;&amp;#039;. Use matching protocol-34 server/client builds.&lt;br /&gt;
&lt;br /&gt;
[[Category:Lua API]]&lt;br /&gt;
[[Category:Lua Examples]]&lt;br /&gt;
[[Category:Nameplate Functions]]&lt;/div&gt;</summary>
		<author><name>QCherry</name></author>
	</entry>
</feed>