Module:ParentItemCalc

local calc = {}

-- Load modules local iter = require("Module:Functional").iter local statistic = require("Module:Statistic") local data = require("Module:Data")

local DurationConverter = require("Module:DurationConverter")

local GameUIBuilder = require("Module:GameUIBuilder") local Button = GameUIBuilder.GameUIButtong local Menu = GameUIBuilder.GameUIMenu local Label = GameUIBuilder.GameUILabel local List = GameUIBuilder.GameUIList local Tabber = GameUIBuilder.GameUITabber local Table = GameUIBuilder.GameUITable

local GameUITemplates = require("Module:GameUITemplates") local BackButton = GameUITemplates.GameUIBackButton local SidebarInfobox = GameUITemplates.GameUISidebarInfobox local DialogMenu = GameUITemplates.GameUIDialogMenu

local Item = require("Module:Item").Item local AGGREGATED_STAT_NAMES = require("Module:ParentItem").AGGREGATED_STAT_NAMES local Ship = require("Module:Ship").Ship local Module = require("Module:Module").Module

local SLOTS = iter(data.SLOT_ICONS):keys:totable

-- Maps each firing arc width to their corresponding image local FIRING_ARC_IMAGES = { [45] = "FiringArc45.png", [65] = "FiringArc65.png", [90] = "FiringArc90.png", [120] = "FiringArc120.png", [195] = "FiringArc195.png", [270] = "FiringArc270.png", [360] = "FiringArc360.png", }

-- Gets the overall menu for the parent item -- `parent_item_type` can either be "Ship" or "Module" -- `layer` can either be "0" or "1" -- `selected_slot_type` and `skin` are optional function calc.getOverallMenu( parent_item, parent_item_type, layer, selected_slot_id ) local menu_title = "" if parent_item_type == "Ship" then menu_title = "Ship Factory" elseif parent_item_type == "Module" then menu_title = "Manage Module" else error(string.format("Unidentified `parent_item_type` %s", parent_item_type)) end assert(layer == "0" or layer == "1", string.format("Unidentified `layer` %s", layer)) local wrapper = Menu(nil, nil, menu_title):css({		["height"] = "768px",		["width"] = "100%",		["max-width"] = "1024px",	})

local notice = Label({ "hide-if-js", "label-error" }, nil, nil, "No JavaScript detected. Please enable JavaScript for your browser.") wrapper:node(notice) local menu = mw.html.create("div"):addClass("hide-if-nojs"):addClass("parent-item-calc"):css({		["height"] = "100%",		["width"] = "100%",	}) wrapper:node(menu) -- Set up data attributes local skin_slot_id = nil for id, slot in parent_item:getSlots do		if slot.slot_type == "Skin" then skin_slot_id = id			break end end menu:attr{ ["data-fullpagename"] = parent_item._pageName, ["data-current-layer"] = layer, ["data-selected-slot-id"] = selected_slot_id, ["data-skin-slot-id"] = skin_slot_id, }	-- Create item selection menu local equipment_selection = calc.getEquipmentSelection(parent_item, selected_slot_id) menu:node(equipment_selection) -- Create config menu local config_menu = calc.getSlotConfigMenu(parent_item, layer, skin) menu:node(config_menu) local sidebar_menu = calc.getSidebarMenu(parent_item) menu:node(sidebar_menu) return wrapper end

-- Returns a nested table { base_name -> {items_with_base_name} } local function groupItemsByBaseName(items) local result = {} iter(items):each(function (item)		local base_name, suffix = item:getBaseNameAndLevelSuffix		result[base_name] = result[base_name] or {}		result[base_name][suffix or ""] = item	end) return result end

