Skip to content

feat(tag): add an always-open tag game to the lobby - #490

Merged
TheMeinerLP merged 12 commits into
mainfrom
feat/lobby-tag
Oct 24, 2026
Merged

TheMeinerLP merged 12 commits into
mainfrom
feat/lobby-tag

Conversation

@TheMeinerLP

Copy link
Copy Markdown
Contributor

Summary

Adds tag (Fangen): an always-open tag game in the lobby for players who opt in. One player is the tagger, a hit passes the role, and there are no rounds, timers or winners. Nothing beyond vanilla is needed (no resource pack, no scoreboard team).

Depends on lobby-settings (merged in #480): the opt-in is the typed setting titan:tag.enabled (SettingDefault.OPT_IN), read and written through the core PlayerSettings port only. The column depends on core only.

Gameplay

  • Participation: off by default. The player switches it in the settings menu or with /tag; both write the same setting.
  • Active: setting on, in a lobby instance, not on a jump and run and not in a hand-over to another server (new core event LobbyPlayerBusyEvent, sent by jumprun and portal).
  • One tagger: from two active players on there is exactly one; below two there is none and the action bar says so. When the tagger leaves, a random active player takes over.
  • Hit: the tagger's melee hit on another active player passes the role (title and sound for the new tagger, hurt animation for the target). Damage between active players is cancelled, so no damage and no knockback. The player who was just tagged cannot tag back for 5 s; the lock is per pair.
  • Idle: the tagger loses the role after 120 s without movement, hit or input.
  • Visibility: the tagger glows (default colour), red particles every 10 ticks for the instance, the action bar of every active player names the tagger.
  • Elytra: hits during a glide do not pass the role.

Settings, commands, telemetry

  • Option titan:tag.enabled registered in SettingsRegistry by the column; name and description added to titan/settings/messages_{de,en}.properties (the only change to the settings bundles, keys of this option only).
  • /tag (English only, no alias) switches the same setting. Console gets "Only players can play tag."; a failed write keeps the value and tells the player.
  • Counters titan.tag.tags{result=tagged|cooldown} and titan.tag.toggles{state=on|off}, no player id, no span.

Tests

  • Unit: TagGameTest, TagCooldownTest, TagOptInTest, TagCommandTest (console and player), TagTelemetryTest, TagMessagesBundleTest, LobbyPlayerBusyEventTest.
  • Env (env.tick(), fresh module per test, injected AdjustableClock, seeded random): TagModuleIntegrationTest (damage, passing, title, reverse hit, instance change, disconnect, action bar, particles per instance, no team, elytra, busy event), TagModuleLifecycleTest (stop removes glow and leaves no listener), JumprunBusyEventTest, and PortalModuleTest#handOverSaysBusy.
  • Architecture: ColumnArchitectureTest and MaintainabilityArchitectureTest (with a new, empty freeze store for the module's package cycles).
  • ./gradlew spotlessApply build --continue --offline green.

Deviations from the change

  • EntityAttackEvent is not cancellable in Minestom 2026.10.07-26.2. The design's cancel-the-attack plan is replaced by a cancelled EntityDamageEvent between active players. The change artifacts were adjusted in 2edb8f3.
  • Dance and sit have no state on the tag side, so there are no module tests for them. Their interplay is in the manual check below.
  • The shared AdjustableClock from core testFixtures is not on main yet; the column keeps its own copy, as the other columns do.
  • The freeze store features/tag/src/test/resources/archunit_store is created by hand from the tickle store (the test refuses to create one). It holds no violations; CODEOWNERS review please.

Open items

  • Manual check on a test server (tasks 8.2, not run): settings menu switch, /tag, glow, particles and action bar, hits without damage, Butterfly name prefixes unchanged.
  • Configuration: the settings module is off by default (settings.enabled: false). Until it is on (with otis.url), the menu entry does not show and the choice is only kept in memory. /tag works either way.
  • openspec validate --strict lobby-tag passes with style warnings only (SHALL/MUST wording), the same as lobby-settings.
  • Not merged; release and merge are left to the maintainers.

https://claude.ai/code/session_01AM6yGQ33vvJqWg39vCAcYt

@TheMeinerLP
TheMeinerLP requested a review from a team as a code owner October 24, 2026 10:43
@github-actions

github-actions Bot commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Test results

  832 files    832 suites   1h 2m 3s ⏱️
3 166 tests 3 165 ✅ 1 💤 0 ❌
6 358 runs  6 356 ✅ 2 💤 0 ❌

Results for commit 7710fd9.

♻️ This comment has been updated with latest results.

Minestom 2026.10.07-26.2 does not make EntityAttackEvent cancellable,
so the tag rules cannot abort an attack. The no-damage guard moves to
EntityDamageEvent, and a settings write error is reported through the
PlayerSettings#set exception path.

Claude-Session: https://claude.ai/code/session_01AM6yGQ33vvJqWg39vCAcYt
Core event fired when a player becomes busy with a run or a hand-over, so the tag game can step out of the round without one column calling another.

Claude-Session: https://claude.ai/code/session_01AM6yGQ33vvJqWg39vCAcYt
The tag column reads titan:tag.enabled through the core PlayerSettings port. /tag flips the same setting the settings menu writes; a failed write keeps the value and tells the player. Option names and descriptions for the settings menu are added in both languages.

Claude-Session: https://claude.ai/code/session_01AM6yGQ33vvJqWg39vCAcYt
One tagger per lobby as soon as two players are active, passed on by a hit and handed over after two minutes of tagger inactivity. The reverse hit within five seconds is refused for that pair only. The rules take time and randomness from the caller, so every case runs without a server or a wait.

Claude-Session: https://claude.ai/code/session_01AM6yGQ33vvJqWg39vCAcYt
The tag module follows the player's setting, the instance they are in, the lobby busy event and elytra flight. Damage between two players in the game is cancelled, the tagger's hit passes the role, and the tagger glows with red particles and an action bar for everyone in the game. No scoreboard team is created, so name prefixes of other modules stay intact.

Claude-Session: https://claude.ai/code/session_01AM6yGQ33vvJqWg39vCAcYt
A jump and run reports busy when it starts and free when it ends; a hand-over to another server reports busy when it begins. The tag game steps the player out on that event, so no column calls another.

Claude-Session: https://claude.ai/code/session_01AM6yGQ33vvJqWg39vCAcYt
Two counters: titan.tag.tags by result (tagged, cooldown) and titan.tag.toggles by state (on, off). Neither carries a player id.

Claude-Session: https://claude.ai/code/session_01AM6yGQ33vvJqWg39vCAcYt
The lobby modules page gets a section for the tag game with its opt-in, command, rules, interactions and telemetry, and the README lists /tag.

Claude-Session: https://claude.ai/code/session_01AM6yGQ33vvJqWg39vCAcYt
The tag module gets the shared maintainability rules and its package cycle baseline, which is empty. The settings bundle test checks the fixed keys and that every option text has a name and a description, since option keys now come from the columns.

Claude-Session: https://claude.ai/code/session_01AM6yGQ33vvJqWg39vCAcYt
Spotless normalises the license header of the new files and the import order.

Claude-Session: https://claude.ai/code/session_01AM6yGQ33vvJqWg39vCAcYt
@TheMeinerLP
TheMeinerLP merged commit e1f56d2 into main Oct 24, 2026
9 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