Module:StatusEffect

local se = {}

-- Load modules local fun = require("Module:Functional") local iter = fun.iter local op = fun.op

local class = require("Module:Class") local data = require("Module:Data") local statistic = require("Module:Statistic")

local CargoUtils = require("Module:CargoUtils") local Variables = require("Module:Variables")

local GameUIBuilder = require("Module:GameUIBuilder") local List = GameUIBuilder.GameUIList

local GameUITemplates = require("Module:GameUITemplates") local ShowAbilityButton = GameUITemplates.ShowAbilityButton

-- Used for building the status effect across templates on the same page local PAGE_WIDE_VAR_NAME = "se_attr"

-- Updates the item that is persisted within the page. local function updatePageWideStatusEffect(se) -- Only serialize the attributes because string.dump is not allowed local as_attr = iter(item):map(function (k, v)		if k ~= "class" and type(v) ~= "function" then return k, v else return false end	end):filter(op.truth):tomap Variables.vardefine(PAGE_WIDE_VAR_NAME, se_attr) end

-- Returns the status effect that is persisted within the page. -- Raises an error if it has not been defined yet, unless the `allow_undefined` option is set, in which case returns nil. local function getPageWideStatusEffect(allow_undefined) local se_attr = Variables.var(PAGE_WIDE_VAR_NAME) local undefined = (se_attr == "" or se_attr == nil) if undefined then if allow_undefined then return nil else error("You have to first define an status effect on this page before this template") end end return se.StatusEffect(se_attr) end

-- Wrapper that represents an list of abilities in the game. AbilitiesList = class("AbilitiesList")

-- Constructor -- -- Creates a new AbilitiesList instance according to the given table { name -> { ["description"] = description, ["status_effect"] = status_effect } }, which may be empty. function AbilitiesList:initialize(abilities) abilities = abilities or {} self._abilities = mw.clone(abilities) end

-- Getters & Setters -- -- If `name` is not nil, returns the set of abilities in the list. -- Otherwise, returns the ability in the list identified by `name`. function AbilitiesList:get(name) if name == nil then return mw.clone(self._abilities) else if type(name) ~= "string" then error(string.format("`name` must be nil or a string, but found %s", mw.dumpObject(name))) end return self._abilities[name] end end

-- Static methods -- -- Returns the formatted string for each value inputted into the description function AbilitiesList.static:getFormattedInput(inp) asesrt(type(inp) == type(""), "`inp` must be a string") return mw.getCurrentFrame:expandTemplate{ title = "Colored Text", args = { inp, "Statistic" } } end

-- Appends the HTML display of a name/description/status_effect tuple to a GameUIList object. function AbilitiesList.static:appendAbilityRowToGameUIList(list, name, description, status_effect) if type(list) ~= "table" then error(string.format("`list` must be a table, but found %s", mw.dumpObject(list))) end if type(name) ~= "string" then error(string.format("`name` must be a string, but found %s", mw.dumpObject(name))) end local image = string.format("", status_effect.image) list:insertRowAt{ ["image_wikitext"] = image, ["left_wikitext"] = name, ["right_wikitext"] = ShowAbilityButton(name, description or "", status_effect._pageName), ["tooltip_wikitext"] = description, ["js_only"] = true, }	-- Fallback for no-JS list:insertRowAt{ ["image_wikitext"] = image, ["left_wikitext"] = statusname, ["tooltip_wikitext"] = description, ["no_js_only"] = true, }	list:insertRowAt{ ["image_wikitext"] = image, ["left_wikitext"] = "Effect", ["right_wikitext"] = string.format("%s", status_effect.main_page or status_effect._pageName, status_effect.name), ["tooltip_wikitext"] = description, ["no_js_only"] = true, }	return list end

-- Other Methods -- -- Returns the visual representation of the list of abilities as a GameUIList object. function AbilitiesList:asGameUIList return iter(self._abilities):reduce(function (acc, name, v)		AbilitiesList:appendAbilityRowToGameUIList(acc, name, v.description, v_status_effect)		return acc	end, List) end