-- Gets the equipment selection menu for the parent item function calc.getEquipmentSelection( parent_item, selected_slot_id ) if not selected_slot_id then return SidebarInfobox("Select a slot to configure", nil) end local SUFFIXES = { "", "I", "II", "III", "IV", "V" } local tier = parent_item.tier if tier then tier = tier:sub(1, 1) else tier = "" end local role = parent_item.role or "" local class = parent_item.class or "" local faction = parent_item.faction or "" local query_args = {where = string.format(		(equip_wl_tiers IS NULL OR equip_wl_tiers HOLDS '%s')		AND (equip_bl_tiers IS NULL OR NOT equip_bl_tiers HOLDS '%s')		(equip_wl_roles IS NULL OR equip_wl_roles HOLDS '%s')		AND (equip_bl_roles IS NULL OR NOT equip_bl_roles HOLDS '%s')		(equip_wl_classes IS NULL OR equip_wl_classes HOLDS '%s')		AND (equip_bl_classes IS NULL OR NOT equip_bl_classes HOLDS '%s')		(equip_wl_factions IS NULL OR equip_wl_factions HOLDS '%s')		AND (equip_bl_factions IS NULL OR NOT equip_bl_factions HOLDS '%s'), tier, tier, role, role, class, class, faction, faction)} local avail_items = Item.class.ATTRIBUTE_TABLE:query(query_args) local avail_items_by_base_name = groupItemsByBaseName(avail_items) local menu = SidebarInfobox(parent_item:getSlotType(selected_slot_id), nil):addClass("equipment-selection") local content = menu:tag("div"):css("display", "grid"):css("grid-template-columns", "50%, 50%") local left = content:tag("div"):addClass("left"):css("grid-column", "1") local right = content:tag("div"):addClass("right"):css("grid-column", "2") iter(avail_items_by_base_name):each(function (base_name, items)		local rarity = iter(SUFFIXES):map(function (suffix) return items[suffix] end):head.rarity		local left_sub = mw.html.create("div"):addClass("select-equipment-left"):attr("data-basename", base_name):wikitext(mw.getCurrentFrame:expandTemplate{ title = "Colored Text", args = { base_name, rarity } })		left:node(left_sub)		local right_sub = mw.html.create("div"):addClass("select-equipment-right"):attr("data-basename", base_name)		iter(SUFFIXES):each(function (suffix) local item = items[suffix] right_sub:node(item:generateSelectableIcon(64):addclass("select-item"):attr("data-fullpagename", item._pageName)) end)		right:node(right_sub)	end) return menu:as_mw_html end

-- Gets the slot configuration menu for the parent item function calc.getSlotConfigMenu( parent_item, layer, skin ) local faction, rarity, name = parent_item.faction, parent_item.rarity, parent_item.name local base_name, suffix = parent_item:getBaseNameAndLevelSuffix local slots = parent_item:getSlots local config_menu = Label({"config-menu", "game-ui-itemconfig-label"}, {		["position"] = "relative",		["background-color"] = "black",	}, nil)

local background_image = Label(nil, {		["position"] = "absolute",		["width"] = "358px",		["height"] = "444px",		["top"] = "66px",		["left"] = "38px",	}, string.format("", base_name:gsub("%s", ""))) config_menu:node(background_image) local faction_icon = Label(nil, {		["position"] = "absolute",		["width"] = "40px",		["height"] = "40px",		["left"] = "5px",		["top"] = "2px",	}, string.format("", data.FACTION_ICONS[faction])) config_menu:node(faction_icon) local faction_title = Label(nil, {		["position"] = "absolute",		["left"] = "48px",		["top"] = "2px",		["justify-content"] = "start",		["text-transform"] = "uppercase",	}, mw.getCurrentFrame:expandTemplate{ title = "Colored Text", args = { faction, faction } }) config_menu:node(faction_title)

