github binary-knight/usurper-reborn v0.28.0
Alpha v0.28.0 - PvP Arena and Living World Simulator (Multiplayer)

latest releases: v0.65.7, v0.65.6, v0.65.5...
5 months ago

Usurper Reborn - v0.28.0 Release Notes

PvP Combat Arena (Online Multiplayer)

Arena Location

  • New "Arena (PvP)" location accessible from Main Street option [6] in online mode only. Players challenge other characters to asynchronous PvP combat in a gladiatorial arena setting.
  • Level 5 minimum required to enter the Arena.
  • Maximum 5 PvP attacks per day to prevent farming.
  • Cannot attack the same player twice in one day to prevent targeted harassment.

Opponent Selection

  • Browse all eligible players (online and offline) within ±20 levels of your character.
  • Opponent list shows: Name, Level, Class, and [ONLINE] tag for currently connected players.
  • Already-attacked-today players are filtered out of the list.
  • Confirmation prompt warns that damage persists and shows gold steal percentage.

Asynchronous Combat

  • Defender is loaded from their saved data at full HP and AI-controlled (CharacterAI.Computer).
  • Defender's spells and learned abilities are restored so the AI can use them in combat.
  • Uses the existing CombatEngine.PlayerVsPlayer() system - same combat mechanics as NPC duels.
  • Defender's save data is never modified by the combat itself (only gold theft is applied atomically).

Gold Theft (Bidirectional)

  • Attacker wins: Steals 10% of the defender's gold on hand. Gold is atomically deducted from the defender's save via DeductGoldFromPlayer() (SQL json_set with MAX(0, ...)).
  • Defender wins: Steals 10% of the attacker's gold on hand. Gold is atomically added to the defender's save via new AddGoldToPlayer() method.
  • Gold stolen is recorded in the pvp_log table for leaderboard tracking.
  • Bank gold is safe from PvP theft (only gold on hand is at risk).

Persistent Damage & Death Handling

  • Attacker's HP/mana damage persists after combat - classic Usurper risk/reward. Attacker is autosaved after every fight.
  • PvP death handled in-Arena with appropriate messaging (no more "Monster defeats" counter incrementing from PvP losses):
    • 10% of attacker's gold stolen by defender
    • 10% XP loss (standard death penalty)
    • 25% gold loss (standard death penalty, applied after PvP theft)
    • Resurrected at Inn with half HP
    • Player is set alive before returning to game loop to prevent the standard HandleDeath() from double-triggering

XP Rewards

  • Attacker earns XP on victory, scaled by level difference (0.5x-2.0x multiplier).
  • XP range clamped to 25-5,000 to prevent extremes.
  • XP is handled by CombatEngine.DeterminePvPOutcome() - no double-counting.

Defender Notifications

  • When attacked, the defender receives an in-game message via SendMessage():
    • Win: [Arena] PlayerName attacked you in the Arena and won! They stole X of your gold.
    • Loss: [Arena] PlayerName attacked you in the Arena but your shadow defeated them! You gained X gold.
  • Messages appear at the defender's next input prompt via the existing chat/message system.

PvP Leaderboard

  • In-game leaderboard shows top 20 players ranked by wins.
  • Table columns: Rank, Name, Level, Class, W/L record, Gold Stolen.
  • Top 3 players get color highlighting (gold/white/yellow).

Fight History

  • View the last 10 PvP fights with attacker vs defender, winner highlighted, gold stolen amount.

Personal Stats

  • View your PvP kills, deaths, and win rate percentage (from PlayerStatistics.TotalPlayerKills/Deaths).

News Integration

  • PvP victories and defeats are posted to the shared news feed visible to all online players.
  • News messages include gold stolen amount when applicable.

While You Were Gone

  • On login, online players see a summary of events since their last session.
  • Shows PvP attacks against the player (wins/losses with gold stolen amounts).
  • Shows unread direct messages (up to 5).
  • Shows recent world news up to 10 items (level ups, boss kills, PvP fights, marriages, etc.).
  • Color-coded by event type (combat=red, pvp=yellow, politics=cyan, romance=magenta, etc.).
  • Only displayed when there are events to show; skipped silently on first login.
  • Data sourced from last_logout timestamp in players table, querying pvp_log, messages, and news tables.