-- Mixin that enables storage of an entity's abilities -- This uses the property _abilities EntityAbilitiesMixin = {}

-- Gets all of the entity's abilities. -- The result is an AbilitiesList instance. function EntityAbilitiesMixin:getAbilities local abilities_table = se.getAbilitiesTableFor(self.class.name) local abilities = abilities_table:query{ where = string.format("_pageName = '%s'", self._pageName) } -- Cache query result if not self._abilities then self._abilities = iter(abilities):map(function (v)			return v.name, {				["description"] = v.descriptiont,				["status_effect"] = v.status_effect,			}		end):tomap end return AbilitiesList(self._abilities) end

-- Sets the entity's abilities and stores them into the Cargo table. -- Also updates each ability so that they are converted from strings into Lua objects, making them -- consistent with the abilities obtained from querying the record. function EntityAbilitiesMixin:storeAbilities(abilities) self._abilities = mw.clone(abilities) local abilities_table = se.getAbilitiesTableFor(self.class.name) local store = "" iter(abilities):each(function (name, inner)		local out_query = {}		store = store .. abilities_table:store({ ["name"] = name, ["description"] = inner.description, ["status_effect"] = inner.status_effect, }, out_query)		self._abilities[out_query.name] = {			["description"] = out_query.description,			["status_effect"] = out_query.status_effect,		}	end) return store end

-- Creates a table that stores abilitiy information of an entity. -- There should be at most one instance of said entity per page. function se.getAbilitiesTableFor(entity_type) return CargoUtils.CargoTable(string.format("%sAbilities", entity_type),		{			["name"] = { "String", { mandatory = true }, { "NOT NULL" } },			["description"] = { "String" },			["status_effect"] = { "String" },			["is_passive"] = { "Boolean" },			["applies_to"] = { "String", { ["allowed values"] = iter(data.APPLIES_TO_STATS):chain(data.APPLIES_TO_NAME_SUBSTR):keys:tomap } },		},		{ "_pageName", "name" },		{			["_pageName"] = { entity_type, "_pageName" },			["status_effect"] = { "StatusEffect", "_pageName" },		}) end

-- Represents a status effect in the game. StatusEffect = data.Entity:subclass("StatusEffect"):include(statistic.EntityStatsMixin):include(statistic.EntityModifiersMixin):include(se.EntityAbilitiesMixin)

StatusEffect.static.ATTRIBUTE_TABLE = CargoUtils.CargoTable("StatusEffect",		{			["name"] = { "String", { mandatory = true }, { "NOT NULL" } },			["image"] = { "File", { mandatory = true }, { "NOT NULL" } },			["description"] = { "Wikitext" },			["main_page"] = { "Page" },		},		{ "_pageName" },		{})

-- Relationship-related tables StatusEffect.static.RELATIONAL_TABLES = { ["StatusEffectStats"] = statistic.getStatsTableFor("StatusEffect"), ["StatusEffectModifiers"] = statistic.getModifiersTableFor("StatusEffect"), ["StatusEffectAbilities"] = se.getAbilitiesTableFor("StatusEffect"), }

-- Static methods -- -- Returns a new StatusEffect instance with properties according to the record stored at the given page. function StatusEffect.static:fromPageName(fullpagename) if type(fullpagename) ~= "string" then error(string.format("`fullpagename` must be a string, but found %s", mw.dumpObject(fullpagename))) end local query_args = {where = string.format("_pageName = '%s'", fullpagename)} return StatusEffect(self.ATTRIBUTE_TABLE:query(query_args, true)[1]) end

