Skip to content

Refactor Player from generic class to inheritance hierarchy#285

Merged
gausie merged 3 commits intomainfrom
player-class-refactor
Mar 28, 2026
Merged

Refactor Player from generic class to inheritance hierarchy#285
gausie merged 3 commits intomainfrom
player-class-refactor

Conversation

@gausie
Copy link
Copy Markdown
Contributor

@gausie gausie commented Mar 28, 2026

Summary

  • Replaces Player<IsFull extends boolean> generic with a Player + Player.Profiled class hierarchy (inspired by Discord.js patterns)
  • Adds a Players manager domain (kolClient.players.resolve() / .fetch() / .getNameFromId()) that owns caching and lookup logic
  • Deletes Cache.ts (absorbed into Players)

Design

// Base Player — lightweight identity from search/chat
const player: Player = await kolClient.players.resolve("gausie");
player.id;        // number
player.name;      // string
player.toString() // "gausie (#1197090)"

// Player.Profiled — full data from profile page, extends Player
const profiled: Player.Profiled = await kolClient.players.fetch("gausie");
profiled.ascensions;  // number (guaranteed present)
profiled.avatar;      // string

// Narrowing via instanceof
if (player instanceof Player.Profiled) {
  player.ascensions; // works
}

// Upgrade: Player → Player.Profiled
const profiled = await player.fetch(); // Player.Profiled | null
const same = await profiled.fetch();   // Player.Profiled (no null — already profiled)

What's better vs the generic approach:

  • No conditional types, no as unknown as Player<true> casts, no If<> helper
  • instanceof narrowing — standard TypeScript, no custom type guards
  • Covariant return type on fetch() — calling on Player.Profiled returns Player.Profiled (not null)
  • Surfaces a real bug: winner.player.createdDate in Raffle was always null (removed from the createRaffleWin call)

gausie added 3 commits March 27, 2026 10:09
Replace Player<IsFull> conditional type pattern with Player + Player.Profiled
class hierarchy using namespace merge. Player is the lightweight identity
(id + name), Player.Profiled extends it with full profile data.

Add Players domain class as a manager with resolve/fetch/cache, replacing
the PlayerCache. Remove accountCreationDate from createRaffleWin since it
was always null (Raffle only has player identity, not profile data).
- Make Player.client truly private (#client)
- Rename parseWhois to parseNameFromWhois for clarity
- Simplify Players.resolve() to a single expression
- Add Players domain tests (resolve, fetch, caching, getNameFromId)
identifyPlayer now returns Player instead of Player.Profiled, saving a
showplayer.php request for done, brainiac, and skills commands. whois
calls .fetch() itself since it needs the full profile.
@gausie gausie merged commit 829091d into main Mar 28, 2026
2 checks passed
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.

1 participant