PvP Achievements

  • Arena Initiate - Win your first PvP fight (Bronze, 10pts, 100g reward)
  • Arena Veteran - Win 10 PvP fights (Gold, 50pts, 2,000g reward)

Website PvP Leaderboard

  • PvP leaderboard section added to the website landing page between Hall of Fame and News Feed.
  • Shows: Rank, Player, Class, Level, Wins, Losses, Gold Stolen.
  • Top 3 ranks get gold/silver/bronze styling.
  • Data sourced from /api/stats endpoint.

Stats API

  • PvP leaderboard added to the GET /api/stats response as pvpLeaderboard array.
  • Each entry: rank, name, level, className, wins, losses, goldStolen.
  • Uses UNION ALL query joining pvp_log with players table, grouped by player.

NPC Lifecycle: Children, Aging & Natural Death

The world of Usurper is now truly alive. Married NPCs have children, children grow up and join the realm, adults age over time, and eventually die of old age. Age-related death is permanent -- no resurrection, no respawn. The soul moves on.

NPC Pregnancies

  • Married female NPCs (ages 18-45) have a 1% chance per world tick to become pregnant.
  • Dynamic pregnancy rate adjusts based on population:
    • 3% when population drops below 40 (to prevent extinction)
    • 0.5% when population exceeds 80 (to prevent overcrowding)
    • 1% at normal population levels
  • Maximum 4 children per couple.
  • Same-sex couples do not have natural pregnancies (keeping the medieval fantasy setting consistent).
  • Pregnancy announcements posted to the news feed.
  • Gestation period: ~7 real hours.

NPC Children

  • Children are born using the existing Child class and FamilySystem.
  • Birth announcements posted to the news feed.
  • Children age using the accelerated lifecycle rate (9.6 real hours = 1 game-year).
  • When a child reaches age 18, ConvertChildToNPC() creates a full NPC with:
    • Stats based on level (18-22 range) and racial modifiers
    • Soul-based trait inheritance from parents
    • Random class assignment
    • Coming-of-age news announcement
  • 80 fantasy names available (40 male, 40 female) with Roman numeral suffixes for duplicates.

Accelerated NPC Aging

  • NpcLifecycleHoursPerYear = 9.6 -- approximately 1 game-year every 9.6 real hours.
  • This gives a Human NPC (~75 year lifespan) approximately 30 real days from birth to natural death.
  • Elves live much longer (~80 real days), while Orcs/Gnolls cycle faster (~20-22 real days).
  • Age is computed from BirthDate using TotalHours / NpcLifecycleHoursPerYear -- drift-proof.

Race Lifespans

Race Max Age Real Days (approx)
Human 75 30
Hobbit 90 36
Elf 200 80
Half-Elf 120 48
Dwarf 150 60
Troll 60 24
Orc 55 22
Gnome 130 52
Gnoll 50 20
Mutant 65 26

Natural Death

  • When an NPC exceeds their race's lifespan, they die permanently.
  • IsAgedDeath = true -- prevents ProcessNPCRespawns() from ever resurrecting them.
  • Widowed spouses have marriage state cleared and receive a bereavement memory.
  • Natural death news posted to the feed.
  • Story NPCs (IsStoryNPC == true) are exempt to prevent breaking narrative quests.

Birthday Announcements

  • When an NPC's age increments (every ~9.6 hours), a birthday announcement is posted.
  • Uses proper ordinal suffixes ("21st", "42nd", "73rd", etc.).
  • ~6 birthday events per hour across all 60 NPCs (one every ~10 minutes).

NPC Divorce

  • Married NPC couples have a base 0.3% chance per world tick to divorce.
  • Personality-driven modifiers:
    • Low commitment (< 0.3) increases divorce chance by +0.5% per spouse
    • High flirtatiousness (> 0.7) increases divorce chance by +0.3% per spouse
    • Alignment mismatch (one good, one evil) adds +0.4%
    • Both high-commitment (> 0.7) reduces chance by 80%
  • On divorce: marriage state cleared, pregnancies cancelled, both NPCs free to remarry.
  • Divorce news posted to the feed.

Polyamorous Relationships

  • NPCs with RelationshipPreference.Polyamorous or OpenRelationship personality can seek additional partners while already married.
  • Both parties must be poly/open for a polyamorous union to form.
  • Uses the existing personality system -- these preferences were already defined but never used in marriage logic.
  • Distinct news: "X and Y have entered a polyamorous union!" vs "Wedding Bells!"

Affairs

  • Flirtatious married NPCs (Flirtatiousness > 0.6) have a 15% chance of conceiving with someone other than their spouse.
  • Unmarried flirtatious NPCs (> 0.5) can conceive casually with any compatible partner.
  • _pregnancyFathers dictionary tracks the actual father for affair babies (separate from SpouseName).
  • Affair partner selection: prefers other flirtatious NPCs, requires mutual attraction.
  • Scandal news: "Scandal! X and Y are having a secret affair!"

NPC Level-Up News

  • NPC level-ups now appear in the news feed with race and class info.
  • Format: "NpcName the Race Class has achieved Level X!"

Legacy Save Migration

  • Existing NPCs without BirthDate (all current saves) are assigned:
    • Random age between 18-50 if Age was not previously saved
    • Computed BirthDate from age: DateTime.Now.AddHours(-age * NpcLifecycleHoursPerYear)
  • Ensures seamless upgrade from pre-lifecycle saves.

The Living World (Website)

SSE Live Feed

  • Server-Sent Events (/api/feed) replace 30-second polling for instant delivery.
  • Separate NPC activity and player news streams pushed to browser clients.
  • 5-second server-side polling of SQLite with high-water mark tracking.
  • Auto-reconnect after 5 seconds on disconnect.

Lifecycle Event Color Coding

