Skip to content

Discovery System #812

Description

@Jattartajjar

feat: Cyclopedia Map - Discovery System ("Measuring Tibia") + Area Donations / Improved Respawn Rate (client 15.25)

Full server-side implementation of the Cyclopedia Map exploration features introduced by CipSoft: the Discovery System (per-subarea viewpoint exploration with permanent unlocks and rewards) and Area Donations unlocking a temporary Improved Respawn Rate per main area. All wire formats were reverse-engineered from the 15.25 client binary; all geometry comes 1:1 from the client's own map assets.

Ships as two patches with disjoint file sets (apply in any order, both required for the full feature):

Patch Scope
0001-feat-discovery-system-cyclopedia-map-15.25.patch Map protocol layer (S2C 0xDD, C2S 0x92/0xDB), discovery logic, generated geometry, god tools
0001-feat-improved-respawn-area-donations-15.25.patch Respawn zone registry + spawn engine, server-save boost cycle, spawn timing fixes, shutdown save fix

Part 1 - Discovery System

Mechanics

  • Automatic exploration (matches the current live behavior, no "Start Discovering" button): entering a registered subarea activates a session and rolls 7 hidden viewpoints inside the subarea's real footprint (walkable tiles only, never inside the player's current view, minimum spacing).
  • Walking within view range (+-7/+-5, same floor) of a viewpoint discovers it (effect + message + map checkmark). 7/7 completes the subarea permanently (player KV).
  • Viewpoints persist across sessions; completed subareas greet with "fully explored".
  • Rewards: first fully explored main area grants the Discoverer outfit + achievement "Widely Travelled"; 50% of main areas grants addon 1 + title "Dedicated Entrepreneur"; 100% grants addon 2 + achievement "Measuring the World" + title "Globetrotter". Titles run through PlayerTitle::checkMap reading discovery/map-percent KV.

Geometry - generated from the client's own assets

The client renders discovery regions from per-subarea bitmap masks, so the server uses the exact same shapes:

  • map.dat (protobuf tibia.protobuf.map.Map) provides all areas/subareas (ids, names, kinds, label pins) and per-subarea mask records (subarea-XXXX-<sha>.bmp.lzma, CIP LZMA container, 32bpp BMP, alpha = painted, 1 px = 1 tile).
  • Generator output data/scripts/discovery_geometry.lua:
    • 207 surface subareas, pixel-exact (1.89M tiles, 41,680 rects, verified mutually disjoint), across 22+ main areas,
    • 69 underground subareas approximated from NPC-landmark clusters (median center, outlier rejection, padding, floors from landmark z +-1),
    • rects are packed as strings and parsed to flat arrays at load (Lua has a 65,536 constants-per-chunk limit),
    • remaining deep dungeons have no client-side data and are intentionally not discoverable (consistent with the original client, which cannot render discovery without a mask). They do not count toward any completion percentage.
  • Runtime lookup: bbox prefilter + floor set + flat rect scan; viewpoint rolls use a cumulative-area binary search. Detection floors are separate from viewpoint-roll floors (rooftops count as inside, viewpoints only roll on reachable levels).

Protocol (RE'd from the 15.25 client)

S2C 0xDD frame: [0xDD][u8 subtype][body]:

Sub Name Body
1 DiscoveryData u16 nMain {u16 areaId, u8 status, u8 progress} + u16 nDiscovered {u16 subId} + u16 nDiscoverable {u16 subId}
5 SetDiscoveryArea u16 subId, bool active, u8 poiTarget, u8 count, {pos(5B), u8 state} x count
9 Donations u64 goal, u8 count, {u16 areaId, bool improvedRespawnActive, u64 donated} x count
10 SetCurrentArea u16 subId (0 = none)
11 SetExploringArea u16 subId (0 = none); the client keeps currentPlayerSubAreaID and currentExploringSubAreaID separately and the viewpoint overlay needs both

C2S 0x92 (protobuf varints: request / viewport / legacy start) is answered with the cached DiscoveryData.

God tool

/disco - refresh map data; status (per-main summary), where (position -> matching footprint, flags approximate boxes), done [subId] (restore/mark complete), reset, tp, poi (dump rolled viewpoints to the server log), plus the donation commands listed below.


Part 2 - Area Donations / Improved Respawn Rate

Mechanics

  1. Players donate gold to a main area through the map UI (or /disco donate <areaId> <gold>); money is taken from inventory + bank, the resource balance is re-sent so the gold shown in the dialog updates instantly.
  2. Pool reaches the goal (default 10,000,000 gold, tunable) -> boost becomes SCHEDULED (broadcast message).
  3. At the daily server save the scheduled boost activates: goal amount is consumed (excess carries over), the boost runs for 24h (until the next save) and then expires by timestamp. A 60s watcher re-applies zones on any active-set change, so expiry also works across long uptimes.
  4. While active, every monster spawn inside the area's subarea footprints (all floors) respawns twice as fast, and the area label on the map shows the improved-respawn indicator (the bool in sub 9).

State lives in global KV (discovery-donations scope), so it survives restarts. Test helpers: /disco donations (pools + SCHEDULED / ACTIVE until states), /disco ss (simulate the server-save boundary), /disco resetdonations.

C2S 0xDB - the 15.25 map channel (raw, not protobuf)

[u8 action][raw LE fields]:

Action Length Body Handling
1 9 u16 areaId, u32 goldAmount, u8 pad Donate
2 5 u32 areaId (currently viewed) Must stay unanswered - replying makes the client re-request in a loop until the packet-rate limiter disconnects it

Spawn engine correctness fixes (required for exact halving, improve vanilla too)

  • Stamp lastSpawn at the actual death/removal moment. Upstream only stamped it when the periodic check's cleanup() first noticed the corpse, so the respawn wait silently started up to a full check tick late; with AoE double-kills the second corpse regularly lost a whole interval (measured: 90s spawn appearing after 120s+).
  • Aim the check timer at the earliest pending respawn (getNextCheckDelay) instead of the fixed gcd cadence, so halved (and vanilla) waits land exactly and are never quantized up.
  • Guard the 4.2s non-blockable staging window against duplicate staged spawn chains (effect spam) now that checks can land inside it.
  • Verified with server-side timeline logging: boosted 90s spawns consistently return in 45.0s + 4.2s staging, measured from the exact death stamp, including simultaneous AoE kills.

Shutdown save fix (SIGBREAK race)

Upstream queued the shutdown handler on the dispatcher and immediately tore the thread pool down, so setGameState(SHUTDOWN) (player kicks + saveAll) never actually ran on Ctrl+Break stops - shutdown logs had no "Saving server..." line and global KV (donation pools) silently vanished on restart. The signal thread now waits (up to 90s) for the handler to finish before the pool goes down.


Tunables

Constant Default Where
DONATION_GOAL 10,000,000 gold data/scripts/discovery_system.lua (published to KV for the C++ handler)
BOOST_DURATION 24h discovery_system.lua
respawn factor 2x (interval / 2) SpawnMonster::checkSpawnMonster / getNextCheckDelay
POI_TARGET 7 viewpoints discovery_system.lua
GEOMETRY_VERSION bump to wipe stale rolled viewpoints on login (completed subareas are kept) discovery_system.lua

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions