# mod-waygate-network

v1: a discover-as-you-explore fast travel network, in the spirit of FFXIV's Aetheryte system, built
as a chat command rather than a spell. See `PLAN.md` for the roadmap and the reasoning behind the
design, including two rounds of in-game-testing surprises this started as before landing here.

## What it does

- Destinations live in `mod_waygate_network_locations` (world DB) and are loaded once at startup
  (`WaygateNetwork_World::OnLoadCustomDatabaseTable`) — adding one is a plain SQL insert + restart
  (or `.waygate reload`), not a code change. Ships with the 8 original capital cities (IDs 1-8),
  Shattrath City (Outland, ID 9) and Dalaran (Northrend, ID 10), and full Eastern Kingdoms, Kalimdor,
  Outland, and Northrend quest-hub batches (IDs 11-27, 28-45, 46-61, 62-74) — see "Custom ID ranges"
  below.
- Each destination has a real, physical **Waygate Stone** standing in the world at its exact
  location — interacting with it **attunes** the destination (a row in
  `mod_waygate_network_discovered`), with a chat message confirming it plus a sound
  (`WaygateNetwork.UnlockSoundId`, see Config). Their very first-ever attunement also gets a one-line
  tip about `.waygate` and macro-binding it. Entering the destination's zone doesn't attune it on its
  own anymore — see "Waygate Stones" below for why, and what it does instead.