Event Color Pattern
Natural death Muted purple (#9966cc) "passed away", "soul moves on"
Combat death Red (#ff4444) dagger symbol
Pregnancy Soft pink (#ffaacc) "expecting a child"
Birth Bright pink (#ff88dd) "proud parents", "born"
Coming-of-age Teal (#44ddaa) "come of age", "joined the realm"
Birthday Gold (#ffdd44) "birthday", "celebrates their"
Level up Gold-orange (#ffaa00) "Level" + number
Divorce Dark red (#cc4444) "divorced"
Affair Hot pink (#ff4488) "affair", "Scandal"
Polyamory Violet (#dd66ff) "polyamorous"
Respawn Green (#88ff88) "returned from the realm of the dead"

Side-by-Side Feed Layout

  • NPC activity and player news feeds displayed in a CSS grid layout.
  • Each feed scrolls independently with its own SSE event stream.

Bug Fixes

Backspace in SSH/Online Mode

  • Backspace key sent raw \x7F/\x08 bytes through Console.ReadLine() in SSH mode, causing garbage characters in input. Fixed with custom ReadLineWithBackspace() that handles DEL/BS character-by-character and discards ANSI escape sequences.

Duplicate Players in PvP Arena & Leaderboards

  • Emergency autosave rows (emergency_*) appeared as duplicate entries in PvP opponent list, website leaderboard, and stats. Fixed by adding username NOT LIKE 'emergency_%' filter to GetAllPlayerSummaries(), GetAllPlayerNames(), and all website stats queries.

Website "Most Popular Class" Showing Unknown

  • NULL class_id from empty/test accounts winning the GROUP BY query. Fixed by adding json_extract(player_data, '$.player.class') IS NOT NULL filter.

PvP Double Kill/XP/Gold Counting

  • CombatEngine.DeterminePvPOutcome() already calls RecordPlayerKill(), awards XP, and awards gold. ArenaLocation.ProcessPvPResult() was duplicating all three. Fixed by removing duplicate calls from ArenaLocation.

PvP Gold Theft Never Worked

  • CreateCombatCharacterFromSave() never set opponent.Gold from playerData.Gold, so it defaulted to 0. Fixed by adding Gold = playerData.Gold to the character builder.

PvP Death Shows "Monster Defeats"

  • Dying in PvP triggered the standard HandleDeath() which incremented the "Monster defeats" counter. Fixed by handling death entirely in ArenaLocation.

Bidirectional Gold Theft

  • Only attacker-wins gold theft was implemented initially. Fixed by adding defender-wins gold theft via AddGoldToPlayer().

Database Changes

New Table: pvp_log

CREATE TABLE IF NOT EXISTS pvp_log (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    attacker TEXT NOT NULL,
    defender TEXT NOT NULL,
    attacker_level INTEGER NOT NULL,
    defender_level INTEGER NOT NULL,
    winner TEXT NOT NULL,
    gold_stolen INTEGER DEFAULT 0,
    xp_gained INTEGER DEFAULT 0,
    attacker_hp_remaining INTEGER DEFAULT 0,
    rounds INTEGER DEFAULT 0,
    created_at TEXT DEFAULT (datetime('now'))
);

Serialization

New NPCData Fields

public int Age { get; set; }
public DateTime BirthDate { get; set; } = DateTime.MinValue;
public bool IsAgedDeath { get; set; }
public DateTime? PregnancyDueDate { get; set; }

Game Balance Constants

MaxPvPAttacksPerDay = 5       // Daily attack limit
MinPvPLevel = 5               // Minimum level to enter Arena
PvPGoldStealPercent = 0.10    // 10% of loser's gold on hand
PvPLevelRangeLimit = 20       // ±20 levels for opponent eligibility
PvPMinXPReward = 25           // Minimum XP for a PvP win
PvPMaxXPReward = 5000         // Maximum XP for a PvP win

Files Changed

New Files

  • Scripts/Locations/ArenaLocation.cs - PvP arena location with opponent selection, combat, leaderboard, history, stats

Modified Files

  • Scripts/Core/GameConfig.cs - Arena = 501 enum, PvP constants, RaceLifespan dictionary, NpcLifecycleHoursPerYear, version 0.28.0
  • Scripts/Core/NPC.cs - BirthDate, IsAgedDeath, PregnancyDueDate properties; BirthDate calculation in constructor
  • Scripts/Core/GameEngine.cs - ShowWhileYouWereGone() login summary; restore lifecycle fields in RestoreNPCs() with migration
  • Scripts/Systems/IOnlineSaveBackend.cs - PvPLeaderboardEntry and PvPLogEntry data classes
  • Scripts/Systems/SqlSaveBackend.cs - pvp_log table, PvP query methods, AddGoldToPlayer(), "While You Were Gone" queries, emergency_% filter on player queries
  • Scripts/Systems/SaveDataStructures.cs - Age, BirthDate, IsAgedDeath, PregnancyDueDate on NPCData
  • Scripts/Systems/SaveSystem.cs - Serialize lifecycle fields in NPC save block
  • Scripts/Systems/WorldSimulator.cs - ProcessNPCAging(), ProcessNPCPregnancies(), HandleSpouseBereavement(), ProcessNPCDivorces(), birthday detection, affair system with _pregnancyFathers
  • Scripts/Systems/FamilySystem.cs - CreateNPCChild(), GenerateNPCChildName(), accelerated aging in ProcessDailyAging(), BirthDate carry-over in ConvertChildToNPC()
  • Scripts/Systems/NewsSystem.cs - WriteNaturalDeathNews(), WriteComingOfAgeNews(), WriteBirthdayNews(), WriteNPCLevelUpNews(), WriteAffairNews(), GetOrdinalSuffix()
  • Scripts/AI/EnhancedNPCBehaviors.cs - Polyamorous marriage support in AttemptNPCMarriage() and ExecuteNPCMarriage()
  • Scripts/UI/TerminalEmulator.cs - ReadLineWithBackspace() for SSH/online mode backspace handling
  • Scripts/Systems/LocationManager.cs - Arena registration and navigation
  • Scripts/Locations/MainStreetLocation.cs - [6] Arena (PvP) menu item (online mode only)
  • Scripts/Systems/AchievementSystem.cs - 2 PvP achievements (pvp_first_fight, pvp_10_wins)
  • web/index.html - PvP leaderboard, SSE live feed, lifecycle color coding, side-by-side layout
  • web/ssh-proxy.js - PvP leaderboard in stats API, SSE /api/feed endpoint, emergency_% filter
  • scripts-server/nginx-usurper.conf - SSE proxy configuration
  • scripts-server/usurper-world.service - Updated to --sim-interval 30 --npc-xp 3.0
  • README.md - Version bump and v0.28.0 changelog

Don't miss a new usurper-reborn release

NewReleases is sending notifications on new releases.