⚓ Seaworth Games

client/adventureMap

Seven States, One Screen

AdventureMapInterface is the screen everything else in this doc's directory sits inside — the kingdom overview, the hero and town lists, the minimap, the end-turn button. Its own header comment calls it “a huge class,” and it earns that: one seven-value state enum decides what the whole screen is doing, and this is the exact object Widget Tree's human-shaped CPlayerInterface talks to every time the server tells it something changed.

The screen

class AdventureMapInterface : public CIntObject — another widget in the tree, holding four smaller ones.

It owns AdventureMapWidget (the side panel — buttons, resource bar, the lists), AdventureMapShortcuts (every hotkey the screen answers to), MapAudioPlayer, and TurnTimerWidget, and it's reachable from anywhere in the client through one global: extern std::shared_ptr<AdventureMapInterface> adventureInt. One Renderer, Many Contexts already met the widget this class delegates actual map drawing to — widget->getMapView() is the same MapView that doc traced end to end. This doc is the layer above it: the thing that decides what mode that view, and everything around it, should be in.

One switch, two systems

EAdventureState has seven values — NOT_INITIALIZED, HOTSEAT_WAIT, MAKING_TURN, AI_PLAYER_TURN, CASTING_SPELL, WORLD_VIEW, DISEMBARKING — and every transition flips it and reconfigures the map view in the same call.

AdventureMapInterface::openWorldView() setState(EAdventureState::WORLD_VIEW) shortcuts->setState() + adjustActiveness() + widget->updateActiveState() the side panel and every shortcut's enabled flag update here same function, next line widget->getMapView()->onViewWorldActivated(tileSize) MapViewController::activateWorldViewContext() this atlas's One Renderer, Many Contexts doc traced everything below this line from the other side — the swappable IMapRendererContext the renderer never branches on
Two systems change together from one call, but neither owns the other: the state enum is this screen's own idea of what mode it's in (which buttons work, which panel is active), and the view context is the renderer's idea of what to draw. enterCastingMode() is the same two-part shape for a different pair — it sets CASTING_SPELL and separately calls GAME->interface()->localState->setCurrentSpell(sp->id), writing into Widget Tree's PlayerLocalState — the screen's state and the client's local memory, updated side by side, never merged into one object.

Told, not asked

Nothing in this class polls. Every update it makes is a response to a named call from somewhere else.

Roughly a dozen public methods exist for exactly one caller each, and their own doc comments say so plainly: onHeroMovementStarted(), onHeroChanged(), onTownChanged(), onMapTilesChanged(), onPlayerTurnStarted(), onEnemyTurnStarted() — each one commented “Called by PlayerInterface when…”. That's Widget Tree's finding made concrete: CPlayerInterface is the human-shaped implementation of the interface the server calls into, and this class is what it forwards those calls to. The direction runs the other way too, and just as directly — One Renderer, Many Contexts's MapViewActions calls adventureInt->onTileLeftClicked(tile), onTileRightClicked(tile), and onTileHovered(tile) straight through the same global pointer, on every click and hover the map view forwards up. Neither side asks the other for its current state; each just tells the other what happened.

A table of shortcuts

AdventureMapShortcuts::getShortcuts() doesn't return a fixed list. It rebuilds one, every time, from the current state.

Each entry is a plain struct — { EShortcut shortcut, bool isEnabled, std::function<void()> callback } — and the table itself is a flat literal: { EShortcut::ADVENTURE_VIEW_WORLD, optionInMapView(), [this]{ worldViewScale1x(); } }, one line per hotkey. isEnabled is never a stored flag; it's the result of calling something like optionInWorldView() or optionHeroSelected() fresh, each time the table is asked for. A shortcut that only makes sense in World View simply reports itself disabled everywhere else — there's no separate code path that has to remember to check the screen's state before honoring a keypress, because the table regenerates against that state every time it's read.

One loop this atlas can now close: enterCastingMode()/performSpellcasting()/isValidAdventureSpellTarget() are the client-side other half of Spell System's finding that adventure-map spells are a hardcoded switch of seven C++ classes with no shared script layer. This is where that switch's input comes from — a player choosing a tile while CASTING_SPELL is active, validated client-side before the request pack for one of those seven ever gets built.