- Typing **`.waygate`** summons a short-lived, harmless "Waygate Portal" NPC next to you (entry
  `911001`, despawns after 15s or as soon as you finish) and immediately opens a **two-page** gossip
  menu against it — no wait yet: first a list of continents you have anything attuned in (see
  "Continent grouping" below), then — after picking one — the attuned destinations within it, each
  showing its gold/silver/copper cost (real coin icons via WoW's `|T...|t` inline texture markup).
  Picking a destination is where the wait happens (see "Cast time" below) — hold still for a few
  seconds and it charges that cost and teleports you; picking a destination you can no longer afford
  just tells you so and does nothing. The portal despawns once you pick something; **"« Back"**
  (destination page only) returns to the continent list; **"I changed my mind..."** (continent page
  only — "« Back" already covers backing out of the destination page) closes the menu and despawns
  the portal, free. Both pages always have 2+ options so there's never a single-choice list. See "Why
  a summoned NPC" below for why the portal exists at all, and "Travel cost" below for how the price
  is worked out.
- `WaygateNetwork.Enable` disables the whole thing live — both the command (`HandleWaygateCommand`)
  and the actual teleport (`npc_waygate_portal::OnGossipSelect`) re-check the flag, so flipping it
  off mid-session stops already-attuned characters from still traveling.
- Deleting a character cleans up its `mod_waygate_network_discovered` rows
  (`OnPlayerDeleteFromDB`), so a recycled guid can't inherit a stranger's attunements.
- **Playerbots (mod-playerbots) are excluded entirely** — the command no-ops for them and they never
  attune destinations. Guarded with `#ifdef MOD_PLAYERBOTS` (compiles fine with or without that
  module present).
- **`.waygate reload`** (GM-only) re-loads `mod_waygate_network_locations` without a full worldserver
  restart.
- **`WaygateNetwork.Debug`** (GM-only regardless of the flag) makes `.waygate` list every known
  destination instead of just attuned ones, tagged `[debug]`, and skips the attunement check on
  travel — for reviewing/testing new locations without visiting each one first.
- **`.waygate` itself is gated behind a quest chain** ("The Ley Wardens", see below) — GM debug mode
  is the only bypass. Attunement keeps working regardless, so finishing the chain doesn't leave you
  staring at an empty menu.

## Why a command, not a spell

v1 originally taught every class a self-cast spell, **Call Waygate** (910000), that opened the
gossip menu on cast. In-game testing (2026-08-13) showed the spell was learned correctly server-side
(confirmed in `character_spell`) but never appeared in the spellbook and couldn't be cast — including
via macro. Root cause: AzerothCore's `spell_dbc` SQL table is the *server's* internal spell data; it
has no relationship to the *client's* own local `Spell.dbc` file. A from-scratch ID like 910000 has
no entry in the client's copy, so the client has no name/icon/anything to render for it, no matter
how correctly the server tracks that the character knows it. There's no client patch distributed
here to fix that. See `PLAN.md`'s "Deferred / rejected" section for what would actually be needed to
make a *real* custom spell work, if that's ever worth revisiting.

The `.waygate` command sidesteps that problem — chat commands are handled server-side only, so
there's no client-DBC dependency.

## Why a summoned NPC (not a menu sourced from the player directly)

The first version of the command opened the gossip menu using the *player's own GUID* as the gossip
sender (the core's `HandleGossipSelectOptionOpcode` does explicitly support that — see PLAN.md). This
also failed in-game (2026-08-13, same session as the spell fix): no gossip window ever appeared, yet
selecting still silently resolved to Ironforge. Every other gossip flow in the game is sourced from
an actual Creature/GameObject the player has interacted with; a menu sourced from the player's own
character, opened out of nowhere via a chat command, isn't something the client's Gossip UI seems to
render correctly, even though the server-side protocol nominally allows it.

Fixed by summoning a real, if short-lived, NPC (`npc_waygate_portal`, entry 911001, faction 35 —
non-hostile to everyone) right next to the player and sourcing the gossip menu from *it* instead.
This is the exhaustively-proven-working path (every vendor/questgiver in the game already validates
it), so it carries no such uncertainty. The portal despawns itself after a destination is picked, or
after 15 seconds as a safety net if the player never interacts with it (e.g. closes the window).

## Waygate Stones

Attunement used to be entirely passive — `WaygateNetwork_Player::OnPlayerUpdateZone` matched a
character's current zone against `mod_waygate_network_locations` and auto-attuned it, no need to
find or interact with anything. As of this version, that's replaced with a real, permanently-spawned
**Waygate Stone** (`gameobject_template` entries `913001`-`913074`, one per destination, `entry =
913000 + destination id`) standing at each destination's *exact* location — a character has to
actually find and interact with it (`go_waygate_standing_stone::OnGossipHello`) to attune, not just
clip through the zone border. Fits the Ley Wardens lore tighter (the quest chain's own text already
calls these things "standing stones") and makes attunement a deliberate, findable moment rather than
an invisible side effect of exploring.

**Travel destinations now match the stones' real positions** (v1.24) — v1.23's hand-tuned stone
positions live in `gameobject`, but `.waygate` actually teleports players using
`mod_waygate_network_locations`, which still held each destination's *original* coordinates.
Corrected by setting each destination's `position_x/y/z/orientation` to a point 1 yard directly in
front of its stone (along the stone's own `orientation`), facing back toward it on arrival —
computed directly from the stones' now-current positions, not re-walked by hand. Takes effect
immediately via the existing `.waygate reload` GM command, no server restart needed (unlike stone
position/rotation changes themselves, which do need one).

**Stone orientation had a real mesh-offset bug, now fixed** (v1.25) — the "Ancient Stone Marker"
model renders its own front 90 degrees ahead of whatever `orientation` is stored, confirmed by
checking multiple stones after a restart ruled out stale data. Every stone's stored orientation is
now offset -90 degrees to compensate, so the model's visual facing matches the direction originally
intended by each `.gps` reading. The destination-offset math above deliberately keeps using the
pre-correction direction (already confirmed correct on its own), not this cosmetic fix.

Model: `displayId 7789`, the real "Ancient Stone Marker" (`gameobject_template` entry `188469`),
`type = 10` (`GAMEOBJECT_TYPE_GOOBER`). Two real bugs before landing here:

1. The original pick — the real "Caverns of Time Standing Stone," `displayId 9501` — turned out
   invalid on this server: `.gobject add` logged `"have invalid displayId (9501), not spawned"` for
   *both* our clone and the real, unmodified `19501` entry, confirming it wasn't a scripting bug but
   a missing DBC record — this server's actual loaded `GameObjectDisplayInfo.dbc` is a reduced client
   build (~3790 of a normal ~9000+ display records), and `9501` isn't one of them.
2. The immediate replacement, "Nightelf Stone Rune" (`displayId 236`), rendered fine but couldn't be
   interacted with at all — because it's `type = 5` (`GAMEOBJECT_TYPE_GENERIC`), and every real
   "click this object, run custom logic via `OnGossipHello`" `GameObjectScript` already in this
   codebase (`go_southfury_moonstone`, `go_tele_to_dalaran_crystal`, etc.) turned out to use
   `type = 10` (`GAMEOBJECT_TYPE_GOOBER`) instead. `GENERIC` is plain, non-interactive scenery — the
   client most likely never offers a "use" prompt for it at all, so `OnGossipHello` never had a
   chance to fire regardless of how correctly it was wired server-side.

Both fixed by "Ancient Stone Marker": `displayId 7789` is confirmed present in this server's real
DBC, `type = 10` matches every real working precedent, and its one non-zero field (`Data3` =
`autoCloseTime`, a cosmetic 3-second "activated" reset) is irrelevant here since `OnGossipHello`
returning `true` suppresses the default open/close behavior before it would ever run. A couple of
thematically-tempting Titan-flavored alternates (`Titan Relic`, `The Discs of Norgannon`, `Ulduar
Teleporter`) were also considered and rejected along the way — all carry real hidden
`SmartGameObjectAI`/spell-focus/quest-link behavior a plain override isn't guaranteed to fully
suppress.

Each of the 74 gets its own dedicated template entry rather than sharing one: the same
one-custom-ID-per-spawn pattern already used for this module's NPCs (`901000`, `911001`,
`911002-911006`), so the C++ side can resolve which destination a given stone represents from its
own template entry (`go->GetEntry() - WAYGATE_STONE_ENTRY_BASE`) rather than its spawn guid or
position. Real `gameobject.guid` values in this client's local DB already run into the millions —
too real a collision risk to use as a deterministic per-destination lookup key the way this module's
earlier NPC work could safely do with low, self-chosen ranges.

**Positioning**: every stone originally reused its destination's coordinates verbatim, which
themselves were sourced by copying a real landmark NPC's exact position back in the v1.11-v1.14
continent batches — so every stone spawned clipped inside/underneath an NPC, and in three spots
(Echo of the Ley Wardens, Windcaller Tyese, The First Warden all sit at a destination's exact
coordinates too) inside a Ley Wardens quest NPC as well. Fixed by nudging each stone 3 yards along
its destination's own saved `orientation`, and the three affected quest NPCs 3 yards the *opposite*
way — the original landmark NPC stays exactly where it was, the stone and the quest NPC end up 6
yards apart on either side of it. That first pass was a heuristic, not individually terrain-verified
(no vmap/LOS data available to this tooling) — since superseded: every stone has since been
individually walked and repositioned by hand in-game (2026-08-21, see "Known v1 limitations" below).

Successfully attuning also plays a one-shot cosmetic flash (`SendPlaySpellVisual`,
`WaygateNetwork.AttuneVisualKitId`, default kit `3394` — the same real Teleport-family visual kit's
ImpactKit already relied on for the travel channel's kit `267`, keeping the two moments visually
related) plus a one-shot animation (`HandleEmoteCommand(EMOTE_ONESHOT_USE_STANDING)`, the real
generic "reach out and use a standing object" gesture) on the player, on top of the existing sound
and chat message.

**Stones are gated the same way `.waygate` itself is.** Interacting with one before the Ley Wardens
chain is complete doesn't attune anything — it opens a gossip popup with in-world flavor text
instead (`"You feel it the moment your hand touches the stone..."`, `npc_text` `911012`), pointing
toward the Hillsbrad Foothills in a general sense without naming Aemos directly (the `.waygate`
command's own dormant-network message already does that — this is often a character's *first* hint
that anything's going on, before they've ever typed the command, so it stays a level more mysterious).
`WaygateNetwork.RequireIntroQuest` covers both gates together; GM debug mode is the only bypass for
either.

`OnPlayerUpdateZone` still fires on zone-entry, but now only as a hint (`"You sense a waygate stone
somewhere nearby..."`) for an un-attuned destination — a nudge, not a trigger. It still repeats every
time an un-attuned character re-enters a *different* un-attuned zone on foot (intentional, not a
bug), but two guards keep it from spamming (v1.21): skipped entirely while on a taxi (`IsInFlight()`)
— a single flight crossing several un-attuned destination zones back to back turned out to be the
actual source of "several messages in a row" reports, not one zone re-triggering — and deduped
against the last destination actually hinted (`Player::CustomData`, session-scoped, not persisted),
so re-entering the *same* zone doesn't repeat the message.

## Continent grouping

Each destination has a `continent` column (`ENUM('Eastern Kingdoms','Kalimdor','Outland','Northrend')`
in the world DB) that drives the two-page menu — added once the list grew past the original 8
capitals, so a flat list wouldn't keep scaling. **This is a curated label, not derived from the raw
`map` column** — Exodar and Silvermoon share `map = 530` with genuine Outland zones purely as a
client-data quirk (their actual continents, Azuremyst Isle and Eversong Woods, got bundled onto that
map ID by Blizzard), so grouping by raw `map` would put them in a nonsensical "Outland" bucket from a
player's point of view. They're tagged `Kalimdor` and `Eastern Kingdoms` respectively instead — set
`continent` deliberately for any new destination, don't infer it from `map`.

## Faction gating

Each destination also has a `faction` column (`ENUM('Alliance','Horde','Neutral')`, default
`Neutral`) so a character can never end up dumped in the middle of an enemy capital just to get
jumped by guards. `WaygateFactionAllowed` checks it against `Player::GetTeamId()` — an `Alliance`/
`Horde` destination is filtered out of the menu (`GetSelectableDestinations`) and re-checked again in
`BeginWaygateChannel`/`FinishWaygateChannel` (same "don't trust the gossip action alone" defense the
attunement check already uses) for anyone not on that faction; `Neutral` destinations (the two
sanctuary cities, Shattrath and Dalaran) are always open to everyone. The 8 original capitals are
tagged `Alliance` (Stormwind, Ironforge, Darnassus, The Exodar) or `Horde` (Undercity, Orgrimmar,
Thunder Bluff, Silvermoon City) accordingly — set `faction` deliberately for any new destination,
same as `continent`.

**GM debug mode (`WaygateNetwork.Debug`) is the only override** — it already lists/travels to every
destination regardless of attunement (see "Admin/debug tooling" in PLAN.md), and that bypass covers
faction too, so a GM can still review or test an opposing-faction destination without needing a
second character.

## Intro quest chain: The Ley Wardens

`.waygate` doesn't do anything until a character completes a 5-quest chain, mostly dialogue but with
one real combat objective. `WaygateNetwork.RequireIntroQuest` (default on) controls this; GM debug
mode is the only other bypass. The check itself is a single guard clause in `HandleWaygateCommand`:
`player->GetQuestRewardStatus(QUEST_WAYGATE_REACTIVATION)` — no custom `CreatureScript` hooks needed
anywhere in the chain, core's default gossip/quest UI handles a plain accept/turn-in questgiver
entirely from `creature_queststarter`/`creature_questender` + `quest_template*`. The one exception is
the kill-credit objective on quest 2 (`RequiredNpcOrGo1`/`RequiredNpcOrGoCount1` on `quest_template`
— positive value = creature kill credit, still no custom C++ needed).

**Redesigned (2026-08-16) to stay entirely within Eastern Kingdoms and Kalimdor**, unlockable early
(`MinLevel = 15`, `QuestLevel = 20` — paced so most characters finish around level 20), starting in a
zone with both a real Alliance and Horde town nearby. **Story:** Aemos, a sentient stone construct —
the last surviving guardian of the network, newly and only partly reawakened — is found in the
Hillsbrad Foothills, the contested no-man's-land between Southshore and Tarren Mill. Deliberately not
a member of either faction, so neither side "owns" the lore (`AllowableRaces = 0` on every quest).
Each quest is accepted from one NPC and turned in to the *next* leg's NPC rather than doubling back,
so the chain is one long trip across the world rather than repeated out-and-back errands, closing
with a return to Aemos for the reactivation itself:

1. **The Waygate Stirs** — Aemos (Hillsbrad Foothills) → Windcaller Tyese (Astranaar, Ashenvale)
2. **Roots Older Than the Circle** — Windcaller Tyese → Borin Ironquill (Uldaman, Badlands) — kill
   **The Ironbound Sentinel**, a Titan-built earthen guardian that wakes to stop the old ley magic
   from being surfaced, along the way. The one combat leg in an otherwise dialogue-only chain, and
   where the "older than the Ley Wardens" reveal lands.
3. **What the Stone Remembers** — Borin Ironquill → The First Warden (Windshear Crag, Stonetalon
   Mountains)
4. **The Long Way Home** — The First Warden → Aemos (Hillsbrad Foothills)
5. **The Network Reborn** — Aemos → Aemos (the reactivation ritual; completing this grants the
   unlock)

Coordinates are real and DB-verified, not guessed: Aemos sits clear of the real levelled wildlife
that already populates the Hillsbrad no-man's-land (Starving Mountain Lion/Vicious Gray Bear, both
20-23, an apt match for the chain's pace). Borin Ironquill sits a few yards from the real Explorers'
League dig camp at Uldaman's entrance (High Explorer Magellas et al.) without overlapping any of
them. Windcaller Tyese reuses Waygate destination 28's zone (Astranaar) but is offset ~4 yards from
Innkeeper Kimlya, who sits at that destination's exact coordinates — the same overlap this module
already fixed once for the Waygate Stones (see "Waygate Stones" below). The First Warden sits a few
yards from the real Horde outpost Windshear Crag (Innkeeper Jayka).

Borin Ironquill occupies the NPC slot originally used by "Echo of the Ley Wardens" — repurposed, not
newly added, since the "spectral echo" framing didn't fit revealing something *older* than the Ley
Wardens themselves (an echo of the order wouldn't know that). The former fourth leg, "Nazrek the
Bent" (the old Outland contact), is retired entirely with no replacement — the new geography only
needs three intermediate nodes. The Ironbound Sentinel's stats are modeled directly on Mor'Ladim
(`creature_template` entry `522`, Duskwood's real solo-able open-world elite) rather than invented —
same `HealthModifier`/`DamageModifier`/level/rank — and its display (`6026`) is reused from the real
"Earthen Guardian" creature, which only ever spawns inside the Uldaman instance, so there's no
open-world collision risk.

## Travel cost

An alternative to flight paths, not a replacement — deliberately priced a bit above a comparable
flight so flying is still the better deal for a route you can actually fly. Same-map trips cost
`WaygateNetwork.CostPerYard` copper per yard of straight-line distance from the player's current
position to the destination; cross-map trips (different continents) cost a flat
`WaygateNetwork.CrossMapCostCopper`, since straight-line distance isn't meaningful across different
coordinate spaces. `WaygateNetwork.MinCostCopper` is a floor so short hops still cost something.

The defaults are a *starting point*, not a precise calibration: this client's real `TaxiPath.dbc`
was checked directly (not guessed) — real flight prices range ~50c (Stormwind ↔ Ironforge) to
~12000c for the longest single-continent legs, and aren't a clean function of distance. The default
`CostPerYard` (0.02) puts Stormwind ↔ Ironforge at ~84c, about 1.7x that real 50c price — the one
real anchor point actually available. Other routes will drift from that exact ratio. Tune the config
values against what you observe in-game rather than trusting these as scientifically calibrated.

Debug mode (`WaygateNetwork.Debug`, GM-only) travels free — it's for reviewing/testing locations,
not for dodging the cost.

## Cast time

Picking a destination starts a `WaygateNetwork.ChannelMs`-long wait (default 4 seconds) before
you're actually charged and teleported — no real spell involved at all. It's a plain,
fully server-controlled wait: a chat message ("Hold still... the waygate reaches for you."), an
optional one-shot cosmetic visual (`SendPlaySpellVisual`, `WaygateNetwork.ChannelVisualKitId`), and a
looping "channeling" animation on the player (`SetEmoteState(EMOTE_STATE_SPELL_CHANNEL_OMNI)` —
Blizzard's own generic spell-channel stance, cleared unconditionally once the wait ends, success or
not) — all purely cosmetic, zero gameplay side effects — plus a timer via the player's own event
scheduler (`player->m_Events.AddEventAtOffset`, `BeginWaygateChannel`/`FinishWaygateChannel`). Move
away from where the wait started (checked by straight-line distance, allowing 2 yards of slack, plus
a same-map check) and it cancels with no charge; entering combat at either end blocks it too. Cost is
only charged once the wait actually completes.

**This replaced an earlier design that hijacked a real spell's cast bar** (first the Mage
`Teleport: ` family, then Hearthstone, ID 8690) purely to get a genuine cast bar/interrupt
UI for free. It worked in principle — real cast time pulled directly from this client's `Spell.dbc`,
a conditional `SpellScript` gated on a pending-entry marker so a real cast of the donor spell was
never touched — but in-game testing found overriding the donor's actual effect
(`PreventHitEffect`) wasn't reliable: the real Hearthstone "teleport home" effect and its ~30-minute
cooldown still fired some of the time despite the guard, in a way that was hard to fully pin down or
trust. See PLAN.md's "v1.7"/"v1.8" sections for the full blow-by-blow (including why generic monster
"Teleport" spells and other "Casting"/"Channeling"-named spells were checked and ruled out earlier in
that exploration). `data/sql/db-world/updates/2026_08_15_03.sql` removes the now-unused
`spell_script_names` binding that approach had added to spell 8690.

## Known v1 limitations

- `WaygateNetwork.ChannelVisualKitId` defaults to `267` — real data, not guessed: traced through this
  client's binary `Spell.dbc` -> `SpellVisual.dbc` -> `SpellVisualKit.dbc`, it's the Mage `Teleport:`
  family's actual sustained casting-loop visual (the glowing rune circle spun under the caster's feet
  while the cast bar fills), in-game confirmed to look right for this use. Preview any other kit
  in-game (GM-only) with `.debug play visual ` if you want to try alternatives.
- The portal's model/visual (`CreatureDisplayID` 26484, reused from existing Alterac Valley Portal
  content) is a placeholder, not custom-built for this module.
