Skip to content

First feature release of the zh-TW fork. Backend hardening, complete redesign of the in-game editor, and a proper internationalisation pipeline. - #4

Open
EvansGoethe wants to merge 20 commits into
Smarts-Hub:masterfrom
EvansGoethe:master
Open

First feature release of the zh-TW fork. Backend hardening, complete redesign of the in-game editor, and a proper internationalisation pipeline.#4
EvansGoethe wants to merge 20 commits into
Smarts-Hub:masterfrom
EvansGoethe:master

Conversation

@EvansGoethe

@EvansGoethe EvansGoethe commented Jun 28, 2026

Copy link
Copy Markdown

First feature release of the zh-TW fork. Backend hardening, complete redesign of the in-game editor, and a proper internationalisation pipeline.

…ed editor GUI

Phase 1 — backend optimizations:
- KothTicker now isolates per-koth tick exceptions
- RefreshInsideKothService uses world.getNearbyEntitiesByType
  instead of iterating all online players per koth per second
- OfflineRewardStorage persists rewards for offline winners
  (YAML + rtag Base64), auto-delivered on next join with drop protection
- Reward drops use Item#setOwner so other players cannot steal
- Plugin Logger replaces Bukkit.getLogger across the project
- KothYamlSaver gains async saveToYamlAsync for editor flows
- NotifyChannel enum + NotifyType.channels() lay groundwork for
  arbitrary channel combinations

Phase 2 — disable team mechanism (surface layer):
- TeamCommand is no longer registered
- KothBuilder forces solo=true and ignores team-related YAML keys
- Team toggles removed from the create/edit GUI
- Underlying API/events/hooks preserved for future re-enable

Phase 3 — CreateKothGui redesign:
- 6-row, 3-tab layout (Basic / Time & Scoreboard / Rewards & Commands)
- Footer with Test, Copy-from-other-KOTH, validation summary, Save
- Inline red/green field validation with translated error messages
- ID character validation + captureTime <= maxTime guard
- Editing existing KOTH detected automatically; save button distinguishes
  Create vs Edit (Slime Ball vs Anvil) and unregisters old before re-registration
- Slot magic numbers replaced with named constants
- chatPromptItem / numericItem / toggleItem factories cut boilerplate
- KothLoreBoardPreview now caches per-player and invalidates on edit
- One-click 'Test this KOTH' button

Deferred for follow-up:
- Anvil GUI text input (needs custom InventoryType.ANVIL wrapper)
- Per-line scoreboard editor (needs index-based KothTempData API)
- README.md is now the Traditional Chinese landing page covering the
  fork's localization, backend optimizations, and redesigned editor GUI
- Original Smarts Hub README preserved verbatim as README_author.md,
  linked prominently at the top of the new README
Phase 3.2 — replace 'close window + chat input' with Anvil GUI:
- Add AnvilTextInput utility wrapping Bukkit InventoryType.ANVIL
- Listens for PrepareAnvilEvent to keep result slot synced with rename text
- Listens for click on result slot (confirm) and inventory close (cancel)
- Uses AnvilView#getRenameText / setRepairCost (Paper 1.21 API)
- CreateKothGui swaps chatPromptItem -> promptItem with a Consumer setter
  and an optional validator; ID, display name, both scoreboard titles
  now open Anvil with live validation feedback

Phase 3.3 — per-line scoreboard editor:
- Add KothTempData index-based line API (getLines, setLineAt,
  removeLineAt, insertLine, moveLine)
- New ScoreboardLineEditorGui: 6-row paginated layout, one line per row
  with edit / up / down / delete buttons, header for add / clear / back
- Edit and add go through AnvilTextInput
- CreateKothGui's scoreboard line items now open this editor instead of
  the 'right click = add, left click = remove last, shift = clear' hack
The new editor code added in the previous release was hardcoded zh-TW,
which broke any non-Chinese localisation. This commit moves every
user-facing string in:

  - CreateKothGui (tabbed editor, validation, footer, all items)
  - ScoreboardLineEditorGui (header, per-line row, pagination)
  - KothLoreBoardPreview (preview hint and content lines)

