-
Notifications
You must be signed in to change notification settings - Fork 0
Town Services
Navigation: Home - Game systems - Town Services
flowchart TD
tile["Tile trigger<br/>collision 0x80"] --> scan["Scanner 0x1754A"]
scan --> vm["Script VM 0x172CA"]
vm --> op0e["OP_0E selector byte"]
op0e --> svc{"Service handler"}
svc --> pub["Pub 0x1A132"]
svc --> inn["Inn -$7CD4"]
svc --> temp["Temple 0x1D208"]
svc --> guild["Mage guild"]
svc --> train["Training 0x9BCA"]
pub --> str["str.dat UI strings"]
inn --> exe["exe-embedded ASCII"]
temp --> spells["3 cleric spells/town"]
guild --> sorc["4 sorcerer spells/town"]
Reverse-engineered entry flow, ASM handlers, strings, and per-town tables for
the five surface towns (map screens 0–4). Cross-references:
30-event-to-string-path.md (how event.dat vs
str.dat vs exe text connect), 07-event-script-opcodes.md
(OP_0E, OP_0B, OP_24), 06-roster-format.md,
11-str-decoded.txt, 14-game-state-struct.md,
29-embedded-exe-strings.md.
[TOC]
Data files: event.dat is in the repo root; full decode:
EXTRACTED/event_decode.txt,
EXTRACTED/event_location_inventory.json.
str.dat may still be absent — UI text from
11-str-decoded.txt. Regenerate:
python tools/decode_event.py event.dat > EXTRACTED/event_decode.txt
python tools/event_location_inventory.py event.dat EXTRACTED/event_location_inventory.json
python tools/mm2_codec.py str-decode str.dat EXTRACTED/docs/11-str-decoded.txt- Scanner
0x1754Areads(y,x)triplets; event id selects a script segment in the location’sevent.datrecord. - Typical service tile chain:
-
OP_0B— service title window (string index + mode byte). - Optional y/n text (
OP_01–OP_03,OP_09/OP_0A). -
OP_0E— one selector byte → engine handler (below). -
OP_0F— end script.
-
- Current map index is in
A4-$79F2(screen_mode_id, values 0–4 for the five towns). Shop/temple/training handlers index per-town tables with this byte (not the roster “home town” byte).
Chained subtraction on the selector byte:
| Selector | Name (decoder) | Handler | Role |
|---|---|---|---|
0x01 |
open_tavern_food |
0x1A132 |
Pub / food / drinks / rumors |
0x02 |
open_inn_lodging |
-$7CD4 |
Inn rest / roster dismiss |
0x03 |
open_temple |
0x1D208 |
Temple services + donations |
0x04 |
open_training |
-$7D16 |
Training (HP / stats / skills) |
0x05 |
open_mages_guild |
-$7D10 |
Mage guild spell shop |
0x06 |
open_blacksmith_shop |
0x1C54A |
Blacksmith (weapons/armor/specials) |
0x07 |
open_general_store |
-$7DB8 |
General store |
0x08 |
open_special_shop |
-$7DBE |
Special shop |
0x0A |
goblet_quest |
-$7DAC |
Goblet / farthing side quest |
0x0D |
enroll_mages_guild |
-$7DA0 (+ $1A1F8 on success) |
Guild membership |
0x64, 0x7E–0x83, 0xC9–0xCF, 0xE2, 0xFD
|
specials | various | Portals / quests |
| default | (category shop) |
0x15EDC → -$7DFA
|
Selector binned to category 0x3C–0x46
|
Default-path binning (from 0x15EDC, range helper -$7E9C):
| Selector range | Category | Index adjust |
|---|---|---|
0x09–0x10
|
0x3C |
-8 |
0x11–0x37
|
0x3D |
-0x10 |
0x38–0x4B
|
0x3E |
-0x37 |
0x4C–0x54
|
0x3F |
-0x4B |
0x56–0x5B
|
0x40 |
-0x55 |
0x5C–0x5E
|
0x41 |
-0x5B |
0x65–0x69
|
0x42 |
-0x64 |
0x6A–0x7C
|
0x43 |
-0x69 |
0x97–0x98
|
0x44 |
-0x96 |
0xE3–0xF3
|
0x45 |
-0xE2 |
0xF4–0xFB
|
0x46 |
-0xF3 |
During default dispatch the engine temporarily overwrites A4-$79F2 with
the category byte, calls -$7DFA, then restores the real map index. Explicit
0x06 blacksmith uses the real map index throughout.
Arena / Monster Bowl / guild spell counters use named selectors in the
default ranges (e.g. Middlegate 0x10, Sandsobar 0x49–0x54, Atlantium
Olympic Trial tiles 0x12–0x25).
-
OP_24—u16gold check →A4-$7951(cond_flag). Observed amounts:10,25,50,200,500,1000,2000,5000(training, travel, Olympic skills, etc.). - Training calendar mode
1/2(stat vs HP) is resolved @0x9B48before0x9BCA. Stat path calls-$7EB4with mode1@0x9BC0; HP path enters0x9BCAwhen mode2.
FAQ §3-6 (lines 1171-1175). Closed-form expressions; exact multiply/index sequence in
0x9B48/0x9BCA/0x1D208not yet ASM-confirmed.
Training = current_level × town_index × 50 gp
Healing = current_level × town_index × 10 gp
(dead: × 100 gp total = × 10 gp × 10)
(eradicated: × 1000 gp total = × 10 gp × 100)
Town index: Middlegate=1, Tundara=2, Sandsobar=2, Vulcania=4, Atlantium=5.
See 34-commerce-formulas.md for town-index table,
day-bonus cycle, portals, bar food/drinks, and Fly landing coords.
See 34-commerce-formulas.md § Portal connections.
Portal selector bytes (0x64, 0x7E–0x83) are handled by the OP_0E
default-range path (§1.2 above, entry 0x64, 0x7E–0x83).
Map index = location id: 0 Middlegate, 1 Atlantium, 2 Tundara, 3 Vulcania, 4 Sandsobar.
| Town | Pub 0x01
|
Inn 0x02
|
Temple 0x03
|
Train 0x04
|
Guild 0x05
|
Smith 0x06
|
Store 0x07
|
Notes |
|---|---|---|---|---|---|---|---|---|
| Middlegate | (7,10) | (14,7) | (3,7) | (6,4) | (8,13) | (7,7) | (15,0) | enroll @ (15,5) 0x0D; goblet quest @ (2,10) 0x0A (find goblet in cavern loc 17 (7,0)) |
| Atlantium | (13,6) | (10,12) | (15,15) | (4,5) | (4,10) | (7,4) | — | Olympic/skills on separate tiles (0x12–0x25) |
| Tundara | (13,11)* | —* | (11,7)* | (9,7)* | —* | (6,11)* | — | *OP_0B titles only in cached decode — no OP_0E hits (see §8) |
| Vulcania | (3,4) | (5,11) | (0,7) | (2,3) | (0,5) | (8,11) | (9,3)? | many arena selectors 0x3E–0x48
|
| Sandsobar | (7,3) | (7,6) | (10,3) | (11,4) | (2,6) | (11,6) | (1,10) 0x08
|
arena 0x49–0x54
|
| Town | Building | Tile | Title string (from cached decode) |
|---|---|---|---|
| Middlegate | Inn sign | (4,5) | Middlegate Inn |
| Smith sign | (6,5) | S.J. Blacksmith | |
| Pub sign | (6,7) | Slaughtered Lamb | |
| Service | (3,7) | Gateway Temple | |
| Service | (6,4) | Turkov's Training | |
| Atlantium | Pub | (13,6) | Boar's Tongue Tavern |
| Smith | (7,4) | Drewnhald Ironworks | |
| Training | (4,5) | Beautify Atlantium | |
| Temple | (15,15) | Island Training (title quirk) | |
| Tundara | Pub | (13,11) | Lucky Dog Saloon |
| Training | (9,7) | Enhancement Center | |
| Temple | (11,7) | White Dove Temple | |
| Smith | (6,11) | Thundrax Weaponry | |
| Vulcania | Pub | (3,4) | Belinthra's Bar |
| Smith | (8,11) | Bestway Blacksmith | |
| Training | (2,3) | Vulcanian Transport (title quirk) | |
| Sandsobar | Pub | (7,3) | Red Lantern Tavern |
| Smith | (11,6) | Big Al's Accessories | |
| Temple | (10,3) | Temple Benedictus | |
| Training | (11,4) | Sheik Training Arena |
Door labels (OP_04) match the same names where present.
From 11-str-decoded.txt — global strings used by engine
handlers (not per-location event.dat banks):
- Menu: A) feeding frenzy B) drink C) specialties D) tip E) listen for rumors.
- Drink lines A–F (Orc Beer … Mystic Brew).
- Food lines A–C per town menu block (three columns of hors d’oeuvres / soup / steak variants).
- Pub NPC intro flavor (sequential block ~lines 89–108):
- Amber — Middlegate Slaughtered Lamb
- Rowena — Atlantium Boar's Tongue
- Belinthra — Vulcania
- Gabrielle — Sandsobar
- Lucindra — (brawling bar; likely Sandsobar alternate / special)
- Rumor hint embedded in str pool: "Donate at all / temples" (~line 171) — pub rumor E), not hard-coded quest text.
- NPC intros (~lines 255–273): Svendegard, Morgan Drewnhald, smelter smith, friendly smith, tough old smith — map to towns by matching Drewnhald→Atlantium; align others with shop names above when play-testing.
- Menu: A) Weapons B) Today's Specials C) Armor D) Misc E) Sell F) Identify.
- Five priest intro variants (~lines 343–357).
- Menu: A) Restore Cond B) Restore Algn C) Donations D–F) cleric spell purchase (no sorcerer stock).
- Donation feedback: "Your generosity is greatly appreciated", "Today you are blessed!".
- Guild hall intros (~lines 328–342).
-
Sorry, you must be a member of this guild to purchase spells.(~line 369).
- Training payment messages via shared "Not enough gold" / stat prompts.
- Inn:
Crispy Critters Innenrollment string @0x1A2B8(post-enroll UI).
Uses A4-$79F2 (map 0–4) directly. Two layers:
-
Static item IDs — builder
0x1C00Ewalkstown × 6 + slotinto data-hunk tables (category jump @0x1C09E):Cat Menu Item IDs (A4−) Meta (A4−) 1 Weapons $68EE$68D02 Today's Specials $683A$683A3 Armor $68B2$68944 Misc $6876$68586 slots per town per category — full matrices in
EXTRACTED/shop_tables.json→static_by_town.blacksmith. -
RNG str captions only — open handler
0x1C54Afills display pointer runtime tables via-$7DE2:Table (A4−) Layout Purpose $5ACE10 × pointer Weapon caption strings $5AA65 × 4 × pointer Category headers (Weapons / Specials / …) $5A566 × pointer Specials wording (item ids still from $683A)
Today's Specials item pick (0x1C146, category 2): game date A4-$79B6
mod 0x1E indexes static bytes A4-$681C / $6816 — not a random item
pool. Discount/meta bytes copied in category 4 path.
Shop loop (0x1C714+): menu A–F → 0x1C25A category id → 0x1C00E
inventory builder → buy/sell/identify (-$7DDC, -$7B78).
Selectors 0x09–0xFB map to category 0x3C–0x46, not the
blacksmith handler. Same -$7DFA shell as other category shops:
- Menu driver
-$7F68: modes0x31= deduct gold,0x32= deduct gems,0x33= HP/food-style deduction (0x7CB0/0x7D3E). - Used for general store, special shop, arena prizes, Vulcania counter tiles, etc.
0x06 vs default: only 0x06 enters 0x1C54A (full smith UI + 6
specials). Default bins share the generic shop shell with different underlying
item pools keyed by adjusted selector index + category byte.
Builds per-town str pointer tables at A4-$59EE, -$599E, -$5986,
-$594E**, **-$58AE**, **-$580E**, **-$57F6** via **-$7DE2` RNG at open
(caption text only).
Cleric spell stock only — temples do not sell sorcerer spells. The sorcerer
price helper 0x1D97A (reads A4-$66E2) is called from the mage guild
path only (0x1E62E, cmpi #4 @ 0x1E64A). Temple purchase loop calls
0x1DAC6 only (0x1DEFC, cmpi #3 @ 0x1DF1A).
| Table (A4−) | Layout | Purpose |
|---|---|---|
$66F6 |
5×4 bytes | Cleric spell id bytes — 3 sold @ 0x1DF1A (menu keys D–F, handler 0x1DAC6) |
$670A |
5×4 bytes | Internal menu key bytes (slot 3 usually 0x80 pad) |
$6738 |
6× BE u16 | Global gold-cost slots for cleric purchase (slot×2 @ 0x1CB48) |
Town index A4-$79F2 (0–4). Fourth byte per town is padding; only slots 0–2
are built into the purchase UI.
Cleric id decode: flat_index = raw_byte + CLERIC_FLAT_ADJUST[town][slot] (see
tools/dump_shop_tables.py). Output: shop_tables.json →
temple_spells.cleric_spells_by_town.
| Town | D (slot 0) | E (slot 1) | F (slot 2) |
|---|---|---|---|
| Middlegate | 1-1 Apparition | 1-2 Awaken | 1-6 Power Cure |
| Atlantium | 8-3 Mass Distortion | 9-3 Resurrection | 9-4 Uncurse Item |
| Tundara | 3-1 Cold Ray | 3-5 Lasting Light | 4-4 Restore Alignment |
| Vulcania | 4-6 Holy Bonus | 5-5 Remove Condition | 7-2 Fiery Flail |
| Sandsobar | 2-2 Heroism | 2-5 Protection From Elements | 2-7 Weaken |
(level-number = manual cleric list; flat_index = spells.dat record 48..95.)
Full acquisition index: 31-spell-sources.md.
Donation cost table A4-$6742 (BE u16 per town, index A4-$79F2
0–4). Debited at 0x1CA3A → 0x1C9C0 (subtract roster.$66
gold). Not loaded from data-hunk 0x25F72 — that u16 cluster has no
read/copy to -$6742 in ASM.
Tracking byte: A4-$799E (global quest bitfield).
On successful donation (0x1D796, after 0x1D90C gold check passes):
bit_mask = A4-$66B1[ A4-$79F2 ] ; u8 per town 0..4
A4-$799E |= bit_mask
if A4-$799E == 0x1F: ; all 5 temple bits
trigger reward sequence (sets A4-$794C, A4-$3F1C, clears A4-$799E)
increment stat A4-$5770
RNG: engine query -$7BB4(100,1); if result > 0x5A → "Today you are blessed!" buff path
A4-$66B1: 5-byte table in the data hunk (same bytes as master copy @
0x2637C first five). Donation GP and quest bit are separate from
cleric spell purchase (spell ids @ A4-$66F6, costs @ A4-$6738
/ menu 0x1D872).
Donation gold (A4-$6742, BE u16, town 0→4):
| Town | GP |
|---|---|
| Middlegate | 20 |
| Atlantium | 250 |
| Tundara | 40 |
| Vulcania | 120 |
| Sandsobar | 40 |
Quest bit masks (A4-$66B1, OR into -$799E):
| Town | Bit |
|---|---|
| Middlegate | 1 |
| Atlantium | 2 |
| Tundara | 4 |
| Vulcania | 8 |
| Sandsobar | 16 |
Temple menu shows donation amount from A4-$6742 @ 0x1D556 (display
only; debit uses same table @ 0x1CA3A).
Quest item: OP_0E 0x0A (goblet_quest, -$7DAC) at Middlegate
(2,1). Cached scripts show gate text "Fool, you have no farthing to flick!"
on related tiles — Olympic/farthing item gate tied to OP_28 / inventory
(not fully traced here without live event.dat).
Pub rumor: str "Donate at all temples" — option E) in tavern; random rumor pool (below), not a dedicated quest opcode.
| Mechanism | Field | Rule |
|---|---|---|
| Hireling flag | roster $0B bit 7 (0x80) |
Set when hiring (0x216E6); clear for normal party members. Murray’s Cave uses OP_15 op=0x01 val=0x80 → "No hirelings allowed!"
|
| Home town | roster $0B & 0x7F
|
Values 1–5 (Middlegate…Tundara) for inn storage / guild membership |
| Dead / gone | roster $26 ≥ 0x81
|
OP_26 party pick skips (0x16C42) |
| Class / attr gate | OP_2D |
Checks $0C/$0E/$0F with arg bitmask (class/race/sex), not hireling bit |
Temple member selection uses the same party iterator as other services (-$7F20
slot lookup); hirelings with $0B.7 still appear unless a script explicitly
filters via OP_2D / OP_15. Guild spell purchase checks guild
membership via roster $0B low 7 bits vs current town (see §6).
| Step | Selector | Handler | Effect |
|---|---|---|---|
| Open spell shop | 0x05 |
-$7D10 → 0x1E3E6
|
Four sorcerer spells per town; membership required |
| Enroll | 0x0D |
-$7DA0 → 0x1A1B2 / 0x1A1F8
|
y/n; on success sets roster guild-home byte |
An earlier pass mis-attributed the alternate stock builders at 0x1EACC /
0x1EB2E (tables A4-$6692 / $669E, 3 menu slots, keys 9 / : /
;) to retail OP_0E 0x05. That produced the same three spellbook
indices everywhere (decoded as Implosion / Light / Silence) with only per-town
gold varying — wrong.
| Wrong (old) | Correct (ASM + hunk) |
|---|---|
Spell ids @ A4-$669E (3× BE u16) global |
A4-$66E2 — 5×4 bytes, sorcerer flat 0..47, per town
|
Keys 9 / : / ;
|
Keys A / B / C / D (sub #$41 @ 0x1E864) |
| 3 spells |
4 spells (cmpi #4 @ 0x1E64A) |
| Same spells all towns | Different four spells per town (school progression by town tier) |
Machine-readable stock: shop_tables.json →
static_by_town.mage_guild_spells (regenerate:
python tools/dump_shop_tables.py).
After -$7FBC y/n (0xFFFF mode @ 0x1A1A6), success path 0x1A1B2
loops active party slots:
- Index:
A4-$796A[slot]×$82→ roster baseA4-$2A33 - Write:
roster.$0B ← A4-$79F2 + 1(1=Middlegate … 5=Sandsobar) @0x1A1CE - Then
0x1A1F8(guild UI cleanup; restores map index)
Purchase gate @ 0x1E410: roster $79 (class flags) AND A4-$66A9[town]
must be non-zero — else "Sorry, you must be a member of this guild…" (str.dat
~369).
Flow: 0x160C2 → -$7D10 → 0x1E3E6 → 0x1E8B0 (RNG guild
name pointers from A4-$5732…$5714, -$7DE2) → stock build
0x1E618–0x1E650 calling 0x1D97A per slot.
| Data | Layout | Use |
|---|---|---|
A4-$66E2 |
5×4 u8 | Sorcerer spells.dat flat index (0..47) per town×slot |
A4-$6698 |
5×4 BE u16 | Per-town per-slot price base |
A4-$6686 |
4 BE u16 | Slot addends [188, 123, 69, 202]
|
A4-$66CE |
5×4 u8 | Cost-encoding flags for 0x1D97A price helper |
Gold: 6698[town×4 + slot] + 6686[slot]. Grant: 0x1D872 sets
spellbook bit from the 66E2 byte (not cleric 66F6). No cleric spells
at guild.
str.dat hall intros (11-str-decoded.txt ~328–342), one per town tone:
| Town | Intro flavour |
|---|---|
| Middlegate | Archmage |
| Atlantium | Cabalist |
| Tundara | Mystics by the fire |
| Vulcania | Magicians |
| Sandsobar | Sorcerers / phials |
| Town | A | B | C | D |
|---|---|---|---|---|
| Middlegate | S1/1 Awaken (196) | S1/3 Energy Blast (166) | S1/7 Sleep (142) | S2/3 Identify Monster (251) |
| Atlantium | S8/2 Mega Volts (243) | S8/3 Meteor Shower (182) | S9/1 Implosion (77) | S9/2 Inferno (218) |
| Tundara | S4/2 Feeble Mind (252) | S4/3 Fire Ball (311) | S5/1 Disrupt (192) | S5/3 Sand Storm (271) |
| Vulcania | S6/1 Disintegration (390) | S6/3 Fantastic Freeze (322) | S6/5 Super Shock (221) | S7/2 Duplication (202) |
| Sandsobar | S2/7 Protection from Magic (188) | S3/1 Acid Stream (126) | S3/4 Lightning Bolt (73) | S4/1 Cold Beam (210) |
Spell acquisition index: 31-spell-sources.md (regenerate
guild tables: python tools/build_spell_sources_doc.py).
Other engine paths still reference A4-$6692 / $6698 / $669E with slot-only
indexing and/or slot×3+9 keys (0x1EACC, 0x1EB2E, 0x1EA66, …).
Do not use these for per-town guild shop lists.
These are OP_0E default-path selectors (category 0x3D, index = selector −
0x10 → 13..21), used on Olympic Trial / skill tiles in Atlantium — not
the mage-guild spell shop (0x05). They route through 0x15EDC → -$7DFA
with a temporary category byte in A4-$79F2, separate from 0x1E3E6.
Handler 0x1A132 → rumor branch uses:
-
A4-$119A(-$EE66): bytecode stream (opcodes + string indices), walked by0x97FC–0x9932. -
A4-$1110: rumor cursor;A4-$71D6: secondary cursor;A4-$71DB: last opcode; compared againstA4-$1116(day/era gate). - Random
-$7BB4(1, n)for which rumor variant fires.
Town binding: stream is not indexed by map directly in the walker; town-
specific rumors are authored in the $119A script for each pub event
(different event segments loc 0–4). Pub NPC name strings (Amber, Rowena, …)
are fixed in str.dat; E) pulls from the shared rumor table with
RNG + day gates.
Embedded quest hints in rumor str pool (examples from 11-str-decoded.txt):
Donate at all temples, Meal B, then C1 2,10, Hirelings at 15,10, etc.
-
Blacksmith menu B) →
0x1C25A(1..6)→ reads pre-rolledA4-$5A56entries (6 per town, built at shop open from-$7DE2RNG). -
General/special shops (
0x07/0x08/default range): inventory from category-specific tables inside-$7DFA; refreshed on eachOP_0Eentry (no separate save flag found in ASM skim). -
Sandsobar drunk guild recruit (ev 26):
OP_24200gp — separate from smith specials.
Data hunk extract: EXTRACTED/ghidra/mm2_data_00.bin (see
python tools/extract_segments.py mm2 -o EXTRACTED/ghidra). Per-town item id
lists for smith specials / pub food are still RNG + str pointers at
shop-open — not static bytes in the data hunk (structure above is ASM-confirmed).
Entry: OP_0E 0x04 → -$7D16 (0x8050 menu). Training mode
(stat vs HP) comes from the calendar table A4-$55BA, indexed by month/day
@ 0x9B48 → -$2(a5): 1 = stat path, 2 = HP path @
0x9BCA. Outdoor/special gate @ 0x9B80.
Shop menu (-$7D16): -$7F68 keys 0x31 / 0x32 only —
0x31 → stat gold path (0x7F68), 0x32 → redistribute current HP
(0x7FF8, sums party $5c). This is separate from temple/inn -$7EB4
menus that also use 0x31–0x33.
A4-$79F2 (map screen 0–4) is not the multiplier used for training
cost or HP-per-level. That byte follows location order in the game data:
A4-$79F2 |
Town |
|---|---|
| 0 | Middlegate |
| 1 | Atlantium |
| 2 | Tundara |
| 3 | Vulcania |
| 4 | Sandsobar |
Training toughness (weakest → strongest), from
Might and Magic FAQ.txt §3-6: Middlegate →
Sandsobar → Tundara → Vulcania → Atlantium. Uses a separate training town
index (FAQ: 1, 2, 2, 4, 5 — no index 3):
| Training index | Town(s) | Notes |
|---|---|---|
| 1 | Middlegate | Cheapest; least HP per level |
| 2 | Sandsobar, Tundara | Same multiplier in FAQ |
| 4 | Vulcania | |
| 5 | Atlantium | ~2× Middlegate HP per level (FAQ) |
Map index → training index: [1, 5, 2, 4, 2] (map 0..4 as above).
Training gold (FAQ): current_level × training_town_index × 50 gp.
Stat training (0x1C898): A4-$6720 / A4-$671A indexed by map
A4-$79F2, not training index. Rolled byte += addend; dual compare @
0x1C8A8/0x1C8BA; stat id 0–6 → roster $6B–$72 @
0x1C7F0.
| Town (map index) | Stat + | Cap |
|---|---|---|
| Middlegate (0) | 5 | 100 |
| Atlantium (1) | 20 | 100 |
| Tundara (2) | 10 | 100 |
| Vulcania (3) | 10 | 100 |
| Sandsobar (4) | 3 | 50 |
0x9BCA does not read A4-$6714 (temple donation ×100 @
0x1DC26 / 0x1DC84).
base = roster[slot].$6B
if party_count > 1: base += one other roster[slot].$6B
RNG (0x6D, 0xA) -> divu #10 -> add to base
if base >= byte_at(A4-$5608): hp_gain_count += 1 ; cmp.b @ 0x9C52
second RNG @ 0x9C54 -> -$7D22 / -$7D28 -> roster.$25 / $5c
Per-level HP also depends on class, endurance, and training town index when leveling (FAQ §2-5, §4-2: ~6 HP/level lost by deferring Atlantium).
7 × (u8 cost, u8 gain, u16 pad) — retail 100 gp; gains 50…116.
First five gains match difficulty M, S, T, V, A (not map order). Runtime copy
A4-$56E. Not A4-$6720.
| Tier (M→S→T→V→A + extras) | Cost | Gain |
|---|---|---|
| 0–4 towns | 100 | 50, 55, 60, 74, 86 |
| 5–6 extended | 100 | 100, 116 |
Tiles (FAQ §8): Middlegate (9,7), Sandsobar (2,7), Tundara (11,7),
Vulcania (5,3), Atlantium (9,4). OP_24 gates in event.dat (typical
200 gp).
| Asset | Location |
|---|---|
| Event opcodes + selectors |
tools/decode_event.py (OPCODES, SELECTOR_NAMES) |
| C event ops mirror | editor/src/core/EventOps.h |
| Roster layout |
editor/src/core/RosterFile, EXTRACTED/docs/06-roster-format.md
|
| Game-state offsets |
tools/mm2_gamestate.py, EXTRACTED/decomp/mm2_gamestate.h
|
| str codec | tools/mm2_codec.py |
| Items |
editor/src/core/ItemsFile, tools/decode_items.py
|
| Spells |
editor/src/core/SpellsFile, tools/decode_spells.py
|
| Shop table dump | tools/dump_shop_tables.py → EXTRACTED/shop_tables.json |
| Topic | Status |
|---|---|
OP_0E selector → handler map |
Complete (ASM) |
| Per-town service tiles | Complete for loc 0,1,3,4 from cached decode |
Tundara OP_0E wiring |
Missing in cached decode — titles only; needs event.dat
|
str.dat shop UI strings |
Complete (11-str-decoded.txt) |
| Blacksmith item ids per town |
Complete — static 5×6 matrices @ $68EE…$6876
|
| Temple donation gold |
Complete — A4-$6742 BE u16 ×5 (not 0x25F72) |
| Temple donation bitfield |
Complete — A4-$66B1 → -$799E, 0x1F reward |
| Temple spell id bytes |
Complete — $66F6 cleric ×3/town only (no sorcerer @ temple) |
| Farthing quest item + flick |
Partial — gate strings + 0x0A selector; Gold Goblet = item 224
|
| Guild membership byte |
Complete (roster $0B, enroll $1A1CE) |
| Guild spell tables |
Complete — $66E2 ids 5×4, $6698 costs 5×4, $6686 addends ×4 |
| Pub rumor mechanism |
Complete (stream $119A + RNG) |
| Pub food costs + menu text |
Complete — $6760 BE u16 + str block per town |
| Pub meal item ids |
N/A — roster $78 encoding, not items.dat
|
| Rumor index → town | Partial (NPC names fixed; rumor ids per loc in event segments) |
| Training cost / town index | Complete (FAQ + ASM stat path); map ≠ training index |
Training HP path @ 0x9BCA
|
Partial — might-sum + RNG; no 6714; town index in FAQ only |
| Training stat tables |
Complete — A4-$6720 / A4-$671A @ 0x1C898 (map index) |
| Today's specials item sets |
Complete — static $683A + date index $681C/$6816
|
| Event → text routing |
Complete — 30-event-to-string-path.md
|
Overall static shop table completeness (town 0–4): ~92%. RNG at shop open affects str.dat captions only (smith/temple/guild names); numeric stock (item ids, spell indices, food GP) is in the data hunk.
Regenerate: python tools/dump_shop_tables.py --json EXTRACTED/shop_tables.json
| Table | Location | Values (town 0→4) |
|---|---|---|
| Temple donation GP | A4-$6742 |
20, 250, 40, 120, 40 |
| Temple quest bit |
A4-$66B1 (= master 0x2637C first 5) |
1, 2, 4, 8, 16 |
| Training stat + |
A4-$6720 (by map 0→4) |
5, 20, 10, 10, 3 |
| Training stat cap |
A4-$671A (by map 0→4) |
100, 100, 100, 100, 50 |
| Training town index | FAQ §3-6 (not map order) | 1, 5, 2, 4, 2 |
| Temple donation ×100 |
A4-$6714 (by map 0→4) |
1, 5, 2, 3, 2 → GP 100…500 |
Training cost: level × training_town_index × 50. Indices:
1 Middlegate, 2 Sandsobar & Tundara, 4 Vulcania, 5 Atlantium.
Healing cost: level × training_town_index × 10 (same town index as training).
- ×10 multiplier when character is dead:
level × index × 100 - ×100 multiplier when eradicated:
level × index × 1,000 - Go to Middlegate (index 1) for cheapest healing. [FAQ §3-6]
Portal table: see 34-commerce-formulas.md §3 — five portals linking all towns, costs 10–100 gp per leg.
Day-based shop bonus cycle: see 34-commerce-formulas.md §2 — ~30-day cycle; best bonus on days 30/60/90/120/150/180 (+5/+6/+7/+8/+9/+12). Calendar day at A4-$79DE; era at A4-$79B6.
HP path @ 0x9BCA (calendar mode 2): might-sum + divu #10 RNG addend;
cmp.b vs A4-$5608 @ 0x9C52. Does not read A4-$6714.
Stat training @ 0x1C898: add A4-$6720[map]; cap A4-$671A[map]
(A4-$79F2, not training index).
Tier template @ 0x26578: 7 × (u8 cost, u8 gain, u16 pad) — retail
100 gp / gains 50…116; first five gains match difficulty order
M, S, T, V, A.
A4-$6714: five BE u16 after stat-cap bytes — used @ 0x1DC26 as
temple donation multiplier × 100, not HP training.
Not training stat addends: data hunk 0x26800 (24 bytes) — unrelated to
A4-$6720.
-
Not inn: food/drinks =
OP_0E0x01, handler0x1A132. Inn0x02→-$7CD4(rest/dismiss only). -
Fixed per town: gold costs in data hunk
A4-$6760, indexedtown×6 + menu×2, BE u16 @0x1CEA4. Menu labels A/B/C map to fixedstr.dattext blocks (seefood_menu_textinshop_tables.json).
| Town | A (gp) | B (gp) | C (gp) |
|---|---|---|---|
| Middlegate | 10 | 50 | 100 |
| Atlantium | 1000 | 2000 | 3000 |
| Tundara | 200 | 100 | 1000 |
| Vulcania | 5000 | 500 | 1000 |
| Sandsobar | 20 | 50 | 250 |
-
Meal mechanics: tier table
A4-$6B08, cost addendsA4-$6B1A(these are not item ids — prior misread mapped to Staff/Blowpipe). Handler0x18EC0computes gold; purchase stores encoding on roster$78. -
Drinks: six global entries — base index
A4-$6AF0, addendA4-$6AEDresolved via0x18F78/-$7BB4. Same drink names all towns (str.datlines 209–214).
| Service | Static in data hunk | Runtime RNG |
|---|---|---|
| Smith weapons/armor/misc/specials |
Yes — $68EE…$6876, 6×/town/cat |
$5ACE / $5AA6 / $5A56 str captions @ 0x1C54A
|
| Smith Today's Specials |
Yes — $683A ids + $681C/$6816 date index |
Specials caption ptrs $5A56
|
| Temple cleric (×3) |
Yes — $66F6 + CLERIC_FLAT_ADJUST decode |
$59EE…$57F6 name ptrs @ 0x1D208
|
| Guild sorcerer (×4) |
Yes — $66E2 per town; $6698/$6686 costs |
$576E / $56F6 name ptrs @ 0x1E8B0
|
Full dumps: python tools/dump_shop_tables.py → EXTRACTED/shop_tables.json
(static_by_town + random_pool).
Sparse BE u16 cluster includes 10, 50, 100, 1000, 2000 (travel, training gates,
skills). Per-tile amounts still live in event.dat (OP_24 argc).
Pub meals are not items.dat SKUs (they encode on roster $78). This table
lists inventory quest items, consumables, and travel food — do not confuse
with tavern food menus (§13 / static_by_town.pub).
| Ref | Category | Event / usage |
|---|---|---|
| 224 | dungeon_treasure |
Gold Goblet — pick up Middlegate Cavern loc 17 tile (7,0) (OP_19 adds item 0xE0); not food (no use power in items.dat). Return to Middlegate (2,10) for Nordon OP_0E 0x0A (farthing flick). |
| 175 | consumable | Magic Meal — Create Food; treasure / Sandsobar smith misc |
| 176 | consumable | Antidote Ale — Cure Poison drink |
| (str) | pub_meal | Devils Food Brownie — Atlantium pub meal C only (roster $78) |
roster $25
|
party_food | Travel food supply; OP_15 oasis +40 |
OP_28 |
inventory | Consume item in scripts (e.g. 0x10 + count) |
Machine-readable: quest_items in EXTRACTED/shop_tables.json.
Olympic Trial (Atlantium (4,8), event str "Hurl the spear…") is not
training 0x04. It uses OP_02 + OP_24 500 + OP_15/OP_20
on roster $6D (personality base) and party_effect to grant Athlete
skill — separate from -$7D16 HP training.
| Page | Why |
|---|---|
| Spell Sources | Where temple/guild spells come from |
| Event to String Path | OP_0E vs event strings |
| Event Script Opcodes | OP_0E / OP_0B / OP_24 |
| Embedded Exe Strings | Exe-embedded shop prompts |
- Docs Wiki Hub ← full doc index
- Getting Started ← read this first
- Overview
- Workspace Notes
- Open Questions
- Full Analysis
- Game Remake
- Startup and Init
- Runtime Memory Map
- Main Loop and Map
- Exploration Input and Options
- Party and Session
- Game State Struct
- Data Hunk mm2 data 00
- Time Era Calendar
- GFX Loading
- ANM TV Format
- PC DOS graphics
- SNES graphics
- 3D View and Game Screen
- Amiga 3D Render Process
- Scripted Scene Graphics
- Event Graphics Opcodes
- Title Screen Assets
- Title Screen Animation
- Format inventory
- items.dat
- monsters.dat
- roster.dat
- attrib.dat
- map.dat
- spells & item use
- event.dat
- Events by location ← 71 maps
- Events hub (numbered)
- Event Script Opcodes
-
Event Script DSL ←
.mm2evtauthoring - Event Text Rendering
- Event Graphics Opcodes
- Combat Overview
- Combat System
- Encounter Tables
- Spell Cast ASM
- monsters.dat abilities
- Spells and item use
- Amiga audio in exe ← Paula / DATA / remake
- Controls: Sounds / Walk Beep
- MM2 music format (superseded)
- Title/death audio paths (superseded)
- Known songs catalog (superseded)
- Town Services
- Spell Sources
- Character Mechanics
- Skills and Hirelings
- Commerce Formulas
- Commerce World Services
- Mount Farview Class Quest
- Class Quest HP Bug
- Event Runtime
- Event to String Path
- Embedded Exe Strings
- Copy Protection
- Time Era Calendar
- Game State Struct
- MM1 Overview ← hub + decode status
- MM1 MAZEDATA format
- MM1 to MM2 outdoor
- MM1 WALLPIX by sector
- MM1 art & graphics
- MM1 items / monsters (status)
- MM1 map walker ↗
- MM1 2D maps gallery
- MM1 WALLPIX gallery