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 whenWhat the shipped file does
Quest_Objective_ProgressedOne step of a quest a player is carrying movesThe 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_CompletedThe last objective is met and the quest pays out on the spotThe library's own file: a gold Reward "Quest complete" notice and its jingle.
Quest_ParkedThe last objective is met but the reward waits to be collectedThree 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_ClaimedA quest's rewards are paid to the playerThe 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_UnlockedAn achievement is earnedA 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_ClaimedAn achievement's rewards are paid to the playerThe 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_LostA player finishes a one-winner achievement somebody else already wonThe library's own file: an amber Warning note ("Somebody else got there first") and no sound.
Rare_FindA rolled loot table lands a roll that named this moment as its Cue, and that roll paid outA gold Reward-toned toast plus the legendary-chest chime.
Reputation_RankA 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 loginThe library's own file: a Reward-toned "You are now {rank}" over "with {name}", and a short jingle. See Reputations.
Reputation_BeyondStanding 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_OpenedA player opens a reward chest's page, once per openThe 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_Claimed or Achievement_Claimed moment 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:

Rare_Find.json - the toast and the chime when a loot table lands one of its rare pulls
{
  "Toast": {
    "Title": { "Key": "notify.rare_find" },
    "Tone": "Reward"
  },
  "Sound": { "Id": "SFX_Chest_Legendary_Open" }
}
Quest_Parked.json - one moment, three wordings, told apart by the values it carries
{
  "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
      }
    }
  ]
}
Achievement_Unlocked.json - the toast, the jingle, and the banner an announcing achievement asks for
{
  "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.

GroupWhat it does
ToastThe corner card the player who earned it sees, as a Title line and an optional Secondary line.
BroadcastA 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.
SoundAny sound id, played at the player's own position, so anyone standing nearby hears it too.
CommandA 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.

  • Key is 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.
  • KeyArg names one of the moment's values, and the line reads its key from that value. That is how Achievement_Unlocked.json draws each announcing achievement's own words.
  • Args names 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. player always answers with the player's name.
  • Toast.Tone colours 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) and Error (red). Unauthored reads as Info.
  • Color on a single line, as hex, still overrides the tone's colour for that line alone.

Two more knobs branch on the moment itself:

  • Variants is an ordered list of {When, <groups>} entries. The first entry whose When values all match the moment's own values (compared as text, ignoring case, so true and 25 may 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 one Quest_Parked.json says three things and how the two claimed files chime only for a collect ("When": {"collected": true}).
  • Toast.EveryPercent marks a progress tick each time it crosses a multiple of that many percent of the way (25 gives the quarter marks), which is what keeps a long step from chattering; the tick that finishes the step always shows. On a toast without PlayerLevel, 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: true hands 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 says PlayerLevel again. Left out, the toast shows as written whatever the player chose.
  • Toast.Merge is on unless set to false: 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 to false for 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: false turns the rows off, and Max caps 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:

MomentValues
every quest momentquest (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_Parkedparked; 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_Claimedcollected (true when the player came back for a parked reward) plus granted / queued / failed reward counts
Quest_Objective_Progressedobjective (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_Openedrarity (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_Claimedachievement, title, icon, points, player; the unlock adds awaiting_claim, and an announcing achievement adds announceTitleKey, announceBodyKey, announceSkill, announceAmount
Achievement_Server_First_Lostachievement, title, player, and no picture
Rare_Findplayer only - it fires for whatever rare roll authored it, so the words are about the find, not any one item
Reputation_Rankrank (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_Beyondname (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.

MomentWhat you seeColorAnchored on
Critical hitA red CRIT! on the struck targetRed (#FF3B3B)The target you hit
Skill level-upA 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.
A red 'CRIT!' popup on a struck mob and a gold '<Skill> LV <n>' popup on the leveling player, captured in the same fight if possible.
A red 'CRIT!' popup on a struck mob and a gold '<Skill> LV <n>' popup on the leveling player, captured in the same fight if possible.

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"
}
FieldTypeDefaultDescription
enabled-trueMaster switch for all colored combat text. Nothing floats when off.
crits-trueToggles the red crit popup on its own.
levelUps-trueToggles the gold level-up popup on its own.
critStyle / levelUpStyle-#FF3B3B / #FFD700The 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.
* required

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

MomentWhen it firesDefault sound
KILL_XPPer-kill skill XP, anchored on the slain mobOff (high frequency)
CRITA critical hit landsOff (high frequency)
COMBO_FINISHERA combo finisher triggersOff (high frequency)
LEVEL_UPA skill levels upOff (level-up plays its own reward sound)
QUEST_ACCEPTA quest or bounty is accepted, from any surfaceSFX_Drop_Items_Paper
QUEST_ABANDONA quest is abandonedSFX_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: false in combat-text.json.
  • Just crits or just level-ups - flip crits or levelUps in combat-text.json.
  • All sounds and camera shake - set enabled: false in feedback.json, or clear a single moment's sound / cameraEffect string.
  • 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.