local item_title = Label(nil, {		["position"] = "absolute",		["left"] = "48px",		["top"] = "18px",		["justify-content"] = "start",		["font-size"] = "1.2em",	}, mw.getCurrentFrame:expandTemplate{ title = "Colored Text", args = { name, rarity } }) config_menu:node(item_title) local list_factions_button = Label({"list-factions"}) list_factions_button:css{ ["position"] = "absolute", ["right"] = "107px", ["top"] = "3px", ["width"] = "50px", ["height"] = "50px", }	config_menu:node(list_factions_button) local view_skin_button = Label({"view-skin"}, nil, nil, {parent_item._pageName, parent_item.image, skin}) view_skin_button:css{ ["position"] = "absolute", ["right"] = "55px", ["top"] = "3px", ["width"] = "50px", ["height"] = "50px", }	config_menu:node(view_skin_button) local toggle_layer_button = Label({"toggle-layer"}, nil, nil, {layer}) toggle_layer_button:css{ ["position"] = "absolute", ["right"] = "3px", ["top"] = "3px", ["width"] = "50px", ["height"] = "50px", }	config_menu:node(toggle_layer_button) iter(parent_item.firing_arc_widths):zip(parent_item.firing_arc_offsets):each(function (width, offset)		local firing_arc_label = Label(nil, { ["position"] = "absolute", ["right"] = "4px", ["top"] = "56px", ["width"] = "50px", ["height"] = "50px", ["transform"] = string.format("rotate(%.2fdeg)", offset) }, string.format("", FIRING_ARC_IMAGES[width]))		firing_arc_label:tag("span").addClass("tooltip-hover-content"):wikitext(string.format("Width: %s deg Offset: %s deg", width, offset))		config_menu:node(firing_arc_label)	end) local firing_arc_center = Label(nil, {		["position"] = "absolute",		["right"] = "4px",		["top"] = "56px",		["width"] = "50px",		["height"] = "50px",	}, "") config_menu:node(firing_arc_center) iter(slots):map(function (slot)		local slot_type, content = slot.slot_type, slot.content		local slot_icon = data.SLOT_ICONS[slot_type]		local slot_label, content_fullpagename, content_basename = nil, nil, nil		if content == nil then			slot_label = Label(nil, nil, string.format("", slot_icon))		else			slot_label = content:generateOccupiedSlotIcon			content_fullpagename = content._pageName			content_basename, _ = content:getBaseNameAndLevelSuffix		end		slot_label:css{			["position"] = "absolute",			["right"] = string.format("calc(%.1f%% + 2px)", slot.relative_right),			["top"] = string.format("calc(%.1f%% + 45px)", slot.relative_top),			["width"] = "88px",			["height"] = "56px",		}		slot_label:addClass("slot"):attr{			["data-slot-id"] = slot_id,			["data-content-fullpagename"] = content_fullpagename,			["data-content-basename"] = content_basename,		}		config_menu:node(slot_label)	end) local mass, max_mass = parent_item:getTotalMass, parent_item:tryGetStat("Maximum Mass") local mass_bar = Label(nil, {		["position"] = "absolute",		["left"] = "73px",		["top"] = "610px",		["height"] = "26px",		["box-sizing"] = "border-box",	}, nil) if mass <= max_mass then mass_bar:css{ ["width"] = string.format("calc(288px * %d / %d)", mass, max_mass), ["border-top"] = "1px solid #70CFE7", ["background-color"] = "#679EA9", }	else mass_bar:css{ ["width"] = "288px", ["border-top"] = "1px solid #D36D6D", ["background-color"] = "#7E3238", }	end config_menu:node(mass_bar) local mass_text = Label(nil, {		["position"] = "absolute",		["left"] = "73px",		["top"] = "610px",		["width"] = "288px",		["height"] = "26px",		["font-size"] = "0.95em",		["color"] = "#D4F8FF",	}, string.format("%d / %d t", mass, max_mass)) config_menu:node(mass_text) return config_menu end

-- Gets the sidebar menu which displays the parent item's overall stats, in addition to its modified stats and modifiers. function calc.getSidebarMenu( parent_item ) local aggregated_stats = parent_item:getAggregatedStats local stats = parent_item:getStatistics local modifiers = parent_item:getModifiers local resistance = parent_item:getResistance local shield_resistance = parent_item:getShieldResistance local screen_resistance = parent_item:getScreenResistance local sidebar_menu = Tabber({"sidebar-menu"}) if aggregated_stats or resistance or shield_resistance or screen_resistance then local list = List if aggregated_stats then statistic.appendStatisticRows(list, aggregated_stats) end if resistance then statistic.appendStatisticRows(list, resistance) end if shield_resistance then statistic.appendStatisticRows(list, shield_resistance) end if screen_resistance then statistic.appendStatisticRows(list, screen_resistance) end sidebar_menu:insertTabAt("General", list) end if stats then local list = stats:asGameUIList if list:getNumRows > 0 then tabber:insertTabAt("Stats", list) end end if modifiers then local list = modifiers:asGameUIList if list:getNumRows > 0 then tabber:insertTabAt("Modifiers", list) end end if abilities then local list = abilities:asGameUIList if list:getNumRows > 0 then tabber:insertTabAt("Abilities", list) end end return sidebar_menu end