behind keys in messages.yml. English defaults live in messages.yml;
the fork's Traditional Chinese translations live in messages_zh_tw.yml
under the same key tree.

Changes:
- MessageRepository gains fmt(path, ...args) and fmtList(path, ...args)
  that substitute {0}, {1}, ... positional placeholders.
- New 'gui:' tree in messages.yml / messages_zh_tw.yml covering
  gui.common, gui.create-koth, gui.scoreboard-editor, gui.board-preview,
  gui.reward.
- CreateKothGui constructor now takes MessageRepository and an injected
  KothLoreBoardPreview (shared with ScoreboardLineEditorGui so the
  preview cache is consistent).
- OfflineRewardStorage keeps an English fallback message: it is
  constructed before the GUI services are wired and pulling
  MessageRepository through that path would just complicate ordering.

Deferred to a follow-up: the legacy GUIs created earlier
(EditKothListGui, AddPhysicalRewardGui, CommandGui, CreateSchedulerGui)
still contain hardcoded zh-TW strings; they will be migrated to the
same i18n pattern in a separate pass.
Completes the i18n pass by moving the four GUIs added during the
earlier Gemini work to the same key-based pattern as the new editor:

  - EditKothListGui  (~7 strings)
  - AddPhysicalRewardGui (~12 strings)
  - CommandGui  (~14 strings, includes per-type lore + chat prompt)
  - CreateSchedulerGui (~18 strings, includes 7 weekday names)

All four constructors now take MessageRepository alongside MessageParser
and SHKoth#guis() passes it through.

Adds to messages.yml / messages_zh_tw.yml:
  gui.edit-list.*
  gui.add-reward.*
  gui.command-editor.*    (with type-winner/start/end variant keys)
  gui.scheduler.*         (with day-monday..day-sunday variant keys)

With this commit there are no hardcoded zh-TW strings left in any of
the editor flow. Server messages, broadcast, title, actionbar, sound,
and bossbar were already i18n-aware before this pass.
@EvansGoethe EvansGoethe changed the title zh_tw translation First feature release of the zh-TW fork. Backend hardening, complete redesign of the in-game editor, and a proper internationalisation pipeline. Jun 30, 2026

@yiyoperez yiyoperez left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR will be marked as a draft untill change suggestions are applied and there is not features which are currently work in progress.

Edit: typo.

import java.util.Set;

/**
* KOTH 通知可用的個別頻道。未來 NotifyService 可依此 EnumSet 過濾。

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Classes & Methods comments should be in English so others contributors can understand.

guiService.openCreateKothGui(actor.asPlayer());
}

@Subcommand({"editor", "edit", "gui"})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Editor seems to not be working properly, tab items overlap slots and they keep showing in other tabs.

} else if (offlineStorage != null) {
offlineStorage.add(winnerId, item);
} else {
logger().warning("離線獎勵儲存未初始化,獎勵遺失 (winner=" + winnerId + ")");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Log must be in English.

new KothCommand(kothRegistry, notifyService, configService, guiService));
// 組隊機制已於此版本停用:TeamCommand 不再註冊,相關 GUI toggle 已下架。
// 底層 service/listener 仍保留以維持 API 相容;若未來要重新啟用,恢復下行即可。
// lamp.register(new TeamCommand(teamHandlingService, teamInviteService, teamInformationService));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Plugin uses an internal team system, to keep it active and dont clash into others team plugins hooks, it could be a config option to disable the command.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is a WIP I undestand going back to your language to keep the development, must be configurable to spect a merge.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When testing option rename didn't work even when plugin was registering the listeners, also item can be moved and kept in the player inventory, closing doesn't open creation, editor gui back.

Might be WIP?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mentioned early on another commit, tab items overlap/don't get replaced properly.

@yiyoperez
yiyoperez marked this pull request as draft July 3, 2026 02:09
EvansGoethe and others added 7 commits July 5, 2026 10:51
The previous AnvilTextInput built an inventory via
Bukkit.createInventory(ANVIL), which has no anvil menu backend: the rename
field did not apply, the paper item could be dragged into the player's
inventory, and closing did not reliably return to the editor.

Switch to net.wesjd:anvilgui (relocated into the plugin's libs package),
which opens a genuine anvil menu, cancels item movement, and manages its
own listeners. Confirm and cancel both re-open the caller on the next tick.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017KKymHBBoAH2xx8X9N5aBD
…ub#4 P0-3)

Language policy for upstream: developer-facing text (comments, Javadoc,
log/exception messages) is English; player-facing text goes through
MessageRepository. Translate all remaining Traditional Chinese in dev-facing
strings across the reward, koth, listener, GUI-cache and API guideline
classes. NotifyType display labels are reduced to plain English ("Title",
"Chat", "Bossbar", "All") since they surface in the editor GUI.

After this change a repo-wide scan finds zero CJK characters in Java source;
only the zh_tw language resource files retain Chinese, as intended.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017KKymHBBoAH2xx8X9N5aBD
…ap (PR Smarts-Hub#4 P0-1, P0-4)

P0-4 — keep the internal team system, make disabling it a config option:
- Add hooks.yml `internal-team.disable-command` (default false). When true,
  the built-in team command is not registered and the team-mode editor
  toggles are hidden. All team services, listeners, hooks and API stay
  active so other team plugins can still integrate.
- KothBuilder: stop hardcoding `isSolo = true`. Read `solo-koth`,
  `deny-entry-if-not-in-team` and `create-team-if-not-exists` from the KOTH
  config — the keys the template and the editor's YamlSaver actually write
  (this also fixes the pre-existing mismatch where the builder read
  `solo-mode`, a key nothing writes).
- Restore the solo/team, deny-entry and auto-create-team toggles in the
  editor's Basic tab (row 4), gated on the same config option. New strings
  are routed through MessageRepository with English defaults + zh_tw
  overrides.

P0-1 — editor tab overlap: refreshAll() now calls gui.clearItems() to clear
triumph-gui's logical item map, not just the backing inventory, so items
from a previous tab no longer bleed into the next one.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017KKymHBBoAH2xx8X9N5aBD
Mark all five PR Smarts-Hub#4 P0 items landed and build-verified; note the KothBuilder
config-key fix and the remaining in-game verification + P1 follow-ups.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017KKymHBBoAH2xx8X9N5aBD
… can capture

Previously the whole teamless-player branch was gated on denyEnterWithoutTeam,
so with deny disabled a teamless player could walk into a team KOTH but the
tally could never resolve them to a team and capture never started. Now a
team-mode KOTH always tries to auto-assign a team first (when
create-team-if-not-exists allows it) and only then applies the deny rule.

Verified in-game on Paper 1.21.1: teamless player enters a team KOTH with
create-team-if-not-exists=true, gets an internal team, capture starts and
resolves a winner.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@EvansGoethe

Copy link
Copy Markdown
Author

Thanks for the detailed review, @yiyoperez — this was really helpful. I've gone through all seven points; here's how each is addressed. The changes are split into focused commits so they're easy to audit.

Summary of commits

  • fix(gui): replace fake anvil input with AnvilGUI — P0-2
  • refactor(i18n): English-only developer comments and logs — P0-3
  • feat(team): config-gated team command + editor toggles; fix tab overlap — P0-1, P0-4
  • language keys for player-facing strings — P0-5

1. AnvilTextInput — rename doesn't apply, item can be taken, close doesn't return

You were right that this was effectively WIP. The root cause was building the menu with Bukkit.createInventory(player, InventoryType.ANVIL), which produces an anvil without a menu backend — so the vanilla rename/repair handling never runs, PrepareAnvilEvent is unreliable, and there was no InventoryDragEvent cancel, so the paper item could be dragged out.

Rather than hand-roll the container, I switched to the maintained net.wesjd:anvilgui library (shaded + relocated into dev.smartshub.shkoth.libs.anvilgui). It opens a genuine anvil, cancels all item movement, and manages its own listeners. Confirm and cancel now both reliably re-open the caller on the next tick.

2 & 7. Editor tabs overlap / tabs not properly replaced

Confirmed. refreshAll() only called gui.getInventory().clear(), which clears the backing Bukkit inventory but not triumph-gui's logical GuiItem map — so update() repopulated stale items from the previous tab, and the background-fill (getGuiItem(slot) == null) skipped those slots, letting old items bleed through.

Fixed by calling gui.clearItems() (clears triumph-gui's item map) before re-registering the active tab's content.

3. GrantRewardsService — log messages should be English

Done, and I took the opportunity to apply this consistently project-wide.

NotifyChannel (point 1 in the thread) + general policy

Adopted an explicit language policy: developer-facing text (comments, Javadoc, log/exception messages) is English; player-facing text goes through MessageRepository. All remaining non-English developer strings were translated (reward services, koth ticker/refresh, YAML saver, listeners, GUI cache, and the NotifyChannel/NotifyType Javadoc). A repo-wide scan now finds zero non-Latin characters in Java source; only the optional zh_tw language resource files contain localized text.

4. SHKoth:250 — team disable should be a config option, keep the internal system

Reworked exactly along these lines:

  • The internal team system (services, listeners, events, hooks, API) is always active — nothing is hard-removed, so integrations with other team plugins keep working.
  • Added internal-team.disable-command in hooks.yml (default false). It only controls whether the built-in team command is registered, and hides the team-mode editor toggles to match. Servers managing teams via another plugin can hide the command without disabling the underlying system.
  • Removed the previous hardcoded isSolo = true in KothBuilder; solo/team is config-driven again. While doing this I noticed the builder was reading solo-mode, but the KOTH template and the editor's YAML writer both use solo-koth (likewise create-team-if-not-exists), so the config value was effectively ignored. The builder now reads the same keys the template/editor write, so solo-koth actually takes effect. Happy to split that key fix into its own commit if you'd prefer.

5. AddPhysicalRewardGui — language must be configurable

The hardcoded strings there (and in the other new editor GUIs) are now message keys with English defaults, overridable per language (a zh_tw set is provided as an example). The one storage-layer message (OfflineRewardStorage) keeps a safe English fallback because that class is constructed before the message services are wired.


All of the above has now been verified in-game on a clean Paper 1.21.1 (build 133) server, including the two flows you got stuck on:

  • Tab switching: cycled Basic → Time → Reward → Basic while dumping every slot each step — no stale items from the previous tab remain.
  • Anvil input: opens a genuine anvil menu; typing applies, confirming via the output slot returns to the editor with the new value shown, the input paper can't be taken (normal click or shift-click), and closing with ESC cancels and returns to the editor.
  • Team gate: with disable-command: false the /team command registers and the editor shows the Solo/Deny toggles; with true the command is gone, the toggles are hidden, and /koth is unaffected.
  • Teamless entry follow-up: while testing I found the teamless-entry branch was still gated on deny-entry-if-not-in-team, so with deny disabled a teamless player could stand in a team KOTH without capture ever starting (the tally can't resolve them to a team). Fixed in a follow-up commit: a team-mode KOTH now auto-assigns a team first (when create-team-if-not-exists allows) and only then applies the deny rule. Verified end-to-end: teamless player enters, gets an internal team, capture starts and a winner resolves.

Two pre-existing upstream observations from testing, outside this PR's scope (happy to open separate issues): commands-perform.start/end are loaded and saved but never dispatched anywhere (only to-winners runs), and %player% in to-winners is only resolved through PlaceholderAPI even though the bundled template uses it as an example.

Let me know if you'd like any of the above restructured, and thanks again for the thorough look.

@EvansGoethe
EvansGoethe marked this pull request as ready for review July 7, 2026 08:26
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.

2 participants