[{"data":1,"prerenderedAt":928},["ShallowReactive",2],{"doc-global_essentials\u002Fadvanced\u002Fachievements":3},{"id":4,"title":5,"body":6,"description":16,"extension":920,"meta":921,"navigation":262,"path":924,"seo":925,"stem":926,"__hash__":927},"docs\u002Fdocs\u002Fglobal_essentials\u002Fadvanced\u002Fachievements.md","Achievements",{"type":7,"value":8,"toc":911},"minimark",[9,13,17,28,31,36,42,63,66,82,88,97,100,102,106,113,186,199,222,225,227,230,233,286,293,295,298,305,419,422,424,428,447,450,527,530,532,536,539,561,564,566,570,573,904,907],[10,11,5],"h1",{"id":12},"achievements",[14,15,16],"p",{},"Achievements are data your game drives. You define them in the editor, your scripts decide when progress moves, and the engine does the rest: it stores the progress in the save, scores the reward points, pops the unlock notification, and renders the Achievements tab.",[14,18,19,20,24,25,27],{},"The split matters. The engine ships ",[21,22,23],"strong",{},"no"," achievements of its own and does ",[21,26,23],{}," tracking, and neither do plugins — a plugin's job is only to make sure the moments worth tracking are covered by emitters. Nothing in the engine knows what \"Anal Slut\" means or how many loads it takes; that lives entirely in your game's data and one tracking script.",[29,30],"hr",{},[32,33,35],"h2",{"id":34},"the-three-tabs","The three tabs",[14,37,38,39,41],{},"The editor's ",[21,40,5],{}," section has three files:",[43,44,45,51,57],"ul",{},[46,47,48,50],"li",{},[21,49,5],{}," — the entries themselves.",[46,52,53,56],{},[21,54,55],{},"Tiers"," — bronze, silver, gold, whatever your game uses. A tier carries the card color and the default reward points for everything at that tier, so retuning a whole rank is one edit.",[46,58,59,62],{},[21,60,61],{},"Groups"," — the sub-tabs the Achievements tab is split into (Combat, Exploration, ...), in the order you give them. The player browses one group at a time; the summary and tier ladder above them always count the whole catalog.",[14,64,65],{},"Every field is documented by its tooltip in the editor. The concepts worth knowing before you author are below.",[14,67,68,71,72,76,77,81],{},[21,69,70],{},"Target."," Progress needed to complete. ",[73,74,75],"code",{},"1"," makes a simple flag (\"bear your first sprout\"), a bigger number makes a counter (\"bear thirty sprouts\"). Leave it empty when the number comes from your data instead — see ",[78,79,80],"em",{},"Targets counted from data",".",[14,83,84,87],{},[21,85,86],{},"Hidden."," Masks the name and description until earned, for spoilers and surprises. The tier stays visible, so the player still knows something is there.",[14,89,90,93,94,81],{},[21,91,92],{},"Tags."," Free-form labels that let one call drive a whole family — see ",[78,95,96],{},"Families",[14,98,99],{},"The tab appears only when a game actually defines achievements. Ship none and the player never sees an empty screen.",[29,101],{},[32,103,105],{"id":104},"moving-progress","Moving progress",[14,107,108,109,112],{},"Three calls, all on ",[73,110,111],{},"game",":",[114,115,120],"pre",{"className":116,"code":117,"language":118,"meta":119,"style":119},"language-js shiki shiki-themes github-light github-dark","game.progressAccolade('first_seedling');            \u002F\u002F +1\ngame.progressAccolade('tidewater', volume);         \u002F\u002F +N\ngame.setAccoladeProgress('overkill', hit.damage);   \u002F\u002F raise to this value\n","js","",[73,121,122,149,167],{"__ignoreMap":119},[123,124,127,131,135,138,142,145],"span",{"class":125,"line":126},"line",1,[123,128,130],{"class":129},"sVt8B","game.",[123,132,134],{"class":133},"sScJk","progressAccolade",[123,136,137],{"class":129},"(",[123,139,141],{"class":140},"sZZnC","'first_seedling'",[123,143,144],{"class":129},");            ",[123,146,148],{"class":147},"sJ8bj","\u002F\u002F +1\n",[123,150,152,154,156,158,161,164],{"class":125,"line":151},2,[123,153,130],{"class":129},[123,155,134],{"class":133},[123,157,137],{"class":129},[123,159,160],{"class":140},"'tidewater'",[123,162,163],{"class":129},", volume);         ",[123,165,166],{"class":147},"\u002F\u002F +N\n",[123,168,170,172,175,177,180,183],{"class":125,"line":169},3,[123,171,130],{"class":129},[123,173,174],{"class":133},"setAccoladeProgress",[123,176,137],{"class":129},[123,178,179],{"class":140},"'overkill'",[123,181,182],{"class":129},", hit.damage);   ",[123,184,185],{"class":147},"\u002F\u002F raise to this value\n",[14,187,188,190,191,194,195,198],{},[73,189,174],{}," ",[21,192,193],{},"only ever raises",". A write lower than the stored progress is dropped, and progress is clamped at the target so a finished counter reads exactly ",[73,196,197],{},"50 \u002F 50",". That one rule is what makes tracking code simple to write:",[43,200,201,210,216],{},[46,202,203,206,207,81],{},[21,204,205],{},"Best-of values"," need no bookkeeping. Call it on every hit and the biggest one stays: ",[73,208,209],{},"setAccoladeProgress('overkill', damage)",[46,211,212,215],{},[21,213,214],{},"Set sizes"," are safe to recompute. Keep your own set of what she's worn, and push its size on every equip; a rebuild that comes back smaller can't erase the record.",[46,217,218,221],{},[21,219,220],{},"Per-scene records"," need no window machinery. Write the running count live during the scene and reset your own counter when the scene ends — the wildest scene is already recorded.",[14,223,224],{},"There is no way to lower progress or un-earn an achievement. Progress models \"the furthest she ever got\", which is what an achievement wants.",[29,226],{},[32,228,96],{"id":229},"families",[14,231,232],{},"Tiered achievements — ten loads, fifty, two hundred — are one event driving several entries. Tag them in the editor rather than listing ids in code:",[114,234,236],{"className":116,"code":235,"language":118,"meta":119,"style":119},"\u002F\u002F every achievement tagged \"seed_ass\" advances; each stops at its own target\ngame.progressAccoladesByTag('seed_ass');\n\n\u002F\u002F or push an absolute value to the whole family\ngame.setAccoladeProgressByTag('mc_level', level);\n",[73,237,238,243,258,264,270],{"__ignoreMap":119},[123,239,240],{"class":125,"line":126},[123,241,242],{"class":147},"\u002F\u002F every achievement tagged \"seed_ass\" advances; each stops at its own target\n",[123,244,245,247,250,252,255],{"class":125,"line":151},[123,246,130],{"class":129},[123,248,249],{"class":133},"progressAccoladesByTag",[123,251,137],{"class":129},[123,253,254],{"class":140},"'seed_ass'",[123,256,257],{"class":129},");\n",[123,259,260],{"class":125,"line":169},[123,261,263],{"emptyLinePlaceholder":262},true,"\n",[123,265,267],{"class":125,"line":266},4,[123,268,269],{"class":147},"\u002F\u002F or push an absolute value to the whole family\n",[123,271,273,275,278,280,283],{"class":125,"line":272},5,[123,274,130],{"class":129},[123,276,277],{"class":133},"setAccoladeProgressByTag",[123,279,137],{"class":129},[123,281,282],{"class":140},"'mc_level'",[123,284,285],{"class":129},", level);\n",[14,287,288,289,292],{},"Adding a fourth tier later is then an editor change, not a code change. ",[73,290,291],{},"game.getAccoladesByTag(tag)"," returns the ids if you need them yourself.",[29,294],{},[32,296,80],{"id":297},"targets-counted-from-data",[14,299,300,301,304],{},"\"Wear every outfit\" has no honest number to hard-code: the moment you add an outfit, the achievement completes one short. Leave ",[73,302,303],{},"target"," empty in the editor and count it at load instead:",[114,306,308],{"className":116,"code":307,"language":118,"meta":119,"style":119},"let outfits = 0;\nfor (const template of game.getData('item_templates').values()) {\n    if ((template.slots || []).includes('outfit')) outfits += 1;\n}\ngame.setAccoladeTarget('well_dressed', outfits);\n",[73,309,310,329,366,399,404],{"__ignoreMap":119},[123,311,312,316,319,322,326],{"class":125,"line":126},[123,313,315],{"class":314},"szBVR","let",[123,317,318],{"class":129}," outfits ",[123,320,321],{"class":314},"=",[123,323,325],{"class":324},"sj4cs"," 0",[123,327,328],{"class":129},";\n",[123,330,331,334,337,340,343,346,349,352,354,357,360,363],{"class":125,"line":151},[123,332,333],{"class":314},"for",[123,335,336],{"class":129}," (",[123,338,339],{"class":314},"const",[123,341,342],{"class":324}," template",[123,344,345],{"class":314}," of",[123,347,348],{"class":129}," game.",[123,350,351],{"class":133},"getData",[123,353,137],{"class":129},[123,355,356],{"class":140},"'item_templates'",[123,358,359],{"class":129},").",[123,361,362],{"class":133},"values",[123,364,365],{"class":129},"()) {\n",[123,367,368,371,374,377,380,383,385,388,391,394,397],{"class":125,"line":169},[123,369,370],{"class":314},"    if",[123,372,373],{"class":129}," ((template.slots ",[123,375,376],{"class":314},"||",[123,378,379],{"class":129}," []).",[123,381,382],{"class":133},"includes",[123,384,137],{"class":129},[123,386,387],{"class":140},"'outfit'",[123,389,390],{"class":129},")) outfits ",[123,392,393],{"class":314},"+=",[123,395,396],{"class":324}," 1",[123,398,328],{"class":129},[123,400,401],{"class":125,"line":266},[123,402,403],{"class":129},"}\n",[123,405,406,408,411,413,416],{"class":125,"line":272},[123,407,130],{"class":129},[123,409,410],{"class":133},"setAccoladeTarget",[123,412,137],{"class":129},[123,414,415],{"class":140},"'well_dressed'",[123,417,418],{"class":129},", outfits);\n",[14,420,421],{},"Run that at script-load time, every boot. An achievement with no target — neither authored nor set — can never complete, so a typo fails loudly rather than handing out a free unlock.",[29,423],{},[32,425,427],{"id":426},"reward-points","Reward points",[14,429,430,431,434,435,438,439,442,443,446],{},"Each achievement is worth points: its own ",[73,432,433],{},"points"," if you set one, otherwise its tier's. Read the score with ",[73,436,437],{},"game.getEarnedPoints()"," and ",[73,440,441],{},"game.getTotalPoints()",", or ask what a single entry is worth with ",[73,444,445],{},"game.getAccoladePoints(id)",". The tab shows the running total.",[14,448,449],{},"Points are a score, not a currency — the engine never spends them. To pay out something real, listen for the unlock:",[114,451,453],{"className":116,"code":452,"language":118,"meta":119,"style":119},"game.on('accolade_completed', (accoladeId, points) => {\n    game.getCharacter('mc').addResource('gold', points * 10);\n});\n",[73,454,455,488,522],{"__ignoreMap":119},[123,456,457,459,462,464,467,470,474,477,479,482,485],{"class":125,"line":126},[123,458,130],{"class":129},[123,460,461],{"class":133},"on",[123,463,137],{"class":129},[123,465,466],{"class":140},"'accolade_completed'",[123,468,469],{"class":129},", (",[123,471,473],{"class":472},"s4XuR","accoladeId",[123,475,476],{"class":129},", ",[123,478,433],{"class":472},[123,480,481],{"class":129},") ",[123,483,484],{"class":314},"=>",[123,486,487],{"class":129}," {\n",[123,489,490,493,496,498,501,503,506,508,511,514,517,520],{"class":125,"line":151},[123,491,492],{"class":129},"    game.",[123,494,495],{"class":133},"getCharacter",[123,497,137],{"class":129},[123,499,500],{"class":140},"'mc'",[123,502,359],{"class":129},[123,504,505],{"class":133},"addResource",[123,507,137],{"class":129},[123,509,510],{"class":140},"'gold'",[123,512,513],{"class":129},", points ",[123,515,516],{"class":314},"*",[123,518,519],{"class":324}," 10",[123,521,257],{"class":129},[123,523,524],{"class":125,"line":169},[123,525,526],{"class":129},"});\n",[14,528,529],{},"That fires the moment progress reaches the target, once per achievement, before any reward exists — so what an achievement actually pays is your game's decision.",[29,531],{},[32,533,535],{"id":534},"when-progress-is-ignored","When progress is ignored",[14,537,538],{},"Writes are silently dropped in three cases, by design:",[43,540,541,547,555],{},[46,542,543,546],{},[21,544,545],{},"Gallery replay."," Replayed scenes run their actions for real, so without this a re-watch would earn achievements again.",[46,548,549,554],{},[21,550,551,81],{},[73,552,553],{},"accolades_frozen"," Set this state from your debug tooling so seeding a test save doesn't unlock the catalog.",[46,556,557,560],{},[21,558,559],{},"Disabled entries."," A retired achievement never completes and never shows. Prefer disabling over deleting so old saves keep making sense, and add a new id rather than rewriting a shipped one.",[14,562,563],{},"An unknown achievement id logs an error instead of failing quietly.",[29,565],{},[32,567,569],{"id":568},"a-tracking-script","A tracking script",[14,571,572],{},"Everything above in practice — one file, imported from your game's main script:",[114,574,576],{"className":116,"code":575,"language":118,"meta":119,"style":119},"const { game } = window.engine;\n\n\u002F\u002F targets that come from data\ngame.setAccoladeTarget('complete_kitchen', game.getData('item_recipes').size);\n\n\u002F\u002F tiered family, driven by tag\ngame.on('sprout_birth', () => game.progressAccoladesByTag('births'));\n\n\u002F\u002F best-of, safe to call on every hit\ngame.on('battle_action_applied', (caster, ev) => {\n    if (ev.damage > 0) game.setAccoladeProgress('overkill', ev.damage);\n});\n\n\u002F\u002F distinct counting, with your own saved set\nconst seen = game.createStore('achievement_track');\ngame.on('status_added', (character, status) => {\n    const list = seen.get('statuses') || [];\n    if (!list.includes(status.id)) {\n        list.push(status.id);\n        seen.set('statuses', list);\n    }\n    game.setAccoladeProgress('afflicted', list.length);\n});\n",[73,577,578,595,599,604,628,632,638,667,672,678,706,731,736,741,747,770,798,827,845,857,873,879,899],{"__ignoreMap":119},[123,579,580,582,585,587,590,592],{"class":125,"line":126},[123,581,339],{"class":314},[123,583,584],{"class":129}," { ",[123,586,111],{"class":324},[123,588,589],{"class":129}," } ",[123,591,321],{"class":314},[123,593,594],{"class":129}," window.engine;\n",[123,596,597],{"class":125,"line":151},[123,598,263],{"emptyLinePlaceholder":262},[123,600,601],{"class":125,"line":169},[123,602,603],{"class":147},"\u002F\u002F targets that come from data\n",[123,605,606,608,610,612,615,618,620,622,625],{"class":125,"line":266},[123,607,130],{"class":129},[123,609,410],{"class":133},[123,611,137],{"class":129},[123,613,614],{"class":140},"'complete_kitchen'",[123,616,617],{"class":129},", game.",[123,619,351],{"class":133},[123,621,137],{"class":129},[123,623,624],{"class":140},"'item_recipes'",[123,626,627],{"class":129},").size);\n",[123,629,630],{"class":125,"line":272},[123,631,263],{"emptyLinePlaceholder":262},[123,633,635],{"class":125,"line":634},6,[123,636,637],{"class":147},"\u002F\u002F tiered family, driven by tag\n",[123,639,641,643,645,647,650,653,655,657,659,661,664],{"class":125,"line":640},7,[123,642,130],{"class":129},[123,644,461],{"class":133},[123,646,137],{"class":129},[123,648,649],{"class":140},"'sprout_birth'",[123,651,652],{"class":129},", () ",[123,654,484],{"class":314},[123,656,348],{"class":129},[123,658,249],{"class":133},[123,660,137],{"class":129},[123,662,663],{"class":140},"'births'",[123,665,666],{"class":129},"));\n",[123,668,670],{"class":125,"line":669},8,[123,671,263],{"emptyLinePlaceholder":262},[123,673,675],{"class":125,"line":674},9,[123,676,677],{"class":147},"\u002F\u002F best-of, safe to call on every hit\n",[123,679,681,683,685,687,690,692,695,697,700,702,704],{"class":125,"line":680},10,[123,682,130],{"class":129},[123,684,461],{"class":133},[123,686,137],{"class":129},[123,688,689],{"class":140},"'battle_action_applied'",[123,691,469],{"class":129},[123,693,694],{"class":472},"caster",[123,696,476],{"class":129},[123,698,699],{"class":472},"ev",[123,701,481],{"class":129},[123,703,484],{"class":314},[123,705,487],{"class":129},[123,707,709,711,714,717,719,722,724,726,728],{"class":125,"line":708},11,[123,710,370],{"class":314},[123,712,713],{"class":129}," (ev.damage ",[123,715,716],{"class":314},">",[123,718,325],{"class":324},[123,720,721],{"class":129},") game.",[123,723,174],{"class":133},[123,725,137],{"class":129},[123,727,179],{"class":140},[123,729,730],{"class":129},", ev.damage);\n",[123,732,734],{"class":125,"line":733},12,[123,735,526],{"class":129},[123,737,739],{"class":125,"line":738},13,[123,740,263],{"emptyLinePlaceholder":262},[123,742,744],{"class":125,"line":743},14,[123,745,746],{"class":147},"\u002F\u002F distinct counting, with your own saved set\n",[123,748,750,752,755,758,760,763,765,768],{"class":125,"line":749},15,[123,751,339],{"class":314},[123,753,754],{"class":324}," seen",[123,756,757],{"class":314}," =",[123,759,348],{"class":129},[123,761,762],{"class":133},"createStore",[123,764,137],{"class":129},[123,766,767],{"class":140},"'achievement_track'",[123,769,257],{"class":129},[123,771,773,775,777,779,782,784,787,789,792,794,796],{"class":125,"line":772},16,[123,774,130],{"class":129},[123,776,461],{"class":133},[123,778,137],{"class":129},[123,780,781],{"class":140},"'status_added'",[123,783,469],{"class":129},[123,785,786],{"class":472},"character",[123,788,476],{"class":129},[123,790,791],{"class":472},"status",[123,793,481],{"class":129},[123,795,484],{"class":314},[123,797,487],{"class":129},[123,799,801,804,807,809,812,815,817,820,822,824],{"class":125,"line":800},17,[123,802,803],{"class":314},"    const",[123,805,806],{"class":324}," list",[123,808,757],{"class":314},[123,810,811],{"class":129}," seen.",[123,813,814],{"class":133},"get",[123,816,137],{"class":129},[123,818,819],{"class":140},"'statuses'",[123,821,481],{"class":129},[123,823,376],{"class":314},[123,825,826],{"class":129}," [];\n",[123,828,830,832,834,837,840,842],{"class":125,"line":829},18,[123,831,370],{"class":314},[123,833,336],{"class":129},[123,835,836],{"class":314},"!",[123,838,839],{"class":129},"list.",[123,841,382],{"class":133},[123,843,844],{"class":129},"(status.id)) {\n",[123,846,848,851,854],{"class":125,"line":847},19,[123,849,850],{"class":129},"        list.",[123,852,853],{"class":133},"push",[123,855,856],{"class":129},"(status.id);\n",[123,858,860,863,866,868,870],{"class":125,"line":859},20,[123,861,862],{"class":129},"        seen.",[123,864,865],{"class":133},"set",[123,867,137],{"class":129},[123,869,819],{"class":140},[123,871,872],{"class":129},", list);\n",[123,874,876],{"class":125,"line":875},21,[123,877,878],{"class":129},"    }\n",[123,880,882,884,886,888,891,894,897],{"class":125,"line":881},22,[123,883,492],{"class":129},[123,885,174],{"class":133},[123,887,137],{"class":129},[123,889,890],{"class":140},"'afflicted'",[123,892,893],{"class":129},", list.",[123,895,896],{"class":324},"length",[123,898,257],{"class":129},[123,900,902],{"class":125,"line":901},23,[123,903,526],{"class":129},[14,905,906],{},"Keep it in one file. The engine gives you the storage, the notification and the screen; this script is the only place that knows what your game considers an achievement.",[908,909,910],"style",{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":119,"searchDepth":151,"depth":151,"links":912},[913,914,915,916,917,918,919],{"id":34,"depth":151,"text":35},{"id":104,"depth":151,"text":105},{"id":229,"depth":151,"text":96},{"id":297,"depth":151,"text":80},{"id":426,"depth":151,"text":427},{"id":534,"depth":151,"text":535},{"id":568,"depth":151,"text":569},"md",{"plugin":922,"category":923,"page":12},"global_essentials","advanced","\u002Fdocs\u002Fglobal_essentials\u002Fadvanced\u002Fachievements",{"title":5,"description":16},"docs\u002Fglobal_essentials\u002Fadvanced\u002Fachievements","WvuU0C735hNY_AvLXxadyKj1HMV5BV_zh9ZqasFBAPM",1788567315834]