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

@@ -385,3 +385,61 @@ type MercenaryRepo interface {
GetGuildHuntCatsUsed(charID uint32) ([]GuildHuntCatUsage, error)
GetGuildAirou(guildID uint32) ([][]byte, error)
}
// Tournament represents a tournament schedule entry.
type Tournament struct {
ID uint32 `db:"id"`
Name string `db:"name"`
StartTime int64 `db:"start_time"`
EntryEnd int64 `db:"entry_end"`
RankingEnd int64 `db:"ranking_end"`
RewardEnd int64 `db:"reward_end"`
}
// TournamentCup represents a competition category within a tournament.
type TournamentCup struct {
ID uint32 `db:"id"`
CupGroup int16 `db:"cup_group"`
CupType int16 `db:"cup_type"`
Unk int16 `db:"unk"`
Name string `db:"name"`
Description string `db:"description"`
}
// TournamentSubEvent represents a specific hunt/fish target within a cup group.
type TournamentSubEvent struct {
ID uint32 `db:"id"`
CupGroup int16 `db:"cup_group"`
EventSubType int16 `db:"event_sub_type"`
QuestFileID uint32 `db:"quest_file_id"`
Name string `db:"name"`
}
// TournamentRankEntry is a single entry in a leaderboard.
type TournamentRankEntry struct {
CharID uint32
Rank uint32
Grade uint16
HR uint16
GR uint16
CharName string
GuildName string
}
// TournamentEntry represents a player's registration for a tournament.
type TournamentEntry struct {
ID uint32 `db:"id"`
CharID uint32 `db:"char_id"`
TournamentID uint32 `db:"tournament_id"`
}
// TournamentRepo defines the contract for tournament schedule and result data access.
type TournamentRepo interface {
GetActive(now int64) (*Tournament, error)
GetCups(tournamentID uint32) ([]TournamentCup, error)
GetSubEvents() ([]TournamentSubEvent, error)
Register(charID, tournamentID uint32) (entryID uint32, err error)
GetEntry(charID, tournamentID uint32) (*TournamentEntry, error)
SubmitResult(charID, tournamentID, eventID, questSlot, stageHandle uint32) error
GetLeaderboard(eventID uint32) ([]TournamentRankEntry, error)
}