Feedback & Colored Combat Text
Coordinated sound, camera shake, and colored floating combat text for every moment
Overview
Adds a single feedback layer so the big moments of play - a critical hit, a level-up, taking or abandoning a quest - feel like more than a line of text. One service fans each moment out to up to five channels at once, in lockstep: floating combat text (colored where it fires), a 3D sound at the player, an optional camera shake, the corner notification feed, and an in-menu toast for actions taken from inside a UI page (for example accepting a quest from the Quests menu shows a toast over that page in addition to the shared sound cue). Everything is cosmetic, server-owner configurable, and can be turned off.
The two pieces are colored combat text (the red "CRIT!" and gold level-up popups, tuned in combat-text.json) and the broader feedback layer (the per-moment sound and camera, tuned in feedback.json). Each channel is independently guarded, so a misconfigured asset can never break combat.
Feedback moments (authored files)
New: the toast and the jingle a player gets for a progression moment are files, not code. One file per moment lives at Server/ZiggfreedCommon/FeedbackMoments/<moment id>.json, and the file name is the moment. Reword one, recolour it, give it a different sound, announce it to the whole server, or run a console command on it, all without a jar involved.
The library underneath ships a plain, neutral file for each moment it fires, so a bare server gets a notice and a jingle out of the box. MMO Skill Tree ships its own file for some of the quest and achievement moments under the same names, and the MMO's files win because a pack that depends on the library loads after it. Where the MMO ships no file, the library's own answers. Rare_Find is the MMO's own, with no neutral default beneath it. The moments a player meets most often:
| Moment (file name) | Fires when | What the shipped file does |
|---|---|---|
Quest_Objective_Progressed | One step of a quest a player is carrying moves | The library's own file: a blue Info tick reading {step} ({current}/{required}), on one corner-feed line that rewrites itself as the step moves. A player on the Milestones level sees it only at the quarter marks (EveryPercent: 25). The tick that finishes the step turns green Success and reads "Objective complete", except for a hand-in step, whose own notice and the quest's rewards already say so. No sound. |
Quest_Completed | The last objective is met and the quest pays out on the spot | The library's own file: a gold Reward "Quest complete" notice and its jingle. |
Quest_Parked | The last objective is met but the reward waits to be collected | Three wordings from one file: a gold Reward "Quest complete" notice telling the player to come back and collect, with a soft sting; a full bag reads as a red Error ("Make room to collect your rewards") and stays silent; a board contract tells the player to collect their pay at the board they took it from. |
Quest_Claimed | A quest's rewards are paid to the player | The library's own file: nothing for a quest that settled on the spot (it already had its completion jingle); when the player came back to collect, a gold "Rewards collected" notice listing what it paid, and the legendary-chest chime. A board contract's Collect is the same moment. |
Achievement_Unlocked | An achievement is earned | A gold Reward toast with the title, the points line and the achievement's own picture, the unlock jingle, and the server-wide banner for the achievements that ask for one. |
Achievement_Claimed | An achievement's rewards are paid to the player | The library's own file, as Quest_Claimed: an "Achievement rewards collected" notice and the chime, only when the player came back to collect. |
Achievement_Server_First_Lost | A player finishes a one-winner achievement somebody else already won | The library's own file: an amber Warning note ("Somebody else got there first") and no sound. |
Rare_Find | A rolled loot table lands a roll that named this moment as its Cue, and that roll paid out | A gold Reward-toned toast plus the legendary-chest chime. |
Reputation_Rank | A player's rank with a reputation goes up during play, from a reward, a kill or a piece of gear; never for a fall, and never at login | The library's own file: a Reward-toned "You are now {rank}" over "with {name}", and a short jingle. See Reputations. |
Reputation_Beyond | Standing earned past a reputation's top rank crosses another step of its Beyond and the reward waits to be collected; never for a reputation whose Beyond pays on the spot (Collect false) | The library's own file: a Reward-toned "A reward from {name} is waiting" over "Collect it on your Reputation page.", and a short discovery chime, at each player's own notification level. |
Reward_Chest_Opened | A player opens a reward chest's page, once per open | The library's own file: no toast, and one sound picked by the rarest reward inside: SFX_Chest_Wooden_Open, SFX_Discovery_Z1_Short for Rare, SFX_Memories_Unlock_Local for Epic and SFX_Chest_Legendary_FirstOpen_Player for Legendary. See Reward chests. |
Rare_Find also shows the no-Java celebration rule: the cue id IS the moment id. Any loot roll or ladder floor in a Lootables file can carry a Cue, and a pack that authors a different cue value plus a moment file of that same name gives that roll a celebration of its own, with no code anywhere. A cue fires only when the grants beside it paid out, so it never celebrates over an empty hand.
Receipts and purchases
A payout with no quest behind it fires no moment, so the library's own toast carries the sound, the same on every server, with or without MMO Skill Tree:
- A conversation line that pays (a trick-or-treat knock, a daily gift) and an item that pays out when used (a Cursed Geode cracked open) show a gold "Rewards received." notice listing each thing handed over, with the pickup sound. With a conversation open, the notice shows inside it.
- A purchase at any shop shows its notice with the sound of coins landing.
- Collect, on a quest, an achievement or a board, is the
Quest_ClaimedorAchievement_Claimedmoment above, so it is one sound per press however it is pressed.
The Collect sound is a server owner's to change, in those two moment files. The receipt and purchase sounds have no file of their own, in the library or in feedback.json.
Three of the MMO's files, each quoted without its $Comment, to show the shape:
{
"Toast": {
"Title": { "Key": "notify.rare_find" },
"Tone": "Reward"
},
"Sound": { "Id": "SFX_Chest_Legendary_Open" }
}{
"Toast": {
"Title": { "Key": "notify.quest_ready_to_claim", "Args": ["title"] },
"Tone": "Reward",
"PlayerLevel": true
},
"Sound": { "Id": "SFX_Discovery_Z1_Short" },
"Variants": [
{
"When": { "reason": "no_space" },
"Toast": {
"Title": { "Key": "notify.quest_inventory_full", "Args": ["title"] },
"Tone": "Error",
"Merge": false,
"PlayerLevel": true
},
"Sound": {}
},
{
"When": { "turnIn": "accept_site" },
"Toast": {
"Title": { "Key": "notify.contract_ready_to_claim", "Args": ["title"] },
"Tone": "Reward",
"PlayerLevel": true
}
}
]
}{
"Toast": {
"Title": { "Key": "notify.achievement_unlocked", "Args": ["title"] },
"Secondary": { "Key": "notify.achievement_points_awarded", "Args": ["points"] },
"Tone": "Reward",
"PlayerLevel": true
},
"Broadcast": {
"Title": { "KeyArg": "announceTitleKey", "Args": ["announceSkill", "announceAmount"] },
"Secondary": { "KeyArg": "announceBodyKey", "Args": ["player", "title"] },
"Major": true
},
"Sound": { "Id": "SFX_Capture_Crate_Capture_Succeed_Local" }
}The unlock toast draws the achievement's own picture beside the words, so there is nothing to name per achievement. The banner is drawn only for an achievement whose own file authored an Announce block: that block carries just two keys, TitleKey and BodyKey, and the moment reads them through KeyArg, so the words are the achievement's own and one that authored none shows no banner at all. The library's server-first note is an amber Warning with no sound: losing a race should never be louder than winning one.
The four groups
A moment file carries up to four independent groups. Author the ones you want and delete the rest; none of them needs any of the others.
| Group | What it does |
|---|---|
Toast | The corner card the player who earned it sees, as a Title line and an optional Secondary line. |
Broadcast | A centered banner for everybody online (Major: true for the big style). Add it to announce every unlock the same way; the shipped unlock file draws it only for an achievement that authored an Announce block. |
Sound | Any sound id, played at the player's own position, so anyone standing nearby hears it too. |
Command | A console command line run on the moment - a title card, a webhook relay, a fireworks spawn - with {player} and any of the moment's values in braces. |
A line of text is written the same way everywhere: Key, Args, and optionally Color; the toast as a whole carries a Tone.
Keyis a line from a language file, written without that file's own name prefix, exactly as you wrote it there. The whole key with its prefix works too, which is how the library's own files write theirs (ziggfreedcommon.feedback.quest.completed). Point it at a key of your own to reword the moment entirely; a key no language file ships is shown as written, so a one-server line can be the sentence itself.KeyArgnames one of the moment's values, and the line reads its key from that value. That is howAchievement_Unlocked.jsondraws each announcing achievement's own words.Argsnames which of the moment's values fill that line's blanks, in order. A line naming a value the moment does not carry is skipped, never printed half-empty, so an experiment can never leave a broken line on screen.playeralways answers with the player's name.Toast.Tonecolours the whole notification, frame and words together, and picks the matching corner-feed style. It takes one of five words:Info(neutral blue),Success(green),Reward(gold),Warning(amber) andError(red). Unauthored reads asInfo.Coloron a single line, as hex, still overrides the tone's colour for that line alone.
Two more knobs branch on the moment itself:
Variantsis an ordered list of{When, <groups>}entries. The first entry whoseWhenvalues all match the moment's own values (compared as text, ignoring case, sotrueand25may be written bare) overlays only the groups it restates; each restated group replaces the file's own whole, and an empty group there means "nothing for this case". This is how oneQuest_Parked.jsonsays three things and how the twoclaimedfiles chime only for a collect ("When": {"collected": true}).Toast.EveryPercentmarks a progress tick each time it crosses a multiple of that many percent of the way (25gives the quarter marks), which is what keeps a long step from chattering; the tick that finishes the step always shows. On a toast withoutPlayerLevel, only the marks and the finish show. On a toast with it, the player's level decides: Every update sees every tick, Milestones only the marks and the finish. Leave it out and there are no marks, so a player on Milestones sees only the finish.
The shipped files set two more toast leaves, and a third tunes the reward rows:
Toast.PlayerLevel:truehands the toast to the player's own notification level, the one they pick on the menu's Settings tab (see Notification Levels). Every quest and achievement toast the library and the MMO ship sets it. A variant's toast replaces the file's whole, so it saysPlayerLevelagain. Left out, the toast shows as written whatever the player chose.Toast.Mergeis on unless set tofalse: a repeat of the moment about the same thing rewrites the corner notice already on screen, so a step counting up keeps one line. Set it tofalsefor a notice the player must not miss when another of its kind lands on top of it, as the MMO's full-bag notice does. Only the corner feed merges; with a menu open, each notice is drawn into the page.Toast.Rows: a moment that hands something over lists it as rows under the headline on the in-page toast (a player with a menu open).Show: falseturns the rows off, andMaxcaps how many show before the rest fold into one "+N more" row. The corner feed has no rows.
What each moment carries, for Args, KeyArg and When:
| Moment | Values |
|---|---|
| every quest moment | quest (its id), title (its name, already in the reader's language), player; all but Quest_Objective_Progressed add icon, the quest's own picture when it has one |
Quest_Completed, Quest_Parked | parked; a parked one adds reason (collect, no_space, away) and, when the quest is collected somewhere in particular, turnIn (character or accept_site) |
Quest_Claimed, Achievement_Claimed | collected (true when the player came back for a parked reward) plus granted / queued / failed reward counts |
Quest_Objective_Progressed | objective (the step's id), kind (the step's kind, such as TURN_IN or KILL_ENTITY, from CommonLib 2.5.1), step (its own sentence), current, required, finished, and source (the one step the notice is about, which Merge keeps one line for). The shipped file's first variant, {"finished": true, "kind": "TURN_IN"}, draws a toast with no Title, which shows nothing |
Reward_Chest_Opened | rarity (the rarest reward's rarity as its drop's chance reads it: Common, Uncommon, Rare, Epic or Legendary; absent when no reward carries a chance), player |
Achievement_Unlocked, Achievement_Claimed | achievement, title, icon, points, player; the unlock adds awaiting_claim, and an announcing achievement adds announceTitleKey, announceBodyKey, announceSkill, announceAmount |
Achievement_Server_First_Lost | achievement, title, player, and no picture |
Rare_Find | player only - it fires for whatever rare roll authored it, so the words are about the find, not any one item |
Reputation_Rank | rank (the rank's name as that reputation calls it, in the reader's language), name (the reputation's name), source (the reputation's id), icon when the reputation has one, player |
Reputation_Beyond | name (the reputation's name, in the reader's language), waiting (how many of its rewards past the top rank wait to be collected, in all), source (the reputation's id), player |
a boss fight's own moments (a binding row's Feedback: Engaged, PhaseChanged, Defeated, Wiped; or a script's ZigFeedback) | encounter (the fight's script id), title (the fight's name), members, seconds (since the fight began), phase when there is one, and wave, the waves the fight has raised so far (from CommonLib 2.5.0, so a moment shown as a wave rises reads that wave's number); when a fight settles, share (the reader's own, in whole percent of the top player's) and rank (their place, 1 for the top), and at a defeat that paid them, rewards |
A Command line may use {player} and any of those values in braces. Each shipped file's own comment repeats the list for its moment.
Overriding one
A moment is content, so it layers like any other: ship a file with the same name from your own pack, or drop one into a pack you already load, and yours wins. Deleting the toast group and keeping the sound is a valid file, and so is a file with nothing but a Broadcast.
Prefer a content pack: author one file per moment under Server/ZiggfreedCommon/FeedbackMoments/ inside a pack folder, zip it, and drop the .zip into your server Mods/ folder. Building the zip.
A player's own notification level, under Quests and achievements on the menu's Settings tab, has the last word on every toast marked PlayerLevel: Every update sees every progress tick, Milestones the authored marks and the finish, Finishes the finish only, and None no quest or achievement toast at all (a player on None does not see the red inventory-full notice either; the reward still waits, and the Quests page still shows it). A completion, a claim or an unlock shows at every level but None. A quest showing on the player's quest tracker draws none of these toasts in the corner, since the tracker already says it; inside an open menu they still show. Only the personal toast is gated that way; a banner, a sound and a console command belong to the moment. See Notification Levels.
The server-wide banner an achievement shows is authored on the achievement itself, as an Announce block carrying TitleKey and BodyKey; how it is drawn is authored once for every achievement, in Achievement_Unlocked.json. The older written-out Title / Body leaves and the owner-file announcementCommand are retired: an achievement file still carrying announcementCommand gets one warning at start naming the replacement (a Command group on Achievement_Unlocked.json, or a Variants entry scoped by "When": {"achievement": "<id>"}) and the leaf is otherwise ignored.
Because the moment is announced by the engine for everything in the shared runtime, these files also dress a quest or achievement the MMO does not own (a board contract, or another mod's content on the same server). A line whose named value the moment did not carry is skipped, never drawn with a gap, so content with no title stays silent; a server running two mods that both want their own words gives one of them moment ids of its own.
Colored Combat Text
Two combat moments float colored text above an entity. The text is fully localized into every supported language and ASCII-only, so it renders cleanly everywhere.
| Moment | What you see | Color | Anchored on |
|---|---|---|---|
| Critical hit | A red CRIT! on the struck target | Red (#FF3B3B) | The target you hit |
| Skill level-up | A gold <Skill> LV <n> | Gold (#FFD700) | The leveling player |
Only crits and level-ups float. Kill XP and per-action XP show in the corner notification feed and do not float above mobs, the combo finisher keeps its bonus and notification but does not float, and lifesteal keeps its notification. Each hit shows one damage number, the game's own, and the crit and level-up text draw at the game's normal combat-text size.
Critical-hit corner notifications ship off by default: the red floating "CRIT!" already marks every crit, so the separate corner toast is redundant. Anyone who wants both can re-enable the crit toggle in Settings and the choice sticks.

combat-text.json
Colored combat text lives in mods/mmoskilltree/combat-text.json. It is override-based - only the fields you change are written; everything else tracks the shipped defaults. A master enabled flag gates the whole feature, and crits and level-ups each have their own toggle.
{
"schemaVersion": 1,
"enabled": true,
"crits": true,
"levelUps": true,
"critStyle": "#FF3B3B",
"levelUpStyle": "#FFD700"
}| Field | Type | Default | Description |
|---|---|---|---|
enabled | - | true | Master switch for all colored combat text. Nothing floats when off. |
crits | - | true | Toggles the red crit popup on its own. |
levelUps | - | true | Toggles the gold level-up popup on its own. |
critStyle / levelUpStyle | - | #FF3B3B / #FFD700 | The color each popup draws in, as a hex code (#RRGGBB). A file from 1.7.0 or earlier keeps working: MMO_CombatText_Red and MMO_CombatText_Gold read as their red and gold, and CombatText as white. Any other value falls back to the default color, with one warning in the server log. |
The Feedback Layer
Behind the floating text, one service emits a moment and fans it out to a sound, an optional camera shake, the notification feed, and (in menus) a toast - all in lockstep, all from one call. Each moment maps to a row in feedback.json that carries the sound and camera ids for that event.
Moments that fire a sound or a shake
| Moment | When it fires | Default sound |
|---|---|---|
KILL_XP | Per-kill skill XP, anchored on the slain mob | Off (high frequency) |
CRIT | A critical hit lands | Off (high frequency) |
COMBO_FINISHER | A combo finisher triggers | Off (high frequency) |
LEVEL_UP | A skill levels up | Off (level-up plays its own reward sound) |
QUEST_ACCEPT | A quest or bounty is accepted, from any surface | SFX_Drop_Items_Paper |
QUEST_ABANDON | A quest is abandoned | SFX_Generic_Crafting_Failed |
Combat moments (KILL_XP, CRIT, COMBO_FINISHER, LEVEL_UP) ship with their sound off by default because they fire often and a default chime would be spammy. The two quest moments ship with a stock sound. Set a sound id on any row to opt a moment in.
Boards and shops have no rows of their own: they belong to the shared library, a finished or collected contract is a quest moment (above), and a purchase plays its own coin sound (see Receipts and purchases). A feedback.json written by an older version that still names BOUNTY_COMPLETE, BOUNTY_REROLL or SHOP_PURCHASE loads as before, with those rows skipped.
Camera shake
Every moment can also drive a brief camera shake, written straight to the player the same way the engine drives its own damage shake. Camera shake is off by default for every moment (no shake assets ship); the channel exists for servers that want it. Set a cameraEffect id and an intensity on a row to turn it on.
feedback.json
The sound and camera channels live in mods/mmoskilltree/feedback.json. Each moment is one row; an empty sound or cameraEffect string means that channel is off for that moment.
{
"enabled": true,
"events": {
"QUEST_ACCEPT": { "sound": "SFX_Drop_Items_Paper", "cameraEffect": "", "cameraIntensity": 1.0 },
"QUEST_ABANDON": { "sound": "SFX_Generic_Crafting_Failed", "cameraEffect": "", "cameraIntensity": 1.0 },
"CRIT": { "sound": "", "cameraEffect": "", "cameraIntensity": 1.0 }
}
}The top-level enabled flag gates the entire sound and camera layer; turning it off leaves colored combat text and the notification feed untouched, since those are governed separately.
Turning It Off
There are independent switches at every level, so you can keep what you want.
- All colored combat text - set
enabled: falseincombat-text.json. - Just crits or just level-ups - flip
critsorlevelUpsincombat-text.json. - All sounds and camera shake - set
enabled: falseinfeedback.json, or clear a single moment'ssound/cameraEffectstring. - Corner notifications - players toggle individual notification categories (including the crit toast) in their in-game Settings, and the choice persists.
Quest accept and abandon cues are shared across every surface, so the bounty board, the NPC quest page, dialogue, and the quest log all play the identical cue. See Quests and Combo Finishers for the gameplay those moments come from.