- No cooldown between attempts beyond the 10s cast time itself and the gold cost.
- Discovery/menu queries hit `acore_characters` synchronously (`CharacterDatabase.Query`, not the
  async callback pattern) — fine at this server's scale, but not the core's preferred pattern for a
  hot path.
- Coin-icon markup (`|T...|t`) in gossip text hasn't actually been eyeballed in-game yet — same "not
  verified against a real client" caveat as everything else in this module until you say otherwise.
- **The Ley Wardens quest chain and the Waygate Stones' rendering/interaction are confirmed working
  end-to-end in-game** (2026-08-15).
- **All 74 Waygate Stones have been individually repositioned by hand in-game** (2026-08-21,
  `data/sql/db-world/updates/2026_08_21_02.sql`) — v1.17's blind 3-yard-offset heuristic is retired;
  every stone now sits at a real, walked-and-verified position rather than an estimate.

## Custom ID ranges

- **Text/data ID 911000** (gossip header `npc_text`, no creature attached to it directly)
- **Creature ID 911001** (`npc_waygate_portal`, the summon-only Waygate Portal NPC)
- **Creature IDs 911002-911004, 911006** — the 4 persistently-placed Ley Wardens quest-chain NPCs
  (Aemos, Borin Ironquill, Windcaller Tyese, The First Warden). `911003` originally held "Echo of the
  Ley Wardens," repurposed in place into Borin Ironquill rather than retired (see "Intro quest chain"
  above); `911005` ("Nazrek the Bent") is retired entirely and no longer in use. `quest_greeting` rows
  reuse these same entries directly (keyed by NPC entry, not a separate ID range).