-- Module call from Template:Status Effect Definition -- Takes in one parameter - the name of the Cargo table. If not given, declares -- `ATTRIBUTE_TABLE`. function se.declareTable( frame ) local args = require("Module:Args").getCleanArgs local name = args[1] local cargo_table if name then cargo_table = assert(StatusEffect.RELATIONAL_TABLES[name], string.format("The Cargo table '%s' is not registered in this module", name)) else cargo_table = StatusEffect.ATTRIBUTE_TABLE end return cargo_table:declare(frame) end -- Module call from Template:Status Effect Definition function se.define( frame ) local args = require("Module:Args").getCleanArgs assert(args.stats == nil, "The argument `stats` is not allowed in the status effect definition. Use Template:Status Effect Definition/stats instead.") assert(args.modifiers == nil, "The argument `modifiers` is not allowed in the status effect definition. Use Template:Status Effect Definition/modifiers instead.")

local se_to_store = StatusEffect(args) local store = se_to_store:storeToTable(StatusEffect.ATTRIBUTE_TABLE) -- Persist status effect page-wide so that other templates can append to it	updatePageWideStatusEffect(se_to_store) return store end

-- Module call from Template:Status Effect Definition/stats function se.appendStats( frame ) local args = require("Module:Args").getCleanArgs local stats = iter(args):map(function (k, v) return "Statistic:" .. k, v end):tomap -- Append to existing item local se = getPageWideStatusEffect local store = se:storeStatistics(stats) updatePageWideStatusEffect(se) return store end

-- Module call from Template:Status Effect Definition/modifiers function se.appendModifiers( frame ) local args = require("Module:Args").getCleanArgs local modifiers = iter(args):map(function (k, v)		local fullpagename = "Statistic:" .. k		local tmp = mw.text.split(modifier_info, "%s*;%s*")		local applies_to = iter(tmp):drop(2)		if applies_to:is_null then applies_to = iter{"All"} end		return fullpagename, applies_to:map(function (e) return e, { ["value"] = tmp[1], ["type"] = tmp[2], }		end):tomap	end):tomap -- Append to existing item local se = getPageWideStatusEffect local store = se:storeModifiers(modifiers) updatePageWideStatusEffect(se) return store end

-- Module call from Template:StatusEffect Definition/abilities function se.appendAbilities( frame ) local args = require("Module:Args").getCleanArgs local ability = { ["name"] = args.name, ["description"] = args.description, ["status_effect"] = "StatusEffect:" .. args.status_effect, }	-- Append to existing item local se = getPageWideStatusEffect local store = se:storeAbilities{ability} updatePageWideStatusEffect(se) return store end

-- Returns a string containing the HTML for the infobox for the status effect local function getStatusEffectInfoboxHTML(fullpagename) return tostring(StatusEffect:fromPageName(fullpagename):generateInfobox) end

-- Module call from Template:Status Effect Infobox function se.generateInfobox( frame ) local args = require("Module:Args").getCleanArgs -- Display item stored on this page, if it exists and `full_name` is not given local new_se = getPageWideStatusEffect(true) if new_se and not args.full_name then return new_se:generateInfobox end local fullpagename = assert(args.full_name, "Missing argument `full_name`") local general_only = args.general_only return getStatusEffectInfoboxHTML(fullpagename, general_only) end

-- Module call from JavaScript function se.generateAbilityBlock( frame ) local args = require("Module:Args").getCleanArgs local title = assert(args[1], "Missing argument #1") local description = assert(args[2], "Missing argument #2") local status_fullpagename = assert(args[3], "Missing argument #3") local status_effect = StatusEffect:fromPageName(status_fullpagename) local block = BlockInfobox(title, HideModifiersButton) block:node(InfoboxDescription(description)) local stats_list = status_effect:getStatistics:asGameUIList(nil, { ["use_colon"] = true }) local modifiers_list = status_effect:getModifiers:asGameUIList{ ["use_colon"] = true } local abilities_list = status_effect:getAbilities:asGameUIList local list = abilities_list:concatenate(stats_list):concatenate(modifiers_list) block:node(list) return block:as_mw_html end

-- Export classes se.StatusEffect = StatusEffect se.EntityAbilitiesMixin = EntityAbilitiesMixin se.AbilitiesList = AbilitiesList

return se