-- Extracts equipment instances from frame args. -- Returns a map { slot_id -> equipment } local function getEquipmentFromArgs( args ) return iter(args):filter(function (k, v) return k:find("slot_") end):each(function (k, v)		local id = k:gsub("slot_", "")		local equipment = Item:fromPageName(v)		return id, equipment	end):tomap end

-- Module call from Template:Ship Calc function calc.getOverallMenu( frame ) local args = require("Module:Args").getCleanArgs local fullpagename = assert(args.full_name, "Missing argument `full_name`") local layer = assert(args.layer, "Missing argument `layer`") local selected_slot_id = args.selected_slot_id local skin_fullpagename = args.skin_fullpagename local category = Item:fromPageName(fullpagename).category local parent_item if category == "Ship" then parent_item = Ship:fromPageName(fullpagename) elseif category == "Module" then parent_item = Module:fromPageName(fullpagename) else error(string.format("The item %s is not a ship or module", fullpagename)) end iter(getEquipmentFromArgs(args)):each(function (id, equipment)		parent_item:equip(equipment, id, true)	end) parent_item:recalculate local skin = nil if skin_fullpagename then skin = skin_fullpagename:gsub(".*:", "") end return calc.getOverallMenu(ship, category, layer, selected_slot_id, skin) end

-- Module call from JavaScript function calc.listFactions( frame ) local args = require("Module:Args").getCleanArgs local menu = DialogMenu("Select Faction", BackButton:addClass("return")) iter(data.FACTIONS):each(function (faction)		local faction_icon = data.FACTION_ICONS[faction]		local faction_label = Label({"select-faction"}, nil, string.format(" %s", faction_icon, faction), {faction})		menu:node(faction_label)	end) return menu:as_mw_html end

-- Module call from JavaScript function calc.listShips( frame ) local args = require("Module:Args").getCleanArgs local faction = assert(args[1], "Missing argument #1") local SUFFIXES = { "", "Mk II", "Mk III", "Mk IV", "Mk V", "Elite" } local menu = DialogMenu("Select Ship", BackButton:addClass("return")) local query_args = {where = string.format("firing_arc_widths IS NOT NULL AND faction = '%s'", faction)} local ships_in_faction = Item.class.ATTRIBUTE_TABLE:query(query_args) iter(groupItemsByBaseName(ships_in_faction)):each(function (name, ships)		local outer = Label(nil, {["height"] = "64px"}, nil)		local image_lbl = Label(nil, { ["left"] = "0", ["top"] = "0", ["height"] = "64px", ["width"] = "64px", }, string.format("", v[1].image))		local name_lbl = Label(nil, { ["left"] = "4px", ["top"] = "4px", }, mw.getCurrentFrame:expandTemplate{ title = "Colored Text", args = { name, v[1].rarity } })		outer:node(image_lbl):node(name_lbl)		local container = Label(nil, { ["left"] = "64px", ["top"] = "32px", }, nil)		iter(SUFFIXES):each(function (suffix) local ship = ships[suffix] if ship then local select_lbl = Label({"select-ship"}, {					["height"] = "32px",					["width"] = "32px",				}, suffix):attr("data-fullpagename", _pageName) container:node(select_lbl) end end)		outer:node(container)		menu:node(outer)	end) return menu:as_mw_html end

-- Module call from JavaScript -- Assumes that the icon and angled render are both PNGs function calc.showSkin( frame ) local args = require("Module:Args").getCleanArgs local current_ship = assert(args[1], "Missing argument #1") local icon = Item:fromPageName(current_ship).image:gsub("%.png", "") local current_skin = args[3] local info_text, angled_img if current_skin ~= nil and current_skin ~= "" then info_text = string.format("Previewing the default look of %s (Equip a skin to preview it)", current_ship) angled_img = string.format("%s-Angled.png", icon) else info_text = string.format("Previewing the %s skin on %s", current_skin, current_ship) angled_img = string.format("%sAngled.png", icon) end local menu = DialogMenu("Ship Preview", BackButton:addClass("return")) local info_lbl = Label(nil, nil, info_text) local angled_lbl = Label(nil, nil, string.format("", angled_img)) menu:node(info_lbl):node(angled_lbl) return menu:as_mw_html end

return calc