Jump to content
Main menu
Main menu
move to sidebar
hide
Navigation
Main page
Recent changes
Random page
MediaWiki help
Creopedia
Search
Search
Appearance
Log in
Personal tools
Log in
View source for Module:Effect
Module
Discussion
English
Read
View source
View history
Tools
Tools
move to sidebar
hide
Actions
Read
View source
View history
General
What links here
Related changes
Special pages
Page information
Appearance
move to sidebar
hide
←
Module:Effect
require( 'strict' ) local infoboxItemCard = require( 'Module:InfoboxLua/Components/Item/Card' ) local p = {} --- Items, traits, abilities and moves all carry the same effect map shape, but --- they do not all read the same way, so each gets its own context. local DEFAULT_CONTEXT = 'item' --- How a self-targeted effect reads, per context. --- --- This is the one clause that cannot be shared. An item is worn, a move is --- used, and a trait simply belongs to the creo, so no single wording serves --- all four without reading as nonsense somewhere. local SELF_CLAUSE = { item = 'to the equipped creo', trait = 'to the creo itself', ability = 'to the creo itself', move = 'to the user', } --- Trigger and target pairings so ordinary that a note would be noise, keyed --- 'trigger id:target id' within each context. --- --- Measured with `effects.py pairs --kind <k>`, not guessed. What counts as --- obvious differs by kind: a move that inflicts something on attack hit is --- just an attacking move, while the same pairing on an item is the whole --- reason the item is interesting. local OBVIOUS = { item = { ['59:0'] = true, -- On stat calculation, to the equipped creo ['1:8'] = true, -- On use, to the chosen creo ['1:50'] = true, -- On use, to you }, trait = { ['59:0'] = true, -- On stat calculation, to the creo itself }, ability = { -- Every ability effect is this one pairing, so no ability shows a note. ['1:50'] = true, -- On use, to you }, move = { ['45:1'] = true, -- On attack hit, to the opposing creo }, } --- How each trigger reads in a note. --- --- Keyed by the numeric id rather than the display name, because ids are stable --- across game data refreshes and the names are not. local TRIGGER_PHRASE = { [1] = 'when used', [2] = 'when the battle starts', [3] = 'when the battle ends', [4] = 'when the creo is summoned', [5] = 'when the creo faints', [6] = 'after the creo is hit', [7] = 'before the creo acts', [8] = "before the opponent's turn", [9] = 'while below 75% HP', [10] = 'while below 50% HP', [11] = 'while below 25% HP', [12] = 'when HP is regained', [13] = 'while below 75% energy', [14] = 'while below 50% energy', [15] = 'while below 25% energy', [16] = 'when energy runs out', [17] = 'on capture', [26] = 'while below full HP', [27] = 'while below full energy', [28] = 'on level up', [29] = 'on prestige up', [30] = 'when the creo is hit', [31] = 'before the creo is hit', [32] = 'after the creo acts', [34] = 'when an item is used', [35] = 'when stance changes', [36] = 'after an item is used', [37] = 'when an attack is used', [38] = 'before an attack is used', [39] = 'after an attack is used', [40] = 'when turn order is decided', [41] = 'before escaping', [42] = 'before the creo is summoned', [43] = 'while moving', [44] = 'always active', [45] = 'on attack hit', [46] = 'before an attack hits', [47] = 'after an attack hits', [49] = 'when a party member is chosen', [50] = 'when experience is earned', [51] = 'when money is earned', [52] = 'when HP change is calculated', [53] = 'when energy use is calculated', [54] = 'when experience is calculated', [55] = 'when accuracy is calculated', [56] = 'when energy regeneration is calculated', [57] = 'while exploring', [58] = 'when creo rarity is rolled', [59] = 'always active', [60] = 'when a boon is gained', [61] = 'when a boon is lost', [62] = 'when a condition is applied', [63] = 'when a condition is removed', [64] = 'at the condition limit', [65] = 'at the boon limit', [66] = 'when a capture is resolved', [67] = 'when an escape is resolved', [68] = 'when money earned is calculated', [69] = 'when money lost is calculated', [70] = 'before stance changes', [71] = 'on entering a map', [72] = 'when a boon counter changes', [73] = 'when a condition counter changes', [74] = 'when the creo is healed', } --- How each target reads in a note. --- --- The preposition is part of the clause, because the targets do not all take --- the same one. An effect applies *to* a creo, but 'to the world' reads as --- nonsense where 'affecting the world' does not. --- --- Target 0 is deliberately absent: it comes from SELF_CLAUSE instead. local TARGET_CLAUSE = { [1] = 'to the opposing creo', [2] = 'to every creo on the field', [3] = 'to your party', [4] = "to the opponent's party", [5] = 'to both parties', [6] = 'to your active creo', [8] = 'to the chosen creo', [50] = 'to you', [51] = 'to the opposing trainer', [52] = 'to both trainers', [80] = 'affecting the world', [81] = 'affecting the whole battle', [100] = 'affecting everything', } --- Names the casing rule below cannot reach on its own, because they need a --- word inserted rather than a letter changed. --- --- Watch the spelling of the keys: the export writes 'Immune Bad burn' with a --- lower case burn but 'Immune Bad Bleed' with an upper case bleed. They are --- matched exactly, so they are copied from `effects.py names` rather than --- typed out from memory. local NAME_OVERRIDES = { ['Immune Bad Bleed'] = 'Immune to bad bleed', ['Immune Bad burn'] = 'Immune to bad burn', ['Immune Bad Poison'] = 'Immune to bad poison', } --- @param context string|nil --- @return string local function contextOf( context ) return SELF_CLAUSE[context] and context or DEFAULT_CONTEXT end --- @param targetId number|nil --- @param context string --- @return string|nil local function targetClause( targetId, context ) if targetId == 0 then return SELF_CLAUSE[context] end return TARGET_CLAUSE[targetId] end --- The effect name as the wiki shows it: the game's own wording, cased sanely. --- --- The export mixes shouting and title case within one table, so a plain list --- of effect names puts 'REGEN 50HP' next to 'Special up 25%' next to --- 'LINK Base Chance' and reads as though it came from three different games. --- Lower casing and restoring the first letter settles that without inventing --- wording the game never used. --- --- @param name string --- @return string local function tidyName( name ) if NAME_OVERRIDES[name] then return NAME_OVERRIDES[name] end local text = mw.ustring.lower( mw.text.trim( name ) ) -- 'REGEN 50HP' -> 'Regen 50 HP', 'Air affinity up 20pts' -> '... 20 pts'. -- These run first, so the HP they write is not matched again below. text = mw.ustring.gsub( text, '(%d)%s*hp', '%1 HP' ) text = mw.ustring.gsub( text, '(%d)%s*pts', '%1 pts' ) -- Whole word fixes: 'Absorb fire 20% HP recovered' and 'One hit KO', where -- the token stands alone rather than following a digit. Padded with spaces -- so a match at either end of the string still has a neighbour, rather than -- relying on a frontier pattern. text = ' ' .. text .. ' ' text = mw.ustring.gsub( text, '([^%w])hp([^%w])', '%1HP%2' ) text = mw.ustring.gsub( text, '([^%w])ko([^%w])', '%1KO%2' ) text = mw.ustring.sub( text, 2, -2 ) -- Sentence case, but only when the name begins with a letter. Capitalising -- the first letter *found* instead would turn '2 to 6 Multi-hit' into -- '2 To 6 multi-hit'. local first = mw.ustring.sub( text, 1, 1 ) if mw.ustring.match( first, '%a' ) then text = mw.ustring.upper( first ) .. mw.ustring.sub( text, 2 ) end return text end --- @param effectMap table --- @return string|nil local function getEffectName( effectMap ) if not effectMap or type( effectMap.effect ) ~= 'table' or not effectMap.effect.name then return nil end return tidyName( effectMap.effect.name ) end --- A block of effect lines. --- --- Built with mw.html rather than as a '*' wikitext list, because the result is --- handed to :wikitext() inside a div, where a bullet list needs leading --- newlines to parse and will not reliably render. --- --- @param lines string[] --- @param class string --- @return string local function renderList( lines, class ) local list = mw.html.create( 'ul' ):addClass( class ) for _, line in ipairs( lines ) do list:tag( 'li' ):wikitext( line ) end return tostring( list ) end --- One line of the Effects list. --- --- The chance is shown only when it is not a certainty. No effect map in the --- August 2026 export has a chance of zero, so a bare '< 100' test carries no --- 'zero means always' trap. --- --- @param effectMap table --- @return string|nil function p.getLabel( effectMap ) local name = getEffectName( effectMap ) if not name then return nil end local chance = effectMap.triggerChance if type( chance ) == 'number' and chance < 100 then return string.format( '%s (%s%%)', name, tostring( chance ) ) end return name end --- One line of the Notes list, or nil when the timing and target are obvious. --- --- A trigger or target with no phrase falls back to the wording in the game --- data rather than dropping the note. A refresh that introduces a new trigger --- should read stiffly for a while, not silently lose information. --- --- @param effectMap table --- @param context string|nil 'item' | 'trait' | 'ability' | 'move' --- @return string|nil function p.getNote( effectMap, context ) local name = getEffectName( effectMap ) if not name then return nil end context = contextOf( context ) local trigger = type( effectMap.effectTrigger ) == 'table' and effectMap.effectTrigger or {} local target = type( effectMap.target ) == 'table' and effectMap.target or {} if trigger.id and target.id and OBVIOUS[context][trigger.id .. ':' .. target.id] then return nil end local when = TRIGGER_PHRASE[trigger.id] local who = targetClause( target.id, context ) if not when and trigger.name then when = mw.ustring.lower( trigger.name ) end if not who and target.name then who = 'to ' .. mw.ustring.lower( target.name ) end if not when and not who then return nil end if not who then return string.format( '%s: %s.', name, when ) end if not when then return string.format( '%s: applies %s.', name, who ) end return string.format( '%s: %s, %s.', name, when, who ) end --- The Effects card, and the Notes card when any effect earns one. --- --- Cards rather than plain infobox items: a section is a two or three column --- grid, and Module:InfoboxLua/styles.css spans a card across the whole row. --- A plain item would squeeze the list into one narrow cell. --- --- @param effectMaps table[]|nil --- @param context string|nil 'item' | 'trait' | 'ability' | 'move' --- @return table[] zero, one or two ItemCardComponentData function p.getInfoboxCards( effectMaps, context ) context = contextOf( context ) local labels, notes = {}, {} -- Guarded, because a record with no effect maps at all would otherwise stop -- the whole infobox with a script error. for _, effectMap in ipairs( effectMaps or {} ) do local label = p.getLabel( effectMap ) local note = p.getNote( effectMap, context ) if label then table.insert( labels, label ) end if note then table.insert( notes, note ) end end local cards = {} if #labels == 0 then return cards end table.insert( cards, infoboxItemCard.getItemComponentData( { label = 'Effects', content = renderList( labels, 't-infobox-effects' ) } ) ) if #notes > 0 then table.insert( cards, infoboxItemCard.getItemComponentData( { label = 'Notes', content = renderList( notes, 't-infobox-notes' ) } ) ) end return cards end return p
Template used on this page:
Module:Effect/doc
(
view source
)
Return to
Module:Effect
.