Skip to content

Lateania: Hearth & Hereafter — classes, archetypes, death & resurrection, pets & housing#374

Open
hardlygospel wants to merge 8 commits into
mpiorowski:mainfrom
hardlygospel:feat/lateania-player-housing
Open

Lateania: Hearth & Hereafter — classes, archetypes, death & resurrection, pets & housing#374
hardlygospel wants to merge 8 commits into
mpiorowski:mainfrom
hardlygospel:feat/lateania-player-housing

Conversation

@hardlygospel

Copy link
Copy Markdown
Contributor

Lateania: Hearth & Hereafter

Thanks @mpiorowski! This is Hearth & Hereafter (#363) — a character-and-home expansion for Lateania, bundled here as one release. Seven reviewable commits, lock-free / snapshot-only, every stage cargo test / clippy / fmt green (123 lateania tests).

Supersedes the stacked PRs #370 / #371 / #373, which I'm closing in favour of this single expansion PR. Branches cleanly off main. Old saves load unchanged (save schema grew to v10; every new field is #[serde(default)]).

🎭 Twelve classes, archetypes & every level

The roster grows from five to twelve — Druid, Necromancer, Bard, Monk, Paladin, Warlock, Berserker join the originals, each with its own resource, trait, and full 1–50 ability roster. Every level now grants a visible reward and a named milestone (Blooded…Ascended). At level 10 each class picks one of two archetype paths, each a Tank / Healer / DPS stance expressed as percent modifiers on the existing combat hooks — no engine changes.

💀 Death & resurrection

A real dead state: when you fall you become a corpse where you stand, not an instant temple trip. Wait for rescue or release (r); if no one comes by the linger deadline you're drawn home automatically. Resurrection is a rite of the holy/nature callings (Cleric/Paladin/Druid) — a living caster (g) raises the nearest corpse in place.

🐺 Combat companions

Buy a beast at a capital Stable (p) — War Hound, Dire Wolf, Moor Hawk, Cave Bear, Emberdrake. It fights at your side (bites your target, kill credited to you), shares the blows you survive and can be downed, and grows by feeding (loyalty → levels). Persisted across sessions.

🏠 Player housing (true Ultima Online)

Hearthward Close, a public courtyard off Embergate's Market Row, is ringed with one home of each of five tiers — Wattle Hut → Thatched Cottage → Timber Longhouse → Stone Manor → Wizard's Tower (the grand ones add upper floors up a stair). Buy a deed at the clerk (n) to claim a home, then furnish it from a catalogue of 50+ pieces, each with its own description, that everyone in the room can see. The homes are shared-world — public rooms anyone can walk into.

The trick that made housing tractable (and the through-line of this whole expansion): static rooms + dynamic side-maps. Homes are pre-built rooms; only ownership and furnishings are runtime side-state, so all the movement/visiting/snapshot machinery — and the UO "walk into someone's house" feel — comes for free. The same pattern carries the archetype/pet/corpse layers.

The seven commits

  1. Two new classes — Druid and Necromancer
  2. Five more classes — Bard, Monk, Paladin, Warlock, Berserker
  3. Make every level mean something
  4. Archetype paths and role stances at level 10
  5. A real dead state and the Resurrection rite
  6. Combat companions bought from capital stables
  7. Player housing — shared-world homes at Hearthward Close

Tests

cargo test -p late-ssh --lib door::lateania   # 123 pass
cargo clippy -p late-ssh --lib                # clean
cargo fmt --check                             # clean

New coverage for all four pillars: the twelve-class/ability invariants and milestones, archetype gating + tuning, the corpse/release/resurrect flow, companion combat/downing/feeding, and housing deeds/furnishing/visiting + the save round-trips.

P.S. — @mpiorowski this completes the roadmap from #363. Happy to split it back into the separate PRs if you'd rather review them piecemeal — just say the word. And the offer on a fresh late.sh banner still stands. 🙂

Tony Hosaroygard added 7 commits June 21, 2026 16:04
First slice of the Hearth & Hereafter expansion (mpiorowski#363): the playable
roster grows from five to seven.

- Druid (WIS, Spirit): a hybrid bruiser-healer of the wild. Trait
  "Nature's Renewal" mends a little health every tick. Kit of thorns,
  roots, moonfire, barkskin, regrowth and a Force of Nature capstone.
- Necromancer (INT, Souls): a hardy shadow caster. Trait "Soul Harvest"
  restores health and Souls on every kill. Kit of shadow bolts, decay,
  bone shields, life-drain and a Soul Reaping capstone.

Two new Resource variants (Spirit, Souls) and full match arms across
classes.rs; ability rosters in abilities.rs (ids 800/900, each with a
level-1 opener, a level-50 capstone, and 8+ unlocks by 50); class-select
keys 6 and 7 in input.rs. The only runtime additions are the two trait
hooks: Druid regen in the per-player upkeep loop and Necromancer harvest
in kill_mob - no combat-engine changes, all effects reuse existing
AbilityEffect kinds.

Adds class round-trip/coverage and trait tests; the existing
ability-roster invariants now cover both new classes. 103 lateania tests
green; clippy/fmt clean. CONTEXT.md updated.

Signed-off-by: Tony Hosaroygard <tasmaniamate@gmail.com>
Completes the dozen-class roster for the Hearth & Hereafter expansion
(mpiorowski#363), building on the Druid/Necromancer slice.

- Bard (CHA, Tempo): support battle-singer. Trait "Battle Hymn" returns
  Tempo faster. Songs that mend, hearten, and unstring the foe.
- Monk (DEX, Ki): martial ascetic. Trait "Iron Body" blunts physical
  blows. Flurries, stuns, and a Touch of Death capstone.
- Paladin (STR, Mana): holy bulwark. Trait "Aura of Devotion" mends a
  little each tick. Smites, shields, and Avenging Wrath.
- Warlock (CHA, Mana): pact caster. Trait "Pact of Souls" restores Mana
  on a kill. Curses, drains, and Cataclysm.
- Berserker (STR, Rage): reckless juggernaut. Trait "Frenzy" scales
  damage up to +50% as health falls past half. Biggest HP pool.

New Resource variants Tempo and Ki; full match arms across classes.rs;
rosters in abilities.rs (ids 1000-1408, each a level-1 opener, level-50
capstone, 9 unlocks). Five small trait hooks in svc.rs reusing existing
sites - no combat-engine changes.

Class select reworked to a cursor list (w/s + Enter, 1-9 quick-pick)
since twelve classes no longer fit single number keys; one compact row
each plus a detail block for the highlight. Adds an all-twelve
integration test; the ability invariants now cover every class. 104
lateania tests green; clippy/fmt clean. CONTEXT.md updated.

Signed-off-by: Tony Hosaroygard <tasmaniamate@gmail.com>
Third slice of the character-depth pillar (Hearth & Hereafter, mpiorowski#363).
Leveling already grew stats every level via the formula curve, but the
gains were invisible - non-ability levels just said "You reach level N".

- check_level_up now announces, for each level gained, the concrete
  reward: the +max HP / +attack / +resource the curve granted, the new
  ability when one unlocks, and a named milestone at every fifth level.
- New milestones (Blooded, Toughened, ... Ascended) land at levels
  5/10/.../50 and grant a permanent +5 HP each, folded into max_hp via
  milestone_hp_bonus - a pure function of level, so no new save state.
- The character sheet shows the current milestone rank under the level.

No dead levels: a test asserts every level for every class either grows
a stat or hits a milestone. Adds milestone + level-up-feedback tests.
106 lateania tests green; clippy/fmt clean. CONTEXT.md updated.

Signed-off-by: Tony Hosaroygard <tasmaniamate@gmail.com>
Add the archetype layer that completes the character-depth pillar. At
level 10 every class chooses one of two permanent paths (the ARCHETYPES
data table in classes.rs), each carrying a role stance — Tank, Healer,
or DPS — expressed as four percent modifiers: outgoing damage, incoming
mitigation, healing received, and max HP.

The modifiers ride existing combat hooks, so there are no engine
changes: DPS amplifies attack() and spell_damage(), Tank reduces the
blow in strike_player(), Healer boosts heal_player(), and the max-HP
bonus folds into max_hp(). The chosen path is held on PlayerState as a
&'static ArchetypeDef and persisted by stable key (schema 7 -> 8,
defaulting gracefully for older saves and validated against the class
on load).

A new level-10 selection screen (draw_archetype_select, gated on a
non-empty archetype_choices in the snapshot) takes over until the player
commits with 1/2, mirroring the class-select flow; the character sheet
shows the chosen path and role.

Tests cover the level gate, immediate stat effect, persistence
round-trip, and tank mitigation. cargo test/clippy/fmt all green.

Signed-off-by: Tony Hosaroygard <tasmaniamate@gmail.com>
Death used to blink a fallen player back to the temple after an 8s
rest. Now falling leaves a real CORPSE where you stand: hp 0, target
and buffs cleared, 20% carried gold and any escort lost (banked gold
still safe), and your spirit lingers awaiting rescue.

From the corpse you choose your fate:
- wait for a resurrection, or
- release to the temple now (r / Enter while dead).

If neither happens before the linger deadline (CORPSE_LINGER_SECS) the
tick auto-releases you, so no one is ever stuck. The temple trip is now
a shared send_to_temple helper used by both the auto-release and the
manual release.

Resurrection becomes a rite of the holy and life-attuned callings
(Class::can_resurrect -> Cleric, Paladin, Druid). A living caster in the
same room spends resource (g key) to raise the nearest corpse in place
at a fraction of full vitality (resurrect_nearest) rather than sending
them to the temple.

The snapshot now carries dead / can_resurrect / corpse_here and a
per-occupant alive flag, so the UI shows a "You have fallen" overlay
with the release hint, tags corpses as (fallen) in the room roster, and
prompts capable players to resurrect. The dead state is transient and
not persisted (a reload returns the character alive at a safe room), so
there is no save-schema change.

Tests cover the lingering corpse (not an instant temple trip),
release-to-temple, and a healer resurrecting a corpse in place while an
incapable class cannot. cargo test (111) / clippy / fmt all green.

Stacked on the character-depth expansion (PR mpiorowski#370, The Twelvefold Path).

Signed-off-by: Tony Hosaroygard <tasmaniamate@gmail.com>
Add beasts of war that fight at your side - the next Hearth & Hereafter
pillar after death & resurrection.

Each capital gains a Stable (new FeatureKind), the companion vendor. Open
it with `p`, browse five species (the new pets.rs PetSpecies table - War
Hound, Dire Wolf, Moor Hawk, Cave Bear, Emberdrake), and buy one with
gold (Enter); `x` feeds and tends the one you have. You keep one
companion at a time; a new purchase sets the old one loose.

A pet rides on PlayerState, so it is always at your side. In the combat
round it bites your target after your own strike (and the kill is yours
if its bite finishes the foe). When you are struck, a share of the blow
(PET_WOUND_PCT) splashes onto it - but only on blows you survive, since
combat is over once you fall. A pet beaten to zero is downed and stops
fighting until you feed it at a stable, which revives it, heals it to
full, and raises its loyalty. Loyalty drives its level (more HP and
attack) via a pure function, so only the species key and loyalty are
persisted (schema 8 -> 9; it reloads at full health).

The snapshot carries the live pet (glyph, level, HP, downed, loyalty)
and the stable catalogue, so the room panel shows your companion, the
character sheet and Stable panel show its state, and a hint points to
the vendor.

Tests cover buying (gold spent / unaffordable refused), the pet piling
onto your target, being downed by a barrage, feeding to revive and
strengthen, and every capital having a stable - plus the pets.rs growth
maths and the save round-trip. A pre-existing bank test that found a
feature by hardcoded index is fixed to find it by kind, since adding the
stable shifts feature indices. cargo test (119) / clippy / fmt green.

Stacked on the death & resurrection PR (mpiorowski#371).

Signed-off-by: Tony Hosaroygard <tasmaniamate@gmail.com>
The final Hearth & Hereafter pillar: true-Ultima-Online-style homes you
buy, own, furnish, and let others visit.

Hearthward Close is a public courtyard off Embergate's Market Row,
ringed with one home of each of five tiers - Wattle Hut, Thatched
Cottage, Timber Longhouse, Stone Manor, and Wizard's Tower - the grander
ones adding upper floors reached by a stair. A housing clerk keeps the
ledger (open it with `n`): buy a deed to claim a free home of that tier
(one home to a name), then, standing inside it, furnish it from a
catalogue of over fifty pieces, each with its own flavour - from an oak
milking stool to a brass orrery. Placed furnishings become part of the
room everyone sees.

The key design choice keeps this tractable: homes are NOT dynamic rooms.
They are pre-built static rooms (housing.rs data + world.rs
extend_housing), so all the movement, snapshot, minimap, and visiting
machinery works unchanged - and because the close and every door are
public, anyone can walk up and visit, exactly the UO shared-world feel.
Only ownership (plot_owner) and furnishings (house_furniture) are
dynamic side-state on the service, the same static-rooms-plus-side-map
pattern that made the mazes, mob behaviours, and wildlife tractable.

Ownership and placed furniture persist (schema 9 -> 10); on load they
are re-registered into the service side-maps. The snapshot carries a
context-sensitive housing ledger (deeds at the clerk, the furniture
catalogue inside a home you own) and the room description lists whatever
has been set down.

Tests cover claiming a deed (gold spent, one-home-per-name), furnishing
only a home you own while a visitor in the same room cannot, the
50+-piece catalogue and non-overlapping plot address maths, the housing
room count, and the save round-trip. cargo test (123) / clippy / fmt
green. Stacked on the combat-pets PR (mpiorowski#373).

Signed-off-by: Tony Hosaroygard <tasmaniamate@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants