CobblemonQuests
Drop-in daily & weekly quest system with configurable rewards, streaks, milestones and hidden quests.
Список изменений
[1.8.0] — 2026-04-23 — "Audit + Hardening Pass"
Six-agent audit of the entire codebase (REPEATABLE coverage, config round-trip, switch exhaustiveness + null safety, GUI navigation, admin / audit surface, i18n keys). Fourteen fixes across four rounds. Version bumped to 1.8.0 because this touches the public config schema (new sections now actually persisted).
Fixed — Config round-trip erasure (BLOCKERS)
effects.*andhud.*were public POJO fields with defaults, butConfigLoadernever read them andConfigWriternever wrote them. The firstConfigWriter.save()(triggered by any admin config GUI) erased every admin-tuned sound ID / volume / pitch / show-title flag and every HUD setting from the on-disk file. Round-tripped now; bundleddefault_config.confincludes theeffects { }andhud { }sections so admins discover them on first install.event-hookswas read byConfigLoaderbut never written byConfigWriter. Admin-authored hook commands vanished the first time anyone saved a different config section from the GUI. Now round-tripped with a comment block.quest-text.category-formatwas defined inGuiConfig.QuestTextbutGuiConfigLoader.loadMainMenunever read it frommain_menu.conf. Admins couldn't customize the category/rarity line on quest tiles. Now loaded.
Fixed — Scheduler null-deref crashes during lifecycle windows
QuestsMainGui,QuestsHubGui,DailyQuestsGui,WeeklyQuestsGuiall calledscheduler().nextDailyReset()/nextWeeklyReset()unguarded. BetweenSERVER_STOPPINGand the nextSERVER_STARTED,scheduleris null — a player still in a GUI crashed the GUI render with an NPE (and in some cases a mixin-side exception cascaded).- New null-safe accessors
nextDailyResetOrFallback()/nextWeeklyResetOrFallback()onCobblemonQuests. Return a sane 24h / 7d future instant so the UI renders a bogus-but-harmless timer instead of crashing. All six call sites switched.
Fixed — QuestDetailGui back routed to the wrong menu
- Same shape as the
RepeatableQuestsGuiback bug fixed in 1.7.9.QuestDetailGuihardcoded back toQuestsHubGuiregardless of caller. Opening detail fromQuestsMainGui,DailyQuestsGui,WeeklyQuestsGui,RepeatableQuestsGui,HiddenQuestsGui,ChainDetailGui, orQuestAdminActionGuithen clicking back dumped the player into the config-driven hub they never opened. QuestDetailGuinow takes an optionalRunnable backAction. Every caller (all eight) passes a back lambda that reopens the caller. Legacy one-arg constructor kept for back-compat.
Fixed — QuestAdminCommand.complete missed REPEATABLE
- When an admin force-completed a REPEATABLE quest, the command
incremented
totalCompletedbut didn't bumprepeatableCompletions. The per-id farming counter shown in the Repeatable GUI header and future leaderboards undercounted admin completions.
Fixed — PermissionHelper silently swallowed provider failures
catch (Throwable)fell back to op-level without logging. A broken LuckPerms (misconfig, missing mod, class-load failure) silently gave every op unrestricted access AND prevented the admin from noticing. Now logs a WARN once per JVM with the node name and exception so the signal reacheslatest.log.
Fixed — Chain step picker allowed REPEATABLE
StepPickerGui.filteredAndSortedreturned every registered quest. An admin building a chain could pick a REPEATABLE step, which would get stuck inactiveHiddensand break the chain (the advance hook fires on claim, but REPEATABLE claims reset in place — the step never "leaves" the active list). Now filtered out along with NORMAL (internal-only type) at the source.
Fixed — Missing i18n key
notification.community_goal_completewas referenced byCommunityGoalManager.java:128, 142but absent fromen_us.json— players saw rawnotification.community_goal_completeinstead of the localized banner. Key added.
Added — Audit log entries for previously silent state changes
/cq admin reloadnow auditsADMIN_RELOAD./cq admin goal set/stop/reset/progressnow auditADMIN_GOAL_SET/ADMIN_GOAL_STOP/ADMIN_GOAL_RESET/ADMIN_GOAL_PROGRESSwith the relevant detail. Admin-initiated community-goal mutations are now fully traceable incobblemonquests-audit.log.
Added — BaseGui no-permission message localizable
BaseGui.open()hardcodedText.literal("No permission."). Now reads fromgui.no_permissionwith MiniMessage rendering, so server owners can rebrand / translate the rejection without patching the jar.
[1.7.9] — 2026-04-23 — "Repeatable Menu Config + Correct Back Routing"
Added — gui/repeatable_menu.conf
- Full customization of the Repeatable Quests viewer, matching the
pattern of
leaderboard_menu.conf/stats_menu.conf. Admins can now retitle the menu, resize it (1–6 rows), change the filler item / background color, relocate every button, edit every label / lore / glow, change the empty-state message, and customize the missing-quest placeholder (for orphaned quest ids after a.confdelete). - New
GuiConfig.RepeatableMenumodel +GuiConfigLoader.loadRepeatableMenu- bundled
default_gui/repeatable_menu.conf.
- bundled
- Configurable:
title,rows,background-color,filler-item,max-per-page,quest-slots[], and button blocks forheader,claim-all,prev-page,page-info,next-page,back,close,empty-state,missing-quest, pluscustom-buttons. Button-lore placeholders:{available},{lifetime_runs},{ready},{page},{pages},{quest_id}. RepeatableQuestsGuiis now fully config-driven — no more hardcoded slot constants. Tile rendering still delegates to the sharedQuestItemRendererso dailies/weeklies/repeatables all look consistent.
Fixed — Back button opened the wrong main menu
- The Repeatable page's back button always routed to
QuestsMainGui(the layout-driven menu backed bygui/player_main.conf). Players who opened the page from/quests— which usesQuestsHubGui(config-driven,gui/main_menu.conf) — hit back and landed on a completely different menu layout they'd never opened. RepeatableQuestsGuinow takes an optionalRunnable backActionand uses it for the back button.QuestsHubGuipasses a back that returns to itself;QuestsMainGuipasses a back that returns to itself. Back now always returns to the menu you came from.
[1.7.8] — 2026-04-23 — "Hub Repeatable, Offline Names, Wild Hunt"
Fixed — /quests had no Repeatable button
/questsopensQuestsHubGui(config-driven viagui/main_menu.conf), which had zero awareness of the new REPEATABLE type. The Repeatable button I added in 1.7.5 lived only on the layout-drivenQuestsMainGui— two different player menus, both reachable, different slot maps. Players running/questssaw no Repeatable tab at all.- Added
mainMenu.repeatablebutton inGuiConfig, wired throughGuiConfigLoader, rendered inQuestsHubGui, default slot 46 with{available}+{lifetime_runs}placeholders. OpensRepeatableQuestsGuion click and re-runsassigner.syncRepeatablesso the page is always current after/cq admin reload. - Default
main_menu.confbundled resource now carries the same block so fresh installs get the button without editing config.
Fixed — Leaderboard/Community-Quest showed "#1 ???" for offline players
ServerQuestGui.java:112hardcoded"???"for any contributor whose entity wasn't currently online. The top contributor logging off erased their name from the board the moment they left.- New
resolvePlayerNamehelper on the GUI: live profile → storedPlayerQuestData.playerName(stamped on every join since 1.6.x) → 8-char UUID prefix. Never returns null, never falls through to a placeholder string.
Fixed — Wild Hunt / DEFEAT_WILD never triggered
CobblemonBridge.hasNpcParticipanthad two bugs: (1) the class- name check folded "wild" into the NPC match, and (2) the tail of the method unconditionally returnedtruewhether or not an NPC actor was found. Net effect: the call always returnedtrue, soBattleTracker'sisWild = !hasNpcParticipant(battle)was permanently false andDEFEAT_WILDnever fired. Wild Hunt, Wilderness Warrior, and every otherdefeat_wildquest stayed at 0/N forever — wins were credited (WIN_BATTLE fires unconditionally) but never as wild wins.- Split into two explicit helpers:
hasNpcParticipant(positively identifies trainer/NPC actor classes) andisWildBattle(positively identifiesPokemonBattleActor/*wild*on the enemy side, with NPC taking precedence in the rare mixed case).BattleTrackernow callsisWildBattle(battle)directly. - Other objective types audited in the same pass: CATCH, HATCH_EGG, LEVEL_UP, GAIN_LEVELS, EVOLVE, USE_CANDY, USE_POKEBALL, VISIT_BIOME, VISIT_DIMENSION, CATCH_SHINY, CATCH_LEGENDARY — all wired correctly; no additional gaps.
[1.7.7] — 2026-04-22 — "Bypass-off-by-default"
Changed — Bypass permissions now gated by config toggle
cobblemonquests.bypass.reroll-cost(and the reservedcobblemonquests.bypass.rate-limit) were previously consulted any time they were granted, so a forgotten LuckPerms group inheritance or a staff member with a wildcard*node silently had the new per-rotation reroll caps disabled. Granting the node is no longer sufficient on its own.- New
permissions.bypass.reroll-cap/permissions.bypass.rate-limitflags inconfig.conf, both defaulting tofalse. The runtime only honors the matching LuckPerms node when the flag istrue. Turn the flag on and grant the node to activate a bypass. - No change required on existing installs: the default-false flag means previously-bypassable staff now hit the same 1-per-rotation cap as everyone else until the admin explicitly opts back in.
[1.7.6] — 2026-04-22 — "Seed Missing Defaults + Reroll Exploit Fix"
Fixed — Bundled repeatable defaults never reached existing servers
QuestRegistry.reloadonly copied bundleddefault_quests/*.conffiles when the config dir was empty. Upgrading from 1.7.4 to 1.7.5 left the three newrepeatable_*templates stuck inside the jar — the Repeatable tab had a button on the GUI but zero tiles behind it because the registry's REPEATABLE pool was empty.- Now each bundled default is copied individually if missing; existing admin-authored files are never overwritten, and admins can still delete a default to remove it permanently.
Fixed — Reroll-to-riches exploit
- The v1.0 reroll flow allowed unlimited paid rerolls after the one free one. Players could spend 50 stars repeatedly to fish the weekly pool for high-reward quests (Apex Predator = 300 stars + 15 000 pd), making the reroll button positive-EV and printing currency. Two known abuse paths: 13 000 pd / 290 stars per weekly rotation by the time it was reported.
- New model: hard per-rotation caps, no paid fallback.
reroll.daily-per-day(default 1) resets with the daily rotation;reroll.weekly-per-week(default 1) resets with the weekly rotation. Hidden quests still reject rerolls; repeatables don't participate in rerolls at all since they don't rotate. Counters are consumed before the roll and refunded if no replacement can be produced, so concurrent double-clicks can't double-spend and an empty pool doesn't burn the cap. - Legacy fields (
free-per-day,currency,cost-stars,cost-pokedollars) stay in the schema for back-compat parsing but are no longer read by the runtime. The admin Reroll Config GUI (/cq admin→ Config → Reroll) is rewritten around the two per-rotation caps — the old price sliders are gone. QuestsMainGuireroll tooltip showsdaily X/Y · weekly X/Yinstead offree today: N.
[1.7.5] — 2026-04-21 — "Repeatable Quests"
Added — REPEATABLE quest type
- New
type = "repeatable"alongside daily / weekly / elite_weekly / hidden. Repeatables never rotate. The registry's REPEATABLE pool is mirrored into every player'sactiveRepeatableson join and on/cq admin reload, and the claim service resets each tile's progress after the reward is paid out so the player can run it again immediately. Rewards are intentionally small — tune in the quest .conf file; nothing hardcoded. PlayerQuestData.repeatableCompletionstracks per-id lifetime runs, separate fromcompletedAllTime, so dashboards can distinguish "ran the daily once" from "farmed Quick Catch 400×".- Bundled three demo templates (
repeatable_catch_any,repeatable_battle_win,repeatable_level_up) that show the reward floor. Delete them to start fresh.
Added — Dedicated RepeatableQuestsGui
- Opens from the new Repeatable button on the main player quests
GUI (slot 49 in the default layout — configurable via
gui/player_main.confwith rolebutton_repeatable). Lists every available repeatable, 28 per page with prev/next pagination, a Claim All button scoped to ready repeatables, and a header showing total available + lifetime runs. - Clicking a tile opens the standard
QuestDetailGui, so the same reroll/claim flow players already know works here too.
Added — Admin tooling
QuestWizardGuitype picker now cycles repeatable (and skips the internal NORMAL type entirely, which was always a footgun). Creating a repeatable immediately re-syncs every online player so the new tile shows up without reconnecting.QuestBrowserGuigains a "Repeat" tab filter next to Hidden.QuestEditorGuitype picker uses the same cycle.
Fixed — Exhaustive-switch compile holes exposed by the new type
QuestItemRenderer.typeTag,QuestHudManager.colorFor,QuestAdminCommand.give/complete,QuestsHubGui.typeTagall now cover REPEATABLE explicitly. Was silently working thanks to Java's implicit default on arrow-switch of enum — this commit makes it load-bearing.
[1.7.4] — 2026-04-21 — "Viewable Hidden Quests"
Fixed — Hidden-quest button routed to Stats, never to the quest
- The Hidden button on the player quests GUI called
new StatsGui(player).open()instead of a page that lists the unlocked hidden quests. Players who triggered a hidden quest saw the "Unlocked: 1" counter on the button but the click silently opened their stats — so there was no in-game way to read the hidden quest's description, objective, or progress. They knew something had unlocked but couldn't see what or how to finish it.
Added — HiddenQuestsGui
- Dedicated viewer for unlocked hidden quests, opened from the
Hidden button. Lists every quest in
activeHiddens(in-flight, live progress, clickable to open the standard detail view) plus every id indiscoveredHiddenQueststhat has already been completed (rendered as a "Completed" trophy tile). 14 tiles per page; refreshes on the shared GUI ticker so progress updates while the player is looking. - Empty-state hint when neither list has anything yet, so newly joined players don't get a blank screen if an admin gives them the Hidden button by accident.
[1.7.3] — 2026-04-21 — "Reliable Crate Keys"
Fixed — Pebble's Crates keys never landed (Apex Predator etc.)
- Default
integrations.crate-key-commandwaspadmin givekey "{player}" {amount} {key} crate. Pebble's Crates (the plugin this template targets) parses/padmin givekey <player> <amount> <crate>as three plain words — the quoted player arg and the trailing literalcrateboth broke Brigadier parsing, so the Apex Predator reward and any othercrate_keyreward was silently dropped (visible as a WARN in latest.log only). - New default:
padmin givekey {player} {amount} {key}. Matches the real Pebble's admin command.
Added — Per-key command override map
integrations.crate-key-commandsin config.conf. Reward'skeyvalue -> a full command template that takes priority over the globalcrate-key-command. Lets admins pointlunarat Pebble's andvoteat ExcellentCrates in the same server without branching elsewhere. Placeholders:{player}{amount}{key}.CrateKeyBridgeconsults the per-key map first, falls back to the global template. A missing/blank template now logs the specific key that needs a command, and dispatch failures include the resolved command string plus a ready-to-paste admin-test command.
Added — Admin test commands
/cobblemonquests admin testkey <player> <key> [amount]— invokes the crate-key bridge live against the current config so admins can verify their template without waiting for a quest to finish./cobblemonquests admin testcommand <player> <command...>— runs an arbitrary command through the same SafeDispatch pipeline reward commands use, with{player}substituted. Useful for validating any new reward template before saving it into a quest.- Both write
ADMIN_TEST_KEY/ADMIN_TEST_COMMANDaudit entries.
Changed — Apex Predator elite weekly
- Crate key renamed
lunar->legendaryin the bundled template, so out-of-the-box it matches the streak/milestone rewards that already uselegendary. A comment in the file explains how to remap with the new per-key override if your plugin uses a different id.
[1.7.2] — 2026-04-21 — "Guaranteed Reset Fill"
Fixed — Empty quest list after timer reset
- On daily/weekly reset, the exclusion set passed to the pool picker
contained every quest the player had completed in the prior period
(
completedToday/completedThisWeek). With the bundled defaults (5 daily, 3 weekly), a player who cleared yesterday's board had the entire pool swallowed by the exclusion set andpickDailyreturned 0 — players woke up to an empty quest GUI. QuestPool.picknow runs a second pass that falls back to the full pool (ignoringexclude, blocking only already-picked ids) to top up to the requested count. Guarantees 7 dailies and 7 weeklies as long as the registry has at least that many of each type.
Added — Larger bundled default quest pool
- First-time bootstrap now copies 9 dailies and 7 weeklies (plus the
elite weekly and the hidden quest) so a fresh install has enough
pool depth for the default
dailyQuestCount=7/weeklyQuestCount=7schedule without any partial-fill warnings. - New daily templates:
daily_hatch_egg,daily_defeat_wild,daily_catch_shiny,daily_use_candy. - New weekly templates:
weekly_defeat_wild,weekly_hatch_eggs,weekly_level_up,weekly_win_battles.
[1.7.1] — 2026-04-21 — "Deep Audit Pass"
Two-phase audit of the whole mod (trackers, rewards, persistence, scheduler, community goals, chains, pool). Targeted the classes of bug that produce "player report: quests stopped working" but leave no trace in the logs — silent executor failures, missing saves, fragile reflection, lost data in races.
Added — Unified util/SafeDispatch
- Single canonical path for every "run an admin-configured command as
console" call in the mod:
RewardDispenser.runCommand,CrateKeyBridge.grant,ImpactorEconomy.depositStars / withdrawStars. One place that:- uses
CommandDispatcher.execute()(returns an int) instead ofCommandManager.executeWithPrefix()(void wrapper that eats parse errors) - treats
result < 1as a hard failure with a WARN carrying the resolved command string - guards against re-entry per context key so a misconfigured template can't loop back into itself
- separates
CommandSyntaxExceptionfrom generic throws
- uses
Fixed — COMMAND-type rewards silently vanished
RewardDispenser.runCommandusedexecuteWithPrefix(void return). A typoed reward command got swallowed into feedback and the player was told "reward granted" with nothing actually having run. Now routed throughSafeDispatch— typos / unknown commands / wrong syntax appear as WARNs inlatest.logwith the exact resolved invocation and theREWARD_GRANTaudit entry correctly marksallOk=false.
Fixed — Chain advancement lost on missing quest
- If a chain step referenced a quest that had been removed from the
config,
ChainManagerwould silently advance the progress counter but never assign the step's active quest, leaving the player "in the chain with no quest to do".assignStepnow returns a success boolean;onQuestClaimed+startChainonly commit the newchainProgressvalue if assignment succeeds. Failed advance → progress rolls back, WARN logged. ChainRegistry.validateAgainst(QuestRegistry)runs at startup and/questsadmin reload, logging a WARN for every dangling step reference so admin sees the issue before a player hits it.
Fixed — PlayerDataStore.getOrLoad TOCTOU race
- Old code loaded from disk outside any lock, then
putIfAbsent'd into the cache. Two concurrent calls → both loaded → loser's data was discarded. If the disk state changed between the two reads, one read was lost. Replaced withcomputeIfAbsentwhose load runs under the map's internal lock — exactly-once.
Fixed — ImpactorEconomy.withdrawStars race
- Old flow: guard-check under lock, release, dispatch, re-check, commit. If the re-check failed (another thread raced the balance below threshold), dispatch had already debited Impactor — player lost external currency without the mirror reflecting it.
- New flow: single
synchronized (data)block that covers guard → dispatch → mirror-subtract, withstore.savereleased outside the lock. Dispatch is synchronous Brigadier (CPU-bound), so the per-player lock window is fine.
Fixed — Community-goal boss-bar cleanup on a raw Thread
onGoalCompletespawned anew Threadthat slept 10s then hitserver.execute(...)— risky under event ordering and adds a thread per completion. Replaced with a tick-scheduledbossBarClearAtinstant consumed by a newtick()method onCommunityGoalManager, wired into the existingEND_SERVER_TICKhook. Same 10-second visible window, no thread.
Fixed — EggHatchTracker field-name reflection
- Was searching
CobblemonEventsclass fields by substring looking for "egg" + "hatch" — fragile (could match the wrong field) and slow. Swapped to compile-timeCobblemonEvents.HATCH_EGG_POSTagainst Cobblemon 1.7.3. If the field name drifts in a future Cobblemon release the build fails loudly.
Added — Pool-exhaustion logging
- When
QuestPool.pick(type, count, exclude)can't fill the requested count, it now logs a WARN with pool size, exclusion count, and the partial count delivered. Previously the caller got a silently-short list and players saw empty quest slots with no explanation.
Hardened — catch blocks no longer fully silent
- Player-message-send catches (
sendMessagefailures) inRewardDispenser.notifyCurrency,QuestClaimService.notify,ProgressTracker.notifyComplete,ChainManagernow log at DEBUG. Enable log-level DEBUG when investigating "X told me it sent a message but nothing appeared" reports.
Technical notes
ImpactorEconomyre-entry guard (ThreadLocal<Boolean>) removed —SafeDispatchprovides this per-context now and is shared across all command paths.CrateKeyBridge— the inline dispatch block shipped in 1.6.5 is gone; it's 3 lines now delegating toSafeDispatch.RewardDispenser.runCommandlikewise trimmed to a delegate.
[1.7.0] — 2026-04-20 — "Quest Tracker Overhaul"
Major cleanup of the Cobblemon event integration. Catch quests and "Throw N Poké Balls" quests weren't progressing reliably — rewritten with compile-time event accessors instead of reflection, and the whole tracker layer now reports firing activity so it's obvious where something isn't wired up.
Fixed — "Throw N Poké Balls" quests never progressed
- v1.6.4 switched ItemUseTracker to Fabric's
UseItemCallback. That event only fires on air right-clicks (PlayerInteractItemC2SPacket). When the player aims at a visible wild Pokémon entity or at a block, the client sends a different packet —UseItemCallbacknever fires and the ball throw went uncounted. - Reverted to
CobblemonEvents.THROWN_POKEBALL_HITbut resolved the thrower from the ball entity itself viaEmptyPokeBallEntity.getOwner()(inherited fromProjectileEntity). Compile-time access — no more silent reflection mismatches against the event class. - The quest now counts every ball that actually contacts a wild Pokémon, regardless of whether the capture roll succeeds. Throws into empty air still don't count (matches the "engage with a Pokémon" spirit of the quest; prevents AFK-clicking farming).
Fixed — Apex Predator quest delivered nothing
- Crate key template was
excellentcrates key give …, but the server's actual crate plugin uses/padmin givekey. The default template is now:
(quotedintegrations.crate-key-command = "padmin givekey \"{player}\" {amount} {key} crate"{player}— matches pcrates' argument parser). - Live TestServer config was also migrated to the new default.
- The 1.6.5 loud-failure work on
CrateKeyBridgealready made this surface inlatest.logif the template is wrong; now the default itself is correct.
Fixed — Catch event reflection drift
PokemonCapturedEventexposesgetPokeBallEntity(), notgetPokeBall()— the previous reflective call returned null, soctx.ballIdwas silently missing. "Catch with Ultra Ball" filters never matched. Rewritten with compile-time event accessors throughout (event.getPlayer(),event.getPokemon(),event.getPokeBallEntity()); species / type extraction goes through the realPokemonandSpeciesclasses.- Every catch now emits a DEBUG log with species, types, shiny, ball, biome so admin can confirm the tracker is seeing events.
Added — /questsadmin debug events — tracker activity dashboard
- Reports per-tracker fire count + last-fired timestamp + last
detail (e.g. "player123 caught bulbasaur"). Use it to triage
"my quests aren't progressing" complaints in seconds:
- counter at 0 while playing → Cobblemon event isn't reaching the mod (version mismatch / API rename)
- counter increments but quest doesn't progress → filter
problem on the quest
.confside
- Reports external bridge activity too (PokeHunts, Raids).
Technical notes
- New
tracker/TrackerDiagnosticsclass — single source of truth for tracker activity, used by every tracker's success path. Thread-safe (AtomicLong+ConcurrentHashMap); zero overhead when nothing's reading it. BattleTracker,EggHatchTracker,TrainingTrackereach got oneTrackerDiagnostics.record*call at their fire sites.CobblemonBridgeis still the reflection escape hatch for fields that DO vary between Cobblemon versions (Species.name vs Species. showdownId, etc.), but the event CLASSES themselves are compile- time now. The distinction matters: event shapes are public API and stable; sub-field names drift.
Migration
- Existing configs: the
integrations.crate-key-commanddefault changes. If you had customized the template, your value is preserved. If you were on the shipped default, upgrade forces you onto the padmin format — swap back in config.conf if you're still on ExcellentCrates.
[1.6.5] — 2026-04-20 — "Quest Logic Hardening"
Three bug fixes from a live-server report: Apex Predator didn't deliver its crate key, the "visit biomes / dimensions" quests were exploitable via A-B ping-pong, and the leaderboard flipped rank on ties.
Fixed — Apex Predator weekly didn't deliver the crate key
- The quest was configured with
key = "elite", but the server's ExcellentCrates plugin doesn't have anelitecrate — it has alunarcrate. The reward templateexcellentcrates key give {player} {key} {amount}resolved toexcellentcrates key give … elite 1, which ExcellentCrates silently rejected (no such crate). The mod thought it had succeeded becauseexecuteWithPrefixis avoidwrapper — same silent-failure class that bit stars in 1.6.3. - Changed
elite_weekly_legendary.confreward tokey = "lunar"(both the bundled default andTestServer/config/...). - Rewrote
CrateKeyBridge.grantto useCommandDispatcher.execute()directly. Exit-code< 1or aCommandSyntaxExceptionnow logs a WARN with the resolved command string and returnsfalseso the audit log records the failure. No more silent drops.
Fixed — "Visit N biomes / dimensions" exploitable via ping-pong
ExplorationTrackerfires on every biome/dimension transition, so a player bouncing between two biomes (or three dimensions) was farming 1 progress per hop. The "visit 10 biomes" quest could be cleared without leaving a 2-biome radius.ActiveQuestnow stores per-questvisitedIds/visitedBonusIdssets (distinct biome/dimension ids already counted). TheProgressTrackerdedup gate (shouldCountVisit) only increments when the set grows — so a player has to actually visit N distinct places. Sets reset naturally on daily/weekly quest re-issue.- Sets are persisted in
<uuid>.jsonalongside the rest of the active-quest snapshot. Pre-1.6.5 saves simply start with empty sets; players mid-quest at upgrade time get a one-time reset of their visit dedup state.
Fixed — Leaderboard flipped rank on ties
- When two players hit the same count, the one loaded later from disk was ranking higher. Report: "I got 9 quests first but then Killiment also got 9 and suddenly I'm #2."
- Added four
Instanttimestamps toPlayerQuestData:lastTotalCompletedAt,lastWeeklyProgressAt,lastStreakChangedAt,lastStarsEarnedAt. Stamped at the mutation site for each counter. LeaderboardManager.rebuildnow sortsvalue desc, timestamp asc— the player who reached the tied value first (earlier timestamp) ranks higher. Null timestamps (pre-1.6.5 data, no recorded increase) sort last by design.- Serialized in
<uuid>.json; missing fields tolerated.
Technical notes
- Dedup sets use
LinkedHashSetso the JSON order of visited ids matches the order the player visited them — useful for debugging "is the tracker firing?" reports via the admin player-detail GUI. PlayerQuestData.incrementCompletednow stamps bothlastTotalCompletedAtandlastWeeklyProgressAtin one go (a completed quest always contributes to both weekly and all-time counts, so the two timestamps co-move until weekly reset).