- **Creature ID 911007** — "The Ironbound Sentinel," the kill-credit objective on quest 912001.
- **Quest IDs 912000-912099** reserved for this module — `912000-912004` are the five Ley Wardens
  quests (`912005` retired in the 2026-08-16 redesign, not reused), leaving room for a future second
  chain without renumbering.
- **`gameobject_template` IDs 913001-913074** — one Waygate Stone per destination
  (`entry = 913000 + mod_waygate_network_locations.id`), see "Waygate Stones" above.
- **Text/data ID 911012** (gossip flavor text shown when interacting with a stone before the Ley
  Wardens chain is complete), continuing the flat `911xxx` text/data space alongside `911000`.
- **`mod_waygate_network_locations` IDs 1-8** are reserved for the original capital cities, **9-10**
  for Shattrath City / Dalaran, **11-27** for the Eastern Kingdoms quest-hub batch, **28-45** for the
  Kalimdor batch, **46-61** for the Outland batch, **62-74** for the Northrend batch (all referenced
  by existing `mod_waygate_network_discovered` rows) — new destinations should start at 75.

Spell ID 910000 (`Call Waygate`) is retired — see "Why a command, not a spell" above. Its `spell_dbc`
/ `spell_script_names` rows are removed by `data/sql/db-world/updates/2026_08_13_01.sql`, and any
character that had learned it gets it unlearned by
`data/sql/db-characters/updates/2026_08_13_01.sql`. Don't reuse 910000 for anything else without
checking those files.

