feat(tournament): implement hunting tournament system end-to-end

Wire format for MsgMhfEnterTournamentQuest (0x00D2) derived from
mhfo-hd.dll binary analysis (FUN_114f4280). Five new tables back
the full lifecycle: schedule, cups, sub-events, player registrations,
and run submissions. All six tournament handlers are now DB-driven:

- EnumerateRanking: returns active tournament schedule with cups and
  sub-events; computes phase state byte from timestamps
- EnumerateOrder: returns per-event leaderboard ranked by submission
  time, with SJIS-encoded character and guild names
- InfoTournament: exposes tournament detail and player registration
  state across all three query types
- EntryTournament: registers player and returns entry handle used by
  the client in the subsequent EnterTournamentQuest packet
- EnterTournamentQuest: parses the previously-unimplemented packet and
  records the run in tournament_results
- AcquireTournament: stubs rewards (item IDs not yet reversed)

Seed data (TournamentDefaults.sql) reproduces tournament #150 cups and
sub-events so a fresh install has a working tournament immediately.
This commit is contained in:
Houmgaor
2026-03-22 14:30:37 +01:00
parent 5ee9a0e635
commit c714374289
17 changed files with 674 additions and 161 deletions

View File

@@ -0,0 +1,48 @@
BEGIN;
CREATE TABLE IF NOT EXISTS tournaments (
id SERIAL PRIMARY KEY,
name VARCHAR(64) NOT NULL,
start_time BIGINT NOT NULL,
entry_end BIGINT NOT NULL,
ranking_end BIGINT NOT NULL,
reward_end BIGINT NOT NULL
);
CREATE TABLE IF NOT EXISTS tournament_cups (
id SERIAL PRIMARY KEY,
tournament_id INTEGER NOT NULL REFERENCES tournaments(id) ON DELETE CASCADE,
cup_group SMALLINT NOT NULL,
cup_type SMALLINT NOT NULL,
unk SMALLINT NOT NULL DEFAULT 0,
name VARCHAR(64) NOT NULL,
description TEXT NOT NULL DEFAULT ''
);
CREATE TABLE IF NOT EXISTS tournament_sub_events (
id SERIAL PRIMARY KEY,
cup_group SMALLINT NOT NULL,
event_sub_type SMALLINT NOT NULL DEFAULT 0,
quest_file_id INTEGER NOT NULL DEFAULT 0,
name VARCHAR(64) NOT NULL
);
CREATE TABLE IF NOT EXISTS tournament_entries (
id SERIAL PRIMARY KEY,
char_id INTEGER NOT NULL REFERENCES characters(id) ON DELETE CASCADE,
tournament_id INTEGER NOT NULL REFERENCES tournaments(id) ON DELETE CASCADE,
registered_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE (char_id, tournament_id)
);
CREATE TABLE IF NOT EXISTS tournament_results (
id SERIAL PRIMARY KEY,
char_id INTEGER NOT NULL REFERENCES characters(id) ON DELETE CASCADE,
tournament_id INTEGER NOT NULL REFERENCES tournaments(id) ON DELETE CASCADE,
event_id INTEGER NOT NULL,
quest_slot INTEGER NOT NULL DEFAULT 0,
stage_handle INTEGER NOT NULL DEFAULT 0,
submitted_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
COMMIT;