This module doesn't use or reserve any real spell ID anymore — see "Cast time" above for why the
earlier Hearthstone-hijack approach (spell ID 8690) was dropped, and
`data/sql/db-world/updates/2026_08_15_03.sql` for the cleanup of the binding it had added.

## Config

See `conf/mod_waygate_network.conf.dist`:

- `WaygateNetwork.Enable` (default `1`) — toggles the whole module: attuning destinations on
  zone-enter and the `.waygate` travel menu.
- `WaygateNetwork.Debug` (default `0`) — GM-only "show all locations" mode, see above.
- `WaygateNetwork.UnlockSoundId` (default `1519`, `TaxiNodeDiscovered`) — `SoundEntries.dbc` ID
  played on attunement. Real data, not guessed: traced through this client's binary
  `SoundEntries.dbc`, it's Blizzard's own sound for discovering a new flight point — the closest real
  in-game equivalent to unlocking a new fast-travel destination.
- `WaygateNetwork.AttuneVisualKitId` (default `3394`) — cosmetic `SendPlaySpellVisual` flash on
  attunement, see "Waygate Stones" above.
- `WaygateNetwork.CostPerYard` (default `0.02`), `WaygateNetwork.CrossMapCostCopper` (default
  `20000`), `WaygateNetwork.MinCostCopper` (default `25`) — see "Travel cost" above.
- `WaygateNetwork.ChannelMs` (default `4000`), `WaygateNetwork.ChannelVisualKitId` (default `267`) —
  see "Cast time" above.
- `WaygateNetwork.RequireIntroQuest` (default `1`) — gates both `.waygate` and the Waygate Stones
  themselves behind the Ley Wardens quest chain, see "Intro quest chain" and "Waygate Stones" above.