diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..69e5187
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+node_modules/
+files/
+*.json
\ No newline at end of file
diff --git a/.gitignore/.gitignore b/.gitignore/.gitignore
deleted file mode 100644
index b6d2f99..0000000
--- a/.gitignore/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
- node_modules/
- files/
- test.txt
diff --git a/These files are for the local mod/Yellow Machine.46ccee.lua b/These files are for the local mod/Yellow Machine.46ccee.lua
new file mode 100644
index 0000000..fc9943e
--- /dev/null
+++ b/These files are for the local mod/Yellow Machine.46ccee.lua
@@ -0,0 +1,2048 @@
+require("vscode/console")
+
+local DEBUG = true
+
+local loadedData, loadedDataOrder, originalLoadedOrder, uiHeight, uiWidth, useDecorativeNames, armyDisplay, armyText
+local url = DEBUG and "http://127.0.0.1:3000/get_army_by_id?id=" or "https://yellowscribe.net/get_army_by_id?id="
+
+local uiTemplates = {
+
+ EDIT_UNIT_CONTAINER = [[
+
+ |
+
+ |
+
+
+
+
+
+
+ ${modelEntries}
+
+
+
+ |
+
+ |
]],-- flexibleHeight="1" flexibleWidth="1" childForceExpandHeight="false"${unassignedWeaponsHeight}
+ EDIT_MODEL_ENTRY = [[
+
+ ${modelCount}${modelName}
+
+ ${weaponSection}
+ ${abilitySection}
+ ${unassignedSection}
+
+
+ | ]],
+ WEAPON_SECTION = [[
+ Weapons:
+
+ ${weapons}
+ ${assignedWeapons}
+
+ ]],
+ ABILITIES_SECTION = [[
+ Abilities:
+
+ ${abilities}
+
+ ]],
+
+ FORMATTEDRULES_SECTION = [[
+ Rules:
+
+ ${formattedrules}
+
+ ]],
+
+ UNASSIGNED_SECTION = [[
+ Unassigned Weapons:
+
+ ${unassigned}
+
+ ]],
+ --assignedSectionHeader = "", --[[ ]]
+ --assignedSectionFooter = "", --[[ ]]
+
+
+
+ UNASSIGNED_WEAPON = [[
+
+
+
+ ]],
+ ASSIGNED_WEAPON = [[
+
+
+
+
+
+ ]],
+ UNIT_CONTAINER = [[
+ ${unitName}
+
+ ${unitData}
+
+ ]],
+ MODEL_CONTAINER = [[
+ ${numberString}${modelName}
+ ${weapons}
+ ${abilities}
+ ]],
+ MODEL_DATA = [[
+
+ ${dataType}
+ ${data}
+ ]],
+
+ MODEL_GROUPING_CONTAINER = [[ ${modelGroups} ]]
+}
+
+--[[ UNIT SCRIPTING DATA ]]--
+--[[ everything in this section is meant to be a string because this is what we
+are inputting into created models ]]--
+
+local UNIT_SPECIFIC_DATA_TEMPLATE = [[ --[[ UNIT-SPECIFIC DATA ${endBracket}--
+local unitData = {
+ unitName = "${unitName}",
+ unitDecorativeName = "${unitDecorativeName}",
+ factionKeywords = "${factionKeywords}",
+ keywords = "${keywords}",
+ abilities = {
+ ${abilities}
+ },
+ formattedrules = {
+ ${formattedrules}
+ },
+ models = {
+ ${models}
+ }${changingCharacteristics}${woundTrack},
+ weapons = {
+ ${weapons}
+ }${psychic},
+ uuid = "${uuid}"${singleModel},
+ uiHeight = ${height},
+ uiWidth = ${width}
+} ]]
+local CHANGING_CHARACTERISTICS_TEMPLATE = [[,
+ changingCharacteristics = {
+ ${changingChars}
+ },
+]]
+local WEAPON_TEMPLATE = [[[c6c930]${name}[-]
+${rangeAndType} S:${s} AP:${ap} D:${d} ${ability} ]]
+local DEFAULT_BRACKET_VALUE_TEMPLATE = "[98ffa7]${val}[-]"
+local ABILITITY_STRING_TEMPLATE = '{ name = [[${name}]], desc = [[${desc}]] }'
+local WEAPON_ENTRY_TEMPLATE = '{ name="${name}", range=[[${range}]], type="${type}", s="${s}", ap="${ap}", d="${d}", abilities=[[${abilities}]] }'
+local CHANGING_CHARACTERISTICS_ENTRY_TEMPLATE = '["${name}"] = { "${characteristics}" }'
+local WOUND_TRACK_ENTRY_TEMPLATE = [[ ["${name}"] = {
+ ${tracks}
+ }]]
+local PSYKER_PROFILE_TEMPLATE = [[ ["${name}"] = ${profiles}]]
+local PSYCHIC_POWER_TEMPLATE = [[${name} (${warpCharge}, ${range})]]
+local PSYCHIC_POWER_ENTRY_TEMPLATE = '{ name="${name}", warpCharge="${warpCharge}", range=[[${range}]], details=[[${details}]] }'
+
+
+
+
+
+local YELLOW_STORAGE_GUID = "43ecc1"
+local ARMY_BOARD_GUID = "2955a6"
+local DELETION_ZONE_GUID = "f33dff"
+local AGENDA_MANAGER_GUID = "45cd3f"
+local IS_IN_HOME_MOD
+local yellowStorage,YELLOW_STORAGE_XML,YELLOW_STORAGE_SCRIPT,army,armyBoard,uiHeight,uiWidth
+local SLOT_POINTS = {slot={},boundingBox={},placed={},models={}}
+local SLOTS_TO_DISPLAY = {
+ "slot",
+ "boundingBox",
+ "placed",
+ "models"
+}
+local DEFAULT_MODEL_SPACING = 0.15
+local DEFAULT_FOOTPRINT_PADDING = 0.5
+local BOUNDING_BOX_RATIO = 2
+local MODEL_PLACEMENT_Y = 5.4
+local ARMY_PLACEMENT_STARTING_X = -5
+local ARMY_PLACEMENT_STARTING_Z = -7
+local WEAPON_TYPE_VALUES = {
+ ["rapid fire"] = 0,
+ ["assault"] = 0,
+ ["heavy"] = 0,
+ ["macro"] = 0,
+ ["pistol"] = 1,
+ ["grenade"] = 2,
+ ["melee"] = 3
+}
+local CREATE_ARMY_BUTTON = {
+ label="CREATE ARMY", click_function="createArmy", function_owner=self,
+ position={0.5,1.5,0}, rotation={180,0,180}, height=550, width=2750, font_size=220, font_style = "Bold",
+ font_color={1,1,1}, color={0,150/255,0}
+}
+local ON_BUTTON = {
+ label="LOAD ROSTER", click_function="turnOnYellowMachine", function_owner=self,
+ position={0,0.52,0}, rotation={180,0,180}, height=550, width=2750, font_size=220, font_style = "Bold",
+ font_color={1,1,1}, color={0,150/255,0}
+}
+local modelAssociations,activeButtons = {},{}
+local numAssociatedObjects,firstModelAssociation = 0,true
+local ERROR_RED = { 1, 0.25, 0.25 }
+
+
+
+
+
+
+
+
+
+
+
+function moveToLoadingScreen()
+ if armyText ~= nil and armyText ~= "" then
+ if #armyText == 8 then
+ loadedData = nil
+ UI.hide("welcomeWindow")
+ UI.show("loading")
+ Wait.time(|| sendRequest(armyText), 0.2)
+ else
+ broadcastToAll("It looks like your Yellowscribe code is malformed, please make sure to enter it correctly!", ERROR_RED)
+ return
+ end
+ else
+ broadcastToAll("Please paste your code into the box before clicking submit!", ERROR_RED)
+ return
+ end
+
+ Wait.time(function () -- delay so that animations dont blend
+ --UI.show("loading")
+
+ --local loadingAnimation = Wait.time(|| updateLoadingDots(), 0.4, -1)
+
+ Wait.condition(function ()
+ if loadedData.err == nil then
+
+ loadEditedArmy(loadedData)
+ --UI.hide("loading")
+
+ --loadArmyDisplay()
+
+ --Wait.time(function ()
+ --showWindow("postLoading")
+ --Wait.stop(loadingAnimation)
+ --end, 0.2)
+ else
+ UI.hide("loading")
+ -- wait because sometimes the response comes back before the loading screen even shows up
+ Wait.time(function ()
+ broadcastToAll(loadedData.err, ERROR_RED)
+ UI.show("welcomeWindow")
+ end, 0.2)
+
+ --Wait.stop(loadingAnimation)
+ end
+ end,
+ || loadedData ~= nil,
+ 20,
+ function ()
+ UI.hide("loading")
+ broadcastToAll("Something has gone horribly wrong! Please try again.", ERROR_RED)
+ UI.show("welcomeWindow")
+ --Wait.stop(loadingAnimation)
+ end)
+ end, 0.15)
+end
+
+
+
+function acceptEditedArmy()
+ UI.hide("mainPanel")
+ loadEditedArmy({ -- args sent as table because this used to be Global and I'm too lazy to rewrite it
+ data = loadedData,
+ order = originalLoadedOrder,
+ uiHeight = uiHeight,
+ uiWidth = uiWidth,
+ useDecorativeNames = useDecorativeNames
+ })
+end
+
+
+
+
+function updateArmyInputText(player, text)
+ armyText = text
+end
+
+
+
+function sendRequest(data)
+ -- Perform the request
+ log(url..data)
+ WebRequest.get(url..data, handleResponse)
+end
+
+
+function handleResponse(response)
+ -- Check if the request failed to complete e.g. if your Internet connection dropped out.
+ if response.is_error then
+ broadcastToAll("Something went wrong at the server!", ERROR_RED)
+ log(response.text)
+ return
+ end
+
+ local data = JSON.decode(response.text)
+
+ if data.err ~= nil then
+ loadedData = data
+ else
+ loadedData = {}
+ --[[ loadedDataOrder = data.order
+ originalLoadedOrder = clone(data.order) --]]
+ loadedData.uiHeight = data.uiHeight
+ loadedData.uiWidth = data.uiWidth
+ YELLOW_STORAGE_SCRIPT = data.baseScript
+ loadedData.armyData = data.armyData
+ loadedData.height = data.height
+ loadedData.xml = data.xml
+ loadedData.order = data.order
+ loadedData.useDecorativeNames = data.decorativeNames == "true"
+ end
+end
+
+
+
+function loadArmyDisplay()
+--[[ {
+ uuid: {
+ containerXML: "...",
+ entries: [
+ id: "...",
+ contentXML: "..."
+ ]
+ }...
+ }
+ ]]
+ local armyDisplayTable = {}
+
+ for _,uuid in ipairs(loadedDataOrder) do
+ armyDisplayTable[uuid] = formatUnitXml(loadedData[uuid])
+ end
+
+ --armyDisplay = armyDisplayTable
+ --local currentContainerValue = UI.getValue("loadedArmyContainer")
+
+ --if currentContainerValue == nil then currentContainerValue = ""
+
+ local containersString,modelContainersString = "",""
+
+ for _,uuid in ipairs(loadedDataOrder) do
+ containersString = containersString..armyDisplayTable[uuid].containerXML
+ end
+
+ loadUnitEditingXML(containersString)
+end
+
+
+
+
+
+
+
+function formatUnitXml(unit)
+ --local unitTitleInputXml = interpolate(uiTemplates.editUnitTitle, { uuid=unit.uuid, unitName=unit.name })
+ --local unitDataXml = ""
+ --local unitEntries = {}
+ local weaponSection,abilitySection,unassignedSection,modelContainersXML = "","","","",""
+ local maxHeight = 0
+ local weaponTitleHeight,abilityTitleHeight,unassignedTitleHeight,maxModelHeight,width,numModels = 0,0,0,0,0,0
+ local modelData, model
+ local sortedModelIDs = sortModels(unit.models.models, function (modelA, modelB)
+ return modelA.name < modelB.name or (modelA.name == modelB.name and modelA.number > modelB.number)
+ end)
+
+ for _,modelID in pairs(sortedModelIDs) do
+ model = unit.models.models[modelID]
+ modelData = getModelFormatData(unit.uuid, model, modelID, unit.unassignedWeapons, model.assignedWeapons) -- no assigned weapons yet
+
+ if modelData.height > maxHeight then
+ maxHeight = modelData.height
+ end
+
+ modelContainersXML = modelContainersXML..interpolate(uiTemplates.EDIT_MODEL_ENTRY, {
+ modelCount = model.number > 1 and (tostring(model.number).."x ") or "",
+ modelName = model.name,
+ weaponSection = modelData.weaponXML,
+ abilitySection = modelData.abilityXML,
+ unassignedSection = modelData.unassignedXML,
+ modelID = modelID
+ })
+
+ numModels = numModels + 1
+ width = width + 400
+ end
+
+ width = width - 15
+
+ maxModelHeight = maxHeight + 30--(maxLines*20) + 30 + weaponTitleHeight + abilityTitleHeight + unassignedTitleHeight
+--[[ {
+ uuid: {
+ containerXML: "...",
+ entries: [
+ id: "...",
+ contentXML: "..."
+ ]
+ }...
+ }
+ ]]
+ -- make room for scroll bars if we need them
+ local moddedMaxHeight = maxModelHeight + (numModels < 4 and 0 or 20)
+
+ return {
+ containerXML = interpolate(uiTemplates.EDIT_UNIT_CONTAINER, {
+ unitTitleInput = unitTitleInputXml,
+ --unitData = unitDataXml,
+ maxHeight = maxModelHeight,
+ noScrollBars = tostring(numModels < 4),
+ moddedMaxHeight = moddedMaxHeight,
+ fullHeight = moddedMaxHeight + 55,
+ uuid = unit.uuid,
+ unitName = unit.decorativeName ~= nil and unit.decorativeName or unit.name,
+ modelEntries = modelContainersXML,
+ width = width
+ })--,
+ --entries = unitEntries
+ }
+end
+
+
+
+function getModelFormatData(unitID, model, modelID, unassignedWeapons, assignedWeapons)
+ local height,weaponTitleHeight,abilityTitleHeight,unassignedTitleHeight = 0,0,0,0
+ local weaponSection,abilitySection,unassignedSection,assignedString = "","","",""
+
+ if assignedWeapons == nil then assignedWeapons = {} end
+ if #model.weapons > 0 or #assignedWeapons > 0 then
+
+ if #assignedWeapons > 0 then
+ --assignedString = uiTemplates.assignedSectionHeader
+
+ for _,weapon in pairs(assignedWeapons) do
+ assignedString = assignedString..interpolate(uiTemplates.ASSIGNED_WEAPON, {
+ weaponName = weapon.name,
+ weaponEscapedName = weapon.name:gsub(",", "$$$"):gsub("%(","***"):gsub("%)","+++"),
+ unitID = unitID,
+ modelID = modelID
+ })
+ end
+
+ --assignedString = assignedString..uiTemplates.assignedSectionFooter
+
+ height = height + (#assignedWeapons * 24)
+ end
+
+ weaponSection = interpolate(uiTemplates.WEAPON_SECTION, {
+ weapons = table.concat(map(model.weapons, |weapon| weapon.number == 1 and weapon.name or (weapon.number.."x "..weapon.name)), "\n"),
+ assignedWeapons = assignedString
+ })
+
+ height = height + (#model.weapons * 20) + 42 -- title and spacing
+ end
+
+ if #model.abilities > 0 then
+ abilitySection = interpolate(uiTemplates.ABILITIES_SECTION, { abilities=table.concat(model.abilities, "\n")})
+ height = height + (#model.abilities * 20) + 42 -- title and spacing
+ end
+
+ if #unassignedWeapons > 0 and #unassignedWeapons > #assignedWeapons then
+ local unassignedString = ""
+
+ for _,weapon in pairs(unassignedWeapons) do
+ --log(weapon.name)
+ --log(includes(assignedWeapons, weapon, "name"))
+ if not includes(assignedWeapons, weapon, "name") then
+ unassignedString = unassignedString..interpolate(uiTemplates.UNASSIGNED_WEAPON, {
+ weaponName = weapon.name,
+ weaponEscapedName = weapon.name:gsub(",", "$$$"):gsub("%(","***"):gsub("%)","+++"),
+ unitID = unitID,
+ modelID = modelID
+ })
+ end
+ end
+
+ unassignedSection = interpolate(uiTemplates.UNASSIGNED_SECTION, { unassigned=unassignedString })
+ height = height + (#unassignedWeapons * 24) + 42 -- title and spacing
+ end
+
+ return {
+ weaponXML = weaponSection,
+ abilityXML = abilitySection,
+ unassignedXML = unassignedSection,
+ height = height,
+ weaponTitleHeight = weaponTitleHeight,
+ abilityTitleHeight = abilityTitleHeight,
+ unassignedTitleHeight = unassignedTitleHeight
+ }
+end
+
+
+
+
+
+
+
+
+function updateUnitName(player, text, elementID)
+ loadedData[split(elementID, "-")[2]].decorativeName = text
+end
+
+
+
+function assignWeapon(player, data)
+ local weaponData = split(data, "|")
+ local weaponName,unitID,modelID,multiple = (weaponData[1]:gsub("%$%$%$", ","):gsub("%*%*%*", "("):gsub("%+%+%+", ")")),weaponData[2],weaponData[3],weaponData[4]
+ local model = loadedData[unitID].models.models[modelID]
+ local newModel
+ -- TODO: fix determining if is single model
+ -- (currently considers the last model in multi-model a single model)
+ -- I dont remember why its important that it knows its a single model or not
+ -- in any case, that is now availible under loadedData[unitID].isSingleModel
+ --log(weaponName)
+ local modelWithSameWeapons = findModelWithSameWeapons(unitID, model, modelID, weaponName, true)
+
+ if multiple == nil and model.number > 1 then
+ if modelWithSameWeapons ~= nil then
+ --newModel = modelWithSameWeapons
+ modelWithSameWeapons.number = modelWithSameWeapons.number + 1
+ else
+ newModel = clone(model)
+ newModel.number = 1
+ modelID = split(modelID, "-")[1].."-"..uuid()
+ if newModel["assignedWeapons"] == nil then
+ newModel["assignedWeapons"] = { { name=weaponName, number=1 } }
+ else
+ table.insert(newModel.assignedWeapons, { name=weaponName, number=1 })
+ end
+ end
+ model.number = model.number - 1
+ else
+ if multiple == nil then
+ if modelWithSameWeapons ~= nil then
+ --newModel = modelWithSameWeapons
+ modelWithSameWeapons.number = modelWithSameWeapons.number + 1
+ model.number = model.number - 1
+ else
+ if model["assignedWeapons"] == nil then
+ model["assignedWeapons"] = { { name=weaponName, number=1 } }
+ else
+ table.insert(model.assignedWeapons, { name=weaponName, number=1 })
+ end
+ end
+ elseif multiple == "all" then
+ if modelWithSameWeapons ~= nil then
+ --newModel = modelWithSameWeapons
+ modelWithSameWeapons.number = modelWithSameWeapons.number + model.number
+ model.number = 0
+ else
+ if model["assignedWeapons"] == nil then
+ model["assignedWeapons"] = { { name=weaponName, number=1 } }
+ else
+ table.insert(model.assignedWeapons, { name=weaponName, number=1 })
+ end
+ end
+ else -- unit
+ for _,editModel in pairs(loadedData[unitID].models.models) do
+ if editModel["assignedWeapons"] == nil then
+ editModel["assignedWeapons"] = { { name=weaponName, number=1 } }
+ else
+ if not includes(editModel.assignedWeapons, {name=weaponName}, "name") then
+ table.insert(editModel.assignedWeapons, { name=weaponName, number=1 })
+ end
+ end
+ end
+ end
+
+ if model.number <= 0 then removeModelByID(unitID, modelID) end
+ end
+
+ if newModel ~= nil then
+ loadedData[unitID].models.models[modelID] = newModel -- if a new model wasn't created this does nothing
+ end
+
+ -- always move most recently edited unit to the top of the window
+ moveUnitToTopOfWindow(unitID)
+ loadArmyDisplay()
+ showWindow("postLoading")
+ --refreshWindowAfterDelay("postLoading", 2, true)
+end
+
+
+
+function removeWeapon(player, data)
+ local weaponData = split(data, "|")
+ local weaponName,unitID,modelID,multiple = (weaponData[1]:gsub("%$%$%$", ","):gsub("%*%*%*", "("):gsub("%+%+%+", ")")),weaponData[2],weaponData[3],weaponData[4]
+ local model = loadedData[unitID].models.models[modelID]
+
+ -- remove the weapon
+ --model.assignedWeapons = filter(model.assignedWeapons, function (name) return name ~= weaponName end)
+
+ if multiple == nil and modelID:find("-", 1, true) then -- if is a multi-model group
+ local modelWithSameWeapons = findModelWithSameWeapons(unitID, model, modelID, weaponName, false)
+
+ if modelWithSameWeapons ~= nil then
+ modelWithSameWeapons.number = modelWithSameWeapons.number + 1
+ else
+ newModel = clone(model)
+ newModel.number = 1
+ modelID = split(modelID, "-")[1].."-"..uuid()
+
+ newModel.assignedWeapons = filter(model.assignedWeapons, |weapon| weapon.name ~= weaponName)
+ loadedData[unitID].models.models[modelID] = newModel
+ end
+
+ model.number = model.number - 1
+
+ if model.number <= 0 then removeModelByID(unitID, modelID) end
+ else
+ if multiple == "unit" then
+ for _,editModel in pairs(loadedData[unitID].models.models) do
+ editModel.assignedWeapons = filter(editModel.assignedWeapons, |weapon| weapon.name ~= weaponName)
+ end
+ else -- if the model is a single-model group or removing from the whole model group
+ -- remove the weapon from the mdoel group
+ model.assignedWeapons = filter(model.assignedWeapons, |weapon| weapon.name ~= weaponName)
+ end
+ end
+
+ -- always move most recently edited unit to the top of the window
+ moveUnitToTopOfWindow(unitID)
+ loadArmyDisplay()
+ showWindow("postLoading")
+ --refreshWindowAfterDelay("postLoading", 2, true)
+end
+
+function moveUnitToTopOfWindow(unitID)
+ for idx,uuid in ipairs(loadedDataOrder) do
+ if uuid == unitID then
+ table.remove(loadedDataOrder, idx)
+ table.insert(loadedDataOrder, 1, unitID)
+ break;
+ end
+ end
+end
+
+
+
+
+
+
+
+function updateLoadingDots()
+ local currentDots = UI.getValue("loadingDots")
+
+ if currentDots == nil or currentDots == "" then
+ UI.setValue("loadingDots", ".")
+ elseif #currentDots == 5 then
+ UI.setValue("loadingDots", "")
+ else
+ UI.setValue("loadingDots", currentDots.."..")
+ end
+end
+
+
+function closeWelcomeWindow()
+ UI.hide("mainPanel")
+end
+
+function turnOnYellowMachine()
+ showWindow("welcomeWindow")
+end
+
+
+
+
+
+
+
+
+--[[ EVENT HANDLERS ]]--
+
+
+function onLoad()
+ IS_IN_HOME_MOD = Global.getVar("isYMBS2TTS") ~= nil
+
+ yellowStorage = getObjectFromGUID(YELLOW_STORAGE_GUID)
+ YELLOW_STORAGE_XML = yellowStorage.getData().XmlUI
+ YELLOW_STORAGE_SCRIPT = yellowStorage.getLuaScript()
+
+ if not IS_IN_HOME_MOD then
+ getObjectFromGUID(AGENDA_MANAGER_GUID).destroy()
+ getObjectFromGUID(DELETION_ZONE_GUID).destroy()
+ yellowStorage.destroy()
+
+ self.setPosition({x=0, y=4, z=0})
+ self.createButton(ON_BUTTON)
+ self.setLock(false)
+
+ CREATE_ARMY_BUTTON.position = {0,0.6,0}
+ else
+ showWindow("welcomeWindow")
+ end
+end
+
+function onScriptingButtonDown(index, player_color)
+ --slotPoints = { {5,1,5}, {-5,1,-5} }
+ if DEBUG then
+ Global.setVectorLines(SLOT_POINTS[SLOTS_TO_DISPLAY[index]])
+ end
+end
+
+function onPlayerAction(player, action, targets)
+ if action == Player.Action.PickUp and #activeButtons > 0 then
+ makeSureObjectsAreAttached(targets)
+
+ local intendedTargets
+
+ if #player.getSelectedObjects() == 0 then
+ intendedTargets = { player.getHoverObject() }
+ else
+ intendedTargets = player.getSelectedObjects()
+
+ if not includes(intendedTargets, player.getHoverObject()) then
+ table.insert(intendedTargets, player.getHoverObject())
+ end
+ end
+
+ local targetsData = map(intendedTargets, function (target)
+ local data = target.getData()
+
+ data.States = nil
+
+ return data
+ end)
+
+ for _,activeButton in pairs(activeButtons) do
+ local buttonModel = army[activeButton.unit].models.models[activeButton.model]
+
+ buttonModel.associatedModels = targetsData
+
+ -- its ok if we overwrite this every time, we only ever need one and they shooould be all the same
+ buttonModel.associatedModelBounds = intendedTargets[1].getBoundsNormalized()
+
+ self.UI.setAttributes(activeButton.buttonID, {
+ color = "#33ff33"
+ })
+ end
+
+ for _,target in ipairs(intendedTargets) do
+ target.highlightOn({ r=51/255, g=1, b=51/255 }, 2)
+ end
+
+ activeButtons = {}
+ end
+end
+
+
+
+
+
+--[[ MODEL SELECTION ]]--
+
+
+function selectModelGroup(player,_, unitAndModelID)
+ local idValues = split(unitAndModelID, "|")
+ local unitID,modelID = idValues[1], idValues[2]
+ local sameButtonIndex = find(map(activeButtons, |button| button.buttonID), unitAndModelID)
+
+ if sameButtonIndex > 0 then
+ for _,modelData in ipairs(army[unitID].models.models[modelID].associatedModels) do
+ getObjectFromGUID(modelData.GUID).highlightOff()
+ end
+ army[unitID].models.models[modelID].associatedModels = nil
+ table.remove(activeButtons, sameButtonIndex)
+ self.UI.setAttribute(unitAndModelID, "color", "White")
+ else
+ table.insert(activeButtons, { unit = unitID, model = modelID, buttonID = unitAndModelID })
+ self.UI.setAttribute(unitAndModelID, "color", "#ff00ca")
+
+ if #activeButtons == 1 then -- if it's the first button selected
+ broadcastToAll("Pick up a model or models to represent your selection!", {r=1, g=0, b=202/255})
+ end
+ end
+end
+
+function showAssociatedModel(_,_, button)
+ highlightAssociatedModel(button, true)
+end
+
+function hideAssociatedModel(_,_, button)
+ highlightAssociatedModel(button, false)
+end
+
+function highlightAssociatedModel(unitAndModelID, on)
+ local idValues = split(unitAndModelID, "|")
+ local buttonModel = army[idValues[1]].models.models[idValues[2]]
+
+ if buttonModel.associatedModels ~= nil and #buttonModel.associatedModels > 0 then
+ for _,associatedModel in ipairs(buttonModel.associatedModels) do
+ local object = getObjectFromGUID(associatedModel.GUID)
+
+ if object ~= nil then
+ if on then
+ object.highlightOn({ r=51/255, g=1, b=51/255 })
+ else
+ object.highlightOff()
+ end
+ end
+ end
+ end
+end
+
+function makeSureObjectsAreAttached(objects)
+ for _,attachmentSet in ipairs(getObjectsToAttach(filter(objects, |object| #object.getJoints() > 0))) do
+ for _,jointedObj in pairs(attachmentSet.toAttach) do
+ if attachmentSet.lowestObj ~= jointedObj then
+ attachmentSet.lowestObj.addAttachment(jointedObj)
+ end
+ end
+ end
+end
+
+function getObjectsToAttach(objects)
+ local toAttach = {}
+
+ for _,object in ipairs(objects) do
+ local attachmentSet = getObjectsToAttachRecursive(object, {}, {
+ lowestY = object.getPosition().y,
+ lowestObj = object,
+ toAttach = { [object.getGUID()]=object }
+ })
+
+ for guid,_ in pairs(attachmentSet.toAttach) do
+ for _,set in ipairs(toAttach) do
+ if set.toAttach[guid] ~= nil then
+ mergeAttachmentSets(attachmentSet, set)
+ goto afterInsert
+ end
+ end
+ end
+
+ table.insert(toAttach, attachmentSet)
+ ::afterInsert::
+ end
+
+ return toAttach
+end
+
+function getObjectsToAttachRecursive(object, found, toAttachTable)
+ for _,joint in ipairs(object.getJoints()) do
+ if found[joint.joint_object_guid] == nil then
+ local jointedObj = getObjectFromGUID(joint.joint_object_guid)
+ local jointedObjY = jointedObj.getPosition().y
+
+ found[joint.joint_object_guid] = true
+ toAttachTable.toAttach[joint.joint_object_guid] = jointedObj
+
+ if jointedObjY < toAttachTable.lowestY then
+ toAttachTable.lowestY = jointedObjY
+ toAttachTable.lowestObj = jointedObj
+ end
+
+ getObjectsToAttachRecursive(jointedObj, found, toAttachTable)
+ end
+ end
+
+ return toAttachTable
+end
+
+function mergeAttachmentSets(setToMerge, mergeIntoSet)
+ for guid,obj in pairs(setToMerge.toAttach) do
+ if mergeIntoSet.toAttach[guid] == nil then
+ mergeIntoSet.toAttach[guid] = obj
+ end
+ end
+
+ if setToMerge.lowestY < mergeIntoSet.lowestY then
+ mergeIntoSet.lowestY = setToMerge.lowestY
+ mergeIntoSet.lowestObj = setToMerge.lowestObj
+ end
+end
+
+
+
+
+
+
+
+
+--[[ ARMY CREATION ]]--
+
+-- formats and creates the army based on selected models
+function createArmy()
+ -- we only want to create models for ones that have a model selected
+ local unitsToCreate = filter(army, function (unit)
+ unit.models.models = filter(unit.models.models, function (model)
+ if model.associatedModels == nil or #model.associatedModels == 0 then
+ -- make sure we are spawning thr right number of models if only part of a unit is beign spawned
+ unit.models.totalNumberOfModels = unit.models.totalNumberOfModels - model.number
+ end
+
+ return model.associatedModels ~= nil and #model.associatedModels > 0
+ end)
+
+ return len(unit.models.models) > 0
+ end)
+
+ if len(unitsToCreate) == 0 then
+ broadcastToAll("You haven't selected any models!", ERROR_RED)
+ return
+ end
+
+ -- delete anything that might get in the way in the future
+ deleteAllObjectsInCreationZone()
+
+ -- this feels so inefficient to go through the array so many times,
+ -- but at this point, the array really shouldn't be that long,
+ -- so I dont have to worry too much about big-O
+ unitsToCreate = table.sort(map(unitsToCreate, function (unit)
+ unit.models.models = table.sort(unit.models.models, |modelA, modelB| modelA.number < modelB.number)
+
+ --[[ for _,model in ipairs(unit.models.models) do
+ model.associatedModel = getObjectFromGUID(model.associatedModel)
+ end --]]
+
+ unit.footprint = determineFootprint(unit)
+
+ return unit
+ end), function (unitA, unitB)
+ if unitA.footprint.width == unitB.footprint.width then
+ return unitB.footprint.height < unitA.footprint.height
+ end
+
+ return unitA.footprint.width > unitB.footprint.width
+ end)
+
+ local selfPosition = self.getPosition()
+
+ -- at this point, we should have a list of units sorted by width then height of their footprints
+ placeArmy(unitsToCreate, ARMY_PLACEMENT_STARTING_X + selfPosition.x, ARMY_PLACEMENT_STARTING_Z + selfPosition.z, selfPosition.y)
+end
+
+
+function placeArmy(unitMap, startingX, startingZ, startingY)
+ local emptySlots = {} -- {{x,z,h,w},...}
+ local boundingBox = { h=0, w=0 }
+
+ for _,unit in pairs(unitMap) do
+ local placedInEmptySlot = false
+
+ -- try to place at an origin
+ for idx,slot in ipairs(emptySlots) do
+ if unit.footprint.height <= slot.h and unit.footprint.width <= slot.w then
+ placeUnit(unit, startingX-slot.x, startingZ+slot.z, startingY)
+
+ if DEBUG then
+ table.insert(SLOT_POINTS.placed, { points= {
+ {startingX-slot.x,MODEL_PLACEMENT_Y+1,startingZ+slot.z},
+ {startingX-slot.x-unit.footprint.width,MODEL_PLACEMENT_Y+1,startingZ+slot.z},
+ {startingX-slot.x-unit.footprint.width,MODEL_PLACEMENT_Y+1,startingZ+slot.z+unit.footprint.height},
+ {startingX-slot.x,MODEL_PLACEMENT_Y+1,startingZ+slot.z+unit.footprint.height},
+ {startingX-slot.x,MODEL_PLACEMENT_Y+1,startingZ+slot.z}
+ },
+ color = {0,0,0}})
+ end
+
+ table.remove(emptySlots, idx)
+
+ -- slot to the side should be filled first if possible
+ -- so insert the top one first
+ if (slot.h - unit.footprint.height) >= 1 then
+ if DEBUG then
+ table.insert(SLOT_POINTS.slot,{points= {
+ {startingX-slot.x,MODEL_PLACEMENT_Y+1,startingZ+slot.z+unit.footprint.height},
+ {startingX-slot.x-slot.w,MODEL_PLACEMENT_Y+1,startingZ+slot.z+unit.footprint.height},
+ {startingX-slot.x-slot.w,MODEL_PLACEMENT_Y+1,startingZ+slot.z+slot.h},
+ {startingX-slot.x,MODEL_PLACEMENT_Y+1,startingZ+slot.z+slot.h},
+ {startingX-slot.x,MODEL_PLACEMENT_Y+1,startingZ+slot.z+unit.footprint.height}
+ },
+ color = {0,1,0}})
+ end
+
+ table.insert(emptySlots, {
+ x = slot.x,
+ z = slot.z + unit.footprint.height,
+ h = slot.h - unit.footprint.height,
+ w = slot.w
+ })
+ end
+
+ if (slot.w - unit.footprint.width) >= 1 then
+ if DEBUG then
+ table.insert(SLOT_POINTS.slot,{ points = {
+ {startingX-slot.x-unit.footprint.width,MODEL_PLACEMENT_Y+1,startingZ+slot.z},
+ {startingX-boundingBox.w,MODEL_PLACEMENT_Y+1,startingZ+slot.z},
+ {startingX-boundingBox.w,MODEL_PLACEMENT_Y+1,startingZ+slot.z+unit.footprint.height},
+ {startingX-slot.x-unit.footprint.width,MODEL_PLACEMENT_Y+1,startingZ+slot.z+unit.footprint.height},
+ {startingX-slot.x-unit.footprint.width,MODEL_PLACEMENT_Y+1,startingZ+slot.z}
+ },
+ color = {0,0,1}})
+ end
+
+ table.insert(emptySlots, {
+ x = slot.x + unit.footprint.width,
+ z = slot.z,
+ w = slot.w - unit.footprint.width,
+ h = unit.footprint.height
+ })
+ end
+ -- >= 1 because we dont want to make additional tiny slots that will never be filled
+
+ placedInEmptySlot = true
+ break;
+ end
+ end
+
+ if placedInEmptySlot then -- do nothing
+
+ -- if expanding upward makes sense
+ elseif (boundingBox.h + unit.footprint.height) < (boundingBox.w * BOUNDING_BOX_RATIO) then
+ placeUnit(unit, startingX, startingZ + boundingBox.h, startingY)
+
+ if DEBUG then
+ table.insert(SLOT_POINTS.placed, { points= {
+ {startingX,MODEL_PLACEMENT_Y+1,startingZ+boundingBox.h},
+ {startingX-unit.footprint.width,MODEL_PLACEMENT_Y+1,startingZ+boundingBox.h},
+ {startingX-unit.footprint.width,MODEL_PLACEMENT_Y+1,startingZ+boundingBox.h+unit.footprint.height},
+ {startingX,MODEL_PLACEMENT_Y+1,startingZ+boundingBox.h+unit.footprint.height},
+ {startingX,MODEL_PLACEMENT_Y+1,startingZ+boundingBox.h}
+ },
+ color = {0,0,0}})
+ end
+
+ if (boundingBox.w - unit.footprint.width >= 1) then
+ if DEBUG then
+ table.insert(SLOT_POINTS.slot, { points= {
+ {startingX-unit.footprint.width,MODEL_PLACEMENT_Y+1,startingZ+boundingBox.h},
+ {startingX-boundingBox.w,MODEL_PLACEMENT_Y+1,startingZ+boundingBox.h},
+ {startingX-boundingBox.w,MODEL_PLACEMENT_Y+1,startingZ+boundingBox.h+unit.footprint.height},
+ {startingX-unit.footprint.width,MODEL_PLACEMENT_Y+1,startingZ+boundingBox.h+unit.footprint.height},
+ {startingX-unit.footprint.width,MODEL_PLACEMENT_Y+1,startingZ+boundingBox.h}
+ },
+ color = {1,0,1}})
+ end
+
+ table.insert(emptySlots, {
+ x = unit.footprint.width,
+ z = boundingBox.h,
+ h = unit.footprint.height,
+ w = boundingBox.w - unit.footprint.width
+ })
+ end
+
+ boundingBox.h = boundingBox.h + unit.footprint.height
+
+ -- else place at far left
+ else
+ placeUnit(unit, startingX - boundingBox.w, startingZ, startingY)
+
+ if DEBUG then
+ table.insert(SLOT_POINTS.placed, { points= {
+ {startingX-boundingBox.w,MODEL_PLACEMENT_Y+1,startingZ},
+ {startingX-boundingBox.w-unit.footprint.width,MODEL_PLACEMENT_Y+1,startingZ},
+ {startingX-boundingBox.w-unit.footprint.width,MODEL_PLACEMENT_Y+1,startingZ+unit.footprint.height},
+ {startingX-boundingBox.w,MODEL_PLACEMENT_Y+1,startingZ+unit.footprint.height},
+ {startingX-boundingBox.w,MODEL_PLACEMENT_Y+1,startingZ}
+ },
+ color = {0,0,0}})
+ end
+
+ if boundingBox.h - unit.footprint.height >= 1 then
+ if DEBUG then
+ table.insert(SLOT_POINTS.slot, { points= {
+ {startingX-boundingBox.w,MODEL_PLACEMENT_Y+1,startingZ+unit.footprint.height},
+ {startingX-boundingBox.w-unit.footprint.width,MODEL_PLACEMENT_Y+1,startingZ+unit.footprint.height},
+ {startingX-boundingBox.w-unit.footprint.width,MODEL_PLACEMENT_Y+1,startingZ+boundingBox.h},
+ {startingX-boundingBox.w,MODEL_PLACEMENT_Y+1,startingZ+boundingBox.h},
+ {startingX-boundingBox.w,MODEL_PLACEMENT_Y+1,startingZ+unit.footprint.height}
+ },
+ color = {0,0,0}})
+ end
+
+ table.insert(emptySlots, {
+ x = boundingBox.w,
+ z = unit.footprint.height,
+ h = boundingBox.h - unit.footprint.height,
+ w = unit.footprint.width
+ })
+ end
+
+ boundingBox.w = boundingBox.w + unit.footprint.width
+
+ if boundingBox.h == 0 then boundingBox.h = unit.footprint.height end -- handle first unit
+ end
+ end
+
+ if DEBUG then
+ SLOT_POINTS.boundingBox = {{
+ points= {
+ {startingX, MODEL_PLACEMENT_Y+1, startingZ},
+ {startingX-boundingBox.w, MODEL_PLACEMENT_Y+1, startingZ},
+ {startingX-boundingBox.w, MODEL_PLACEMENT_Y+1, startingZ+boundingBox.h},
+ {startingX, MODEL_PLACEMENT_Y+1, startingZ+boundingBox.h},
+ {startingX, MODEL_PLACEMENT_Y+1, startingZ},
+ },
+ color = {0,0,0}
+ }}
+ end
+
+ local boardPosition = { x=startingX-(boundingBox.w*0.5), y=5+startingY, z=startingZ+(boundingBox.h * 0.5) }
+ local boardScale = { x=(0.5*boundingBox.w), y=1, z=(0.5*boundingBox.h)}
+
+ if armyBoard == nil then
+ armyBoard = spawnObject({
+ type = "Custom_Tile",
+ sound = false,
+ position = boardPosition,
+ scale = boardScale
+ })
+ armyBoard.setCustomObject({
+ image = "http://cloud-3.steamusercontent.com/ugc/1698405413696745750/BC055E0445A3CEC1A0A0754CF4F1646977612B09/",
+ thickness = 0.37
+ })
+ armyBoard.setLock(true)
+ else
+ armyBoard.setScale(boardScale)
+ armyBoard.setPosition(boardPosition)
+ end
+end
+
+
+function placeUnit(unit, startX, startZ, startY)
+ -- cheap way of determining a "sergeant" model:
+ -- sort by number, pick the first, hope for the best
+ local isFirstModel = true
+ local xOffset = startX - DEFAULT_FOOTPRINT_PADDING -- left is negative
+ local zOffset = startZ + DEFAULT_FOOTPRINT_PADDING -- up is positive
+ local modelSize
+ local currentRowHeight,currentModelsInRow = 0,0
+ local leaderData = formatLeaderScript(unit)
+
+ for modelID,model in pairs(unit.models.models) do
+ --local currentModelObj = getObjectFromGUID(model.associatedModel)
+ -- I dont remember why I'm passing the data as an object instead of just as arguments
+ local modelProfile = getProfileForModel(model, unit)
+ local modelDescription = buildModelDescription(model, unit, modelProfile)
+ local modelNickname = (modelProfile ~= nil and ("[00ff16]"..modelProfile.w.."/"..modelProfile.w.."[-] ") or "")
+ ..getModelDisplayName(model, unit)
+ local modelTags = getModelTags(model, unit)
+
+ local modelData = formatModelData(model.associatedModels,
+ modelDescription,
+ modelNickname,
+ modelTags)
+
+ modelSize = model.associatedModelBounds.size
+ --log(model)
+
+ if currentRowHeight < modelSize.z then currentRowHeight = modelSize.z end
+
+ for i=1,model.number do
+ createModelFromData(chooseRandomModel(modelData),
+ --unit.decorativeName and unit.decorativeName or unit.name,
+ xOffset-(modelSize.x*0.5),
+ zOffset+(modelSize.z*0.5),
+ startY,
+ leaderData)
+ table.insert(SLOT_POINTS.models,{ points = {
+ {xOffset,MODEL_PLACEMENT_Y+1,zOffset},
+ {xOffset-modelSize.x,MODEL_PLACEMENT_Y+1,zOffset},
+ {xOffset-modelSize.x,MODEL_PLACEMENT_Y+1,zOffset+modelSize.z},
+ {xOffset,MODEL_PLACEMENT_Y+1,zOffset+modelSize.z},
+ {xOffset,MODEL_PLACEMENT_Y+1,zOffset}
+ },
+ color = {0,0,1}})
+ leaderData = nil
+ currentModelsInRow = currentModelsInRow + 1
+
+
+ if currentModelsInRow == unit.modelsPerRow then
+ currentModelsInRow = 0
+ xOffset = startX - DEFAULT_FOOTPRINT_PADDING
+ zOffset = zOffset + currentRowHeight + DEFAULT_MODEL_SPACING
+ else
+ xOffset = xOffset - (modelSize.x + DEFAULT_MODEL_SPACING)
+ end
+ end
+ end
+end
+
+-- determines how much space a unit should take up once it is created
+function determineFootprint(unit)
+ -- determine models per row
+ local modelsPerRow = unit.models.totalNumberOfModels
+ local currentModelsInRow,currentWidth,footprintWidth,footprintHeight,modelsLeft = 0,0,0,0,0
+ local currentRow = 1
+ local currentHeights = {}
+ local currentModelBounds
+
+ if modelsPerRow > 5 then
+ if modelsPerRow < 20 and modelsPerRow % 3 == 0 then
+ if modelsPerRow < 12 then modelsPerRow = 3
+ else modelsPerRow = modelsPerRow / 3 end
+ elseif modelsPerRow < 20 and modelsPerRow % 5 == 0 then
+ modelsPerRow = 5
+ elseif modelsPerRow > 10 then
+ modelsPerRow = 10
+ end
+ end
+
+ unit.modelsPerRow = modelsPerRow
+
+ -- I realize that this is doing almost exactly what we will do later when actually creating the models
+ -- unfortunately, this is the only way that I can think of to guarantee the footprint of a unit
+ -- with models of different sizes
+ for _,model in pairs(unit.models.models) do
+ currentModelBounds = model.associatedModelBounds.size
+
+ if currentHeights[currentRow] == nil or currentModelBounds.z > currentHeights[currentRow] then
+ currentHeights[currentRow] = currentModelBounds.z
+ end
+
+ if (currentModelsInRow + model.number) >= modelsPerRow then
+ currentWidth = currentWidth + ((modelsPerRow - currentModelsInRow) * (currentModelBounds.x + DEFAULT_MODEL_SPACING))
+
+ if currentWidth > footprintWidth then footprintWidth = currentWidth end
+
+ modelsLeft = model.number - (modelsPerRow - currentModelsInRow)
+ currentRow = currentRow + 1
+
+ while modelsLeft >= modelsPerRow do
+ table.insert(currentHeights, currentModelBounds.z + DEFAULT_MODEL_SPACING)
+ currentRow = currentRow + 1
+ modelsLeft = modelsLeft - modelsPerRow
+ currentWidth = (currentModelBounds.x + DEFAULT_MODEL_SPACING) * modelsPerRow
+ end
+
+ if modelsLeft > 0 then
+ table.insert(currentHeights, currentModelBounds.z + DEFAULT_MODEL_SPACING)
+ currentModelsInRow = modelsLeft
+ end
+
+ if currentWidth > footprintWidth then footprintWidth = currentWidth end
+
+ currentWidth = currentModelsInRow * (currentModelBounds.x + DEFAULT_MODEL_SPACING)
+ else
+ currentWidth = currentWidth + (model.number * (currentModelBounds.x + DEFAULT_MODEL_SPACING))
+ currentModelsInRow = currentModelsInRow + model.number
+ end
+ end
+
+ --if footprintHeight == 0 then footprintHeight = currentHeight end -- in case it hasnt been set yet (usually only because a row hasnt been filled)
+ for _,height in ipairs(currentHeights) do
+ footprintHeight = footprintHeight + height
+ end
+
+ return { width = footprintWidth+(2*DEFAULT_FOOTPRINT_PADDING), height = footprintHeight+(2*DEFAULT_FOOTPRINT_PADDING) }
+end
+
+-- formats both the leader and follower model data from a given model
+function formatModelData(associatedModels, description, nickname, tags)
+ for _,modelData in ipairs(associatedModels) do
+ modelData.Description = description
+ modelData.Nickname = nickname
+ modelData.Tags = tags
+ -- make sure base data doesnt include any xml or luascript
+ modelData.XmlUI = ""
+ modelData.LuaScript = ""
+ modelData.LuaScriptState = nil
+ end
+
+ return associatedModels
+end
+
+
+function formatLeaderScript(unit)
+ return interpolate(UNIT_SPECIFIC_DATA_TEMPLATE, {
+ unitName = unit.name,
+ unitDecorativeName = (unit.decorativeName ~= nil and unit.decorativeName ~= "") and unit.decorativeName:gsub('"', '\\"') or unit.name,
+
+ factionKeywords = table.concat(unit.factionKeywords, ", "), -- dont break xml --map(unit.factionKeywords, |keyword| (keyword:gsub(">", ">"):gsub("<", "<")))
+
+ keywords = table.concat(unit.keywords, ", "), -- dont break xml --map(unit.keywords, |keyword| (keyword:gsub(">", ">"):gsub("<", "<")))
+
+ abilities = getFormattedAbilities(unit.abilities, unit.rules),
+ formattedrules = getFormattedAbilities(unit.formattedrules,""),
+
+ models = table.concat(map(unit.modelProfiles, function (profile)
+ -- if the unit has brackets, treat each one as a separate model
+ if unit.woundTrack ~= nil and unit.woundTrack[profile.name] then
+ local toReturn = {}
+ local originalName = profile.name
+ local changing
+
+ for key,bracket in pairs(unit.woundTrack[profile.name]) do
+ profile.name = originalName.." ("..key..")"
+ changing = tableToFlatString(profile)
+
+ -- this seems like an inefficient way of doing it, but was the easiest to come up with
+ for _,val in ipairs(bracket) do
+ changing = changing:gsub("%*", val:gsub('"', '\\"'), 1)
+ end
+
+ table.insert(toReturn, changing)
+ end
+
+ return table.concat(toReturn, ",\n\t\t")
+ end
+
+ -- otherwise, just add the model
+ return tableToFlatString(profile)
+ end), ",\n\t\t"),
+
+ weapons = table.concat(map(unit.weapons, |weapon| interpolate(WEAPON_ENTRY_TEMPLATE, weapon)), ",\n\t\t"),
+
+ endBracket = "]]",
+ uuid = unit.uuid,
+ height = uiHeight,
+ width = uiWidth,
+
+ changingCharacteristics = unit.woundTrack == nil and "" or interpolate(CHANGING_CHARACTERISTICS_TEMPLATE, {
+ changingChars = formatChangingCharacteristics(unit)
+ }),
+
+ woundTrack = unit.woundTrack == nil and "" or "\twoundTrack = "..tableToString(map(unit.woundTrack, function (tracks, name)
+ return interpolate(WOUND_TRACK_ENTRY_TEMPLATE, {
+ tracks = table.concat(map(tracks, function (track, key)
+ local temp = '["'..key..'"] = { "'
+
+ temp = temp..table.concat(map(track, |val| (val:gsub('"', '\\"'))), '", "')
+
+ return temp..'" }'
+ end), ",\n\t\t\t", start_index, end_index ),
+ name = name
+ })
+
+ end), ",\n", true, "\t"),
+
+ singleModel = (not unit.isSingleModel) and "" or ",\n\tisSingleModel = true",
+
+ psychic = unit.psykerProfiles == nil and "" or ",\n\tpsykerProfiles = "..
+ tableToString(map(unit.psykerProfiles, |profile| tableToFlatString(profile)), ",\n\t\t", true, "\t", "\t\t")..
+ ",\n\tpowersKnown = "..
+ tableToString(map(unit.powersKnown, |power| interpolate(PSYCHIC_POWER_ENTRY_TEMPLATE, power)), ",\n\t\t", true, "\t", "\t\t")
+ })..YELLOW_STORAGE_SCRIPT
+end
+
+function formatChangingCharacteristics(unit)
+ local changing = {}
+
+ for _,profile in pairs(unit.modelProfiles) do
+ for char,val in pairs(profile) do -- profile
+ if val == "*" then
+ if changing[profile.name] == nil then changing[profile.name] = {} end
+
+ table.insert(changing[profile.name], char)
+ end
+ end
+ end
+
+ --[[ for name,_ in pairs(unit.woundTrack) do
+
+ changing[name] = {}
+
+ for char,val in pairs(unit.modelProfiles[name]) do -- profile
+ if val == "*" then table.insert(changing[name], char) end
+ end
+ end --]]
+
+ local toReturn = {}
+
+ for name,arr in pairs(changing) do
+ table.insert(toReturn, interpolate(CHANGING_CHARACTERISTICS_ENTRY_TEMPLATE, {
+ characteristics = table.concat(arr, '", "'),
+ name = name
+ }))
+ end
+
+ return table.concat(toReturn, ",\n\t\t")
+end
+
+function getModelDisplayName(model, unit)
+ if unit.isSingleModel or useDecorativeNames then
+ if unit.decorativeName ~= nil and unit.decorativeName ~= "" then
+ return unit.decorativeName
+ else
+ return model.name
+ end
+ end
+
+ return model.name
+end
+
+function getModelTags(model, unit)
+ local tags = { "uuid:"..unit.uuid }
+
+ if unit.woundTrack ~= nil then
+ for key,_ in pairs(unit.woundTrack) do
+ if key == model.name then table.insert(tags, "wt:"..model.name)
+ -- this is a special case for Armigers (i.e. units that have multiple of the same model that has a wound track)
+ -- where the data source creator named the profile in the plural ("Armigers")
+ elseif key == model.name.."s" then table.insert(tags, "wt:"..model.name.."s") end
+ end
+ end
+
+ return tags
+end
+
+-- Combine abilities and rules and format them properly to be displayed in a unit's datasheet
+function getFormattedAbilities(abilities, rules)
+ local abilitiesString = table.concat(map(abilities, function (ability)
+ ability.name = ability.name:gsub("%[", "("):gsub("%]", ")") -- try not to break formatting
+
+ return interpolate(ABILITITY_STRING_TEMPLATE, ability)
+ end), ",\n\t\t")
+
+ if #rules > 0 then
+ abilitiesString = abilitiesString..
+ (len(abilities) > 0 and ",\n\t\t" or "")..
+ interpolate(ABILITITY_STRING_TEMPLATE, {
+ name="Additional Rules\n(see the books)",
+ desc = table.concat(map(rules, |rule| (rule:gsub("%[", "("):gsub("%]", ")"))), ", ")-- try not to break formatting
+ })
+ end
+
+ return abilitiesString
+end
+
+-- chooses a random model from the given array
+-- technically this is a general method that could be used for selecting
+-- a random value from any array
+function chooseRandomModel(modelArray)
+ if #modelArray == 1 then return modelArray[1] end
+ if modelArray == nil or #modelArray == 0 then return nil end
+
+ return modelArray[math.random(1, #modelArray)] -- both inclusive
+end
+
+-- spawns a model from the given data set
+function createModelFromData(modelData, x, z, y, leaderModelScript)
+ if leaderModelScript ~= nil then
+ modelData = clone(modelData) -- prevent weird things with tables being treated as references
+ table.insert(modelData.Tags, "leaderModel")
+ modelData.XmlUI = YELLOW_STORAGE_XML
+ modelData.LuaScript = leaderModelScript
+ end
+
+ local spawnData = {
+ data = modelData,
+ position = {
+ x = x,
+ y = MODEL_PLACEMENT_Y+y,
+ z = z
+ },
+ rotation = { x=0, y=180, z=0 }, -- this seems right for most (but not all models)
+ }
+
+ spawnObjectData(spawnData)
+end
+
+-- finds the appropriate characteristic profile for the given model in the given unit
+function getProfileForModel(model, unit)
+ for _,profile in pairs(unit.modelProfiles) do
+ if profile.name == model.name then
+ return profile
+ end
+ end
+ -- if there arent any exactly matching profiles, try a more fuzzy search
+ for _,profile in pairs(unit.modelProfiles) do
+ local found = profile.name:find(model.name, 1, true) -- search for plain text (ie not pattern)
+
+ if found ~= nil then return profile end
+ end
+ -- if there arent any matching profiles, assume theres only one profile for every model in the unit
+ for _,profile in pairs(unit.modelProfiles) do return profile end
+
+ -- returns nil if not found
+end
+
+-- gets a model's description
+function buildModelDescription(model, unit, modelProfile)
+ return formatCharDesc(modelProfile, unit)..
+ formatWeaponDesc(model, unit, modelProfile ~= nil)..
+ formatAbilityDesc(model, unit, modelProfile ~= nil)..
+ formatPsychicDesc(model, unit, modelProfile ~= nil)
+end
+
+-- formats the characteristics section in a model's description
+function formatCharDesc(modelProfile, unit)
+ if modelProfile == nil then return "" end -- handles the rare case where a model just doesnt have a profile (eg Mekboy Workshop)
+
+ local charHeadingString,charValueString = "[56f442]",""
+ local currentChar = 1
+ local woundTrack
+
+ if unit.woundTrack ~= nil then
+ if unit.woundTrack[modelProfile.name] ~= nil then
+ woundTrack = map(unit.woundTrack[modelProfile.name], |v| v) -- make it array-like
+ elseif len(unit.woundTrack) == 1 then
+ for _,wt in pairs(unit.woundTrack) do
+ woundTrack = map(wt, |v| v)
+ end
+ end
+ end
+
+ for heading,value in pairs(modelProfile) do
+ if heading ~= "name" then
+ if value == "*" and woundTrack ~= nil then
+ value = woundTrack[1][currentChar]
+ charValueString = charValueString..interpolate(DEFAULT_BRACKET_VALUE_TEMPLATE, { val=value }).." "
+ currentChar = currentChar + 1
+ else
+ charValueString = charValueString..(value == "-" and " "..value or value).." "
+ end
+
+ charHeadingString = charHeadingString..formatHeading(heading, value)
+ end
+ end
+
+ charHeadingString = charHeadingString.."[-]\n"
+
+ return charHeadingString..charValueString.."[-][-]" -- the double brackets at the end helps us to update brakcets if the unit has them
+end
+
+-- formats the heading line for the characteristics section in a model's description
+-- the spacing is based on the values given so that they line up properly
+function formatHeading(heading, value)
+ local spacing = value:gsub("\\",""):len()-heading:len()
+
+ if heading == "ws" or heading == "m" or heading =="a" then
+ spacing = spacing + 2
+ else
+ spacing = spacing + 3
+ end
+
+ if (heading == "m" and value:len() > 2) or ((heading == "a" or heading == "s" or heading == "t" or heading == "w") and value:len() > 1) then
+ if heading == "m" and value ~= "-" and value:find('%-') ~= nil then
+ heading = heading.." "
+ end
+
+ heading = " "..heading
+ end
+
+ return capitalize(heading)..string.rep(" ", spacing)
+end
+
+-- decides whether to fully capitalize or (in the case of ld and sv) titlecase a string
+function capitalize(heading)
+ if heading == "ld" or heading == "sv" then return titlecase(heading) end
+ return heading:upper()
+end
+
+-- only use this for changing ld and sv to Ld and Sv
+function titlecase(s)
+ return s:gsub("^(%w)", |a| a:upper())
+end
+
+-- formats the string for the weapons section in a model's description
+function formatWeaponDesc(model, unit, needSpacingBefore)
+ if #model.weapons == 0 then return "" end
+
+ local weapons = (needSpacingBefore and "\n\n" or "").."[e85545]Weapons[-]"
+
+ for _,weapon in pairs(model.weapons) do
+ weapons = weapons.."\n"..formatWeapon(unit.weapons[weapon.name], weapon.number)
+ end
+
+ return weapons
+end
+
+-- formats the string for a weapon entry in a model's description
+function formatWeapon(weaponProfile, number)
+ return interpolate(WEAPON_TEMPLATE, {
+ name = number == 1 and weaponProfile.name or (number.."x "..weaponProfile.name),
+ rangeAndType = (weaponProfile.range == "Melee") and "Melee" or weaponProfile.range.." "..weaponProfile.type,
+ s = weaponProfile.s,
+ ap = weaponProfile.ap,
+ d = weaponProfile.d,
+ ability = weaponProfile.abilities == "-" and "" or "Sp:*"
+ })
+end
+
+-- formats the string for the abilities section in a model's description
+function formatAbilityDesc(model, unit, needSpacingBefore)
+ if #model.abilities == 0 then return "" end
+
+ return ((needSpacingBefore or #model.weapons > 0) and "\n\n" or "").."[dc61ed]Abilities[-]\n"..table.concat(model.abilities, "\n")
+end
+
+
+function formatPsychicDesc(model, unit)
+ if unit.powersKnown == nil or #unit.powersKnown == 0 then return "" end
+
+ return ((needSpacingBefore or #model.weapons > 0 or #model.abilities > 0) and "\n\n" or "")..
+ "[5785fe]Psychic Powers[-]\n"..table.concat(map(unit.powersKnown, |power| interpolate(PSYCHIC_POWER_TEMPLATE, {
+ name = power.name,
+ warpCharge = power.warpCharge,
+ range = power.range
+ })), "\n")
+end
+
+
+function deleteAllObjectsInCreationZone()
+ local deletionZone = getObjectFromGUID(DELETION_ZONE_GUID)
+
+ if deletionZone == nil then return end
+
+ for _,object in ipairs(deletionZone.getObjects()) do
+ if object ~= armyBoard and object.getGUID() ~= YELLOW_STORAGE_GUID then
+ object.setLuaScript("") -- prevent unintended consequences of destruction
+ object.destruct() -- at this point the object is a different object because we reloaded it
+ end
+ end
+end
+
+
+
+
+
+
+
+
+--[[ INITIALIZATION HELPER FUNCTIONS ]]--
+
+
+function loadUnitEditingXML(xml)
+ self.UI.setValue("loadedArmyContainer", xml)
+end
+
+function showWindow(name)
+ -- delay in case of update
+ Wait.frames(function ()
+ UI.setXml(self.UI.getXml())
+
+ Wait.frames(function ()
+ UI.setAttribute("mainPanel", "active", true)
+ UI.show(name)
+ end, 2)
+ end, 2)
+end
+
+
+
+
+
+
+--[[ LOADING FROM GLOBAL UI ]]--
+
+
+function loadEditedArmy(data)
+ self.clearButtons()
+
+ army = data.armyData
+ uiHeight = data.uiHeight
+ uiWidth = data.uiWidth
+ useDecorativeNames = data.useDecorativeNames
+ --YELLOW_STORAGE_SCRIPT = armyData.baseScript -- yes I know I'm assigning a new value to something I marked as a constant, sue me
+
+ local formattedArmyData = getLoadedArmyXML(data.order)
+
+ if formattedArmyData.totalHeight < 3000 then
+ self.UI.setAttributes("loadedScrollContainer", {
+ noScrollbars = true,
+ width = 2030
+ })
+ else
+ self.UI.setAttributes("loadedScrollContainer", {
+ noScrollbars = false,
+ width = 2050
+ })
+ end
+
+ self.UI.setAttribute("loadedContainer", "height", formattedArmyData.totalHeight)--formattedArmyData.totalHeight
+ self.UI.setValue("loadedContainer", formattedArmyData.xml)
+ self.UI.setAttribute("postLoading", "active", "false")
+ self.UI.hide("postLoading")
+ self.UI.setClass("mainPanel", "hiddenBigWindow")
+
+ self.createButton(CREATE_ARMY_BUTTON)
+
+ Wait.frames(function ()
+ UI.hide("mainPanel")
+ self.UI.setAttribute("loadedScrollContainer", "active", "true")
+ self.UI.setXml(self.UI.getXml())
+ end, 2)
+end
+
+
+function getLoadedArmyXML(order)
+ local xmlString = ""
+ local modelInUnitCount,modelDataForXML,currentUnitContainerHeight,totalUnitContainerHeight
+ local maxModelHeight,totalHeight = 0,0
+
+ for _,uuid in ipairs(order) do
+ local unit = army[uuid]
+ local modelGroupString,unitDataString = "",""
+
+ modelInUnitCount = 0
+ currentUnitContainerHeight = 0
+ totalUnitContainerHeight = 50 -- name
+
+ for modelID,model in pairs(unit.models.models) do
+ modelInUnitCount = modelInUnitCount + 1
+ modelDataForXML = getModelDataForXML(uuid, modelID, model, unit.weapons)
+ modelGroupString = modelGroupString..interpolate(uiTemplates.MODEL_CONTAINER, modelDataForXML)
+
+ if modelDataForXML.height > maxModelHeight then
+ maxModelHeight = modelDataForXML.height
+ currentUnitContainerHeight = modelDataForXML.height
+ end
+
+ if modelInUnitCount % 4 == 0 then
+ unitDataString = unitDataString..interpolate(uiTemplates.MODEL_GROUPING_CONTAINER, {
+ modelGroups = modelGroupString,
+ width = "1000",
+ height = maxModelHeight
+ })
+
+ modelInUnitCount = 0
+ maxModelHeight = 0
+ modelGroupString = ""
+ totalUnitContainerHeight = totalUnitContainerHeight + currentUnitContainerHeight + 20 -- spacing
+ end
+ end
+
+ if modelInUnitCount ~= 0 then
+ unitDataString = unitDataString..interpolate(uiTemplates.MODEL_GROUPING_CONTAINER, {
+ modelGroups = modelGroupString,
+ width = tostring(250 * modelInUnitCount),
+ height = maxModelHeight
+ })
+ maxModelHeight = 0
+ totalUnitContainerHeight = totalUnitContainerHeight + currentUnitContainerHeight
+ end
+
+ totalHeight = totalHeight + totalUnitContainerHeight + 100 -- spacing
+
+ xmlString = xmlString..interpolate(uiTemplates.UNIT_CONTAINER, {
+ unitName = unit.decorativeName and unit.decorativeName or unit.name,
+ unitData = unitDataString,
+ height = totalUnitContainerHeight
+ })
+ end
+
+ return { xml = xmlString, totalHeight = totalHeight }
+end
+
+
+function getModelDataForXML(unitID, modelID, model, characteristicProfiles)
+ local weaponSection,abilitiesSection = "",""
+ local totalCardHeight = 40 -- name
+
+ model.weapons = table.sort(model.weapons, function (weaponA,weaponB) --combine(model.weapons, model.assignedWeapons)
+ local typeA = trim(characteristicProfiles[weaponA.name].type):gsub("%s+%d?D?d?%/?%d+$", ""):lower()
+ local typeB = trim(characteristicProfiles[weaponB.name].type):gsub("%s+%d?D?d?%/?%d+$", ""):lower()
+ local typeAVal = WEAPON_TYPE_VALUES[typeA] == nil and 0 or WEAPON_TYPE_VALUES[typeA]
+ local typeBVal = WEAPON_TYPE_VALUES[typeB] == nil and 0 or WEAPON_TYPE_VALUES[typeB]
+
+ if typeAVal == typeBVal then return weaponA.name < weaponB.name end
+ return typeAVal < typeBVal
+ end)
+
+ if model.weapons ~= nil and #model.weapons > 0 then
+ weaponSection = interpolate(uiTemplates.MODEL_DATA, {
+ dataType = "Weapons:",
+ data = table.concat(map(model.weapons,
+ |weapon| weapon.number == 1 and weapon.name or (weapon.number.."x "..weapon.name))
+ ,"\n"),
+ height = 37 * #model.weapons
+ })
+ totalCardHeight = totalCardHeight + (37 * #model.weapons) + (#model.abilities > 0 and 55 or 60) -- title and spacer
+ end
+
+ if #model.abilities > 0 then
+ abilitiesSection = interpolate(uiTemplates.MODEL_DATA, {
+ dataType = "Abilities:",
+ data = table.concat(model.abilities, "\n"),
+ height = 37 * #model.abilities
+ })
+ totalCardHeight = totalCardHeight + (37 * #model.abilities) + 60 -- title and spacer
+ end
+
+ return {
+ modelName = model.name,
+ numberString = model.number > 1 and (tostring(model.number).."x ") or "",
+ weapons = weaponSection,
+ abilities = abilitiesSection,
+ unitID = unitID,
+ modelID = modelID,
+ height = totalCardHeight
+ }
+end
+
+
+
+
+
+
+
+
+
+
+
+--[[ UTILITY FUNCTIONS ]]--
+
+
+function interpolate(templateString, replacementValues)
+ return (templateString:gsub('($%b{})', |w| replacementValues[w:sub(3, -2)] or w)) -- extra parenthesis to prevent double return from gsub
+end
+
+function combine(tab1, tab2)
+ if tab1 == nil then return clone(tab2) end
+ if tab2 == nil then return clone(tab1) end
+
+ local newTab = clone(tab1)
+
+ for _,val in pairs(clone(tab2)) do
+ table.insert(newTab, val)
+ end
+
+ return newTab
+end
+
+function clone(orig)
+ local orig_type = type(orig)
+ local copy
+ if orig_type == 'table' then
+ copy = {}
+ for orig_key, orig_value in next, orig, nil do
+ copy[clone(orig_key)] = clone(orig_value)
+ end
+ setmetatable(copy, clone(getmetatable(orig)))
+ else -- number, string, boolean, etc
+ copy = orig
+ end
+ return copy
+end
+
+function split(s, delimiter)
+ local result = {};
+ for match in (s..delimiter):gmatch("(.-)%"..delimiter) do
+ table.insert(result, match);
+ end
+ return result;
+end
+
+function filter(t, filterFunc)
+ local out = {}
+
+ for k, v in pairs(clone(t)) do
+ if filterFunc(v, k, t) then table.insert(out,v) end
+ end
+
+ return out
+end
+
+function includes (tab, val, checkKey)
+ for index, value in ipairs(tab) do
+ if checkKey ~= nil then
+ if value[checkKey] == val[checkKey] then
+ return true
+ end
+ else
+ if value == val then
+ return true
+ end
+ end
+ end
+
+ return false
+end
+
+function find(tab, val)
+ for index, value in ipairs(tab) do
+ if value == val then
+ return index
+ end
+ end
+
+ return -1
+end
+
+function filterKeepKeys(t, filterFunc)
+ local out = {}
+
+ for k, v in pairs(clone(t)) do
+ if filterFunc(v, k, t) then out[k] = v end
+ end
+
+ return out
+end
+
+function map(t, mapFunc)
+ local out = {}
+
+ for k,v in pairs(clone(t)) do
+ table.insert(out, mapFunc(v,k))
+ end
+
+ return out
+end
+
+function len(t)
+ local count = 0
+
+ for _,_ in pairs(t) do
+ count = count + 1
+ end
+
+ return count
+end
+
+function trim(s)
+ return s:match'^()%s*$' and '' or s:match'^%s*(.*%S)'
+end
+
+-- this should only ever be used with one dimensional tables
+function tableToFlatString(t)
+ return tableToString(t, ", ")
+end
+
+-- this is not a particularly robust solution, it is only really for my purposes in this script
+-- thus, I very much do not recommend anyone copy this
+-- note to self: can make it recursive to traverse multi-dimensional tables but eh
+-- warnings:
+-- this assumes a table is array-like if the key "1" exists,
+-- this assumes all values are strings
+function tableToString(t, delimiter, bracketsOnNewLine, extraTabbing, tabBeforeFirstElement)
+ local out = "{ "
+ local arrayLike = t[1] ~= nil
+
+ if bracketsOnNewLine ~= nil and bracketsOnNewLine then
+ out = out.."\n"..(tabBeforeFirstElement ~= nil and tabBeforeFirstElement or "")
+ end
+
+ out = out..table.concat(map(t, function (v,k)
+ if arrayLike then return v end
+ return k..'="'..v:gsub('"', '\\"')..'"'
+ end), delimiter)
+
+ if bracketsOnNewLine ~= nil and bracketsOnNewLine then
+ return out.."\n"..(extraTabbing ~= nil and extraTabbing or "").."}"
+ end
+
+ return out.." }"
+end
+
+function removeModelByID(unitID, modelID)
+ loadedData[unitID].models.models[modelID] = nil
+end
+
+function findModelWithSameWeapons(unitID, model, ignoreKey, weaponName, adding)
+ return filter(loadedData[unitID].models.models, function (checkedModel, key)
+ -- solves a few problems
+ if checkedModel.assignedWeapons == nil then checkedModel.assignedWeapons = {} end
+ if model.assignedWeapons == nil then model.assignedWeapons = {} end
+ if ignoreKey ~= nil and ignoreKey == key then return false end
+
+ if checkedModel == model or
+ checkedModel.name ~= model.name or
+ #checkedModel.weapons ~= #model.weapons or
+ #checkedModel.abilities ~= #model.abilities or
+ #checkedModel.assignedWeapons ~= (#model.assignedWeapons + (adding and 1 or -1)) then
+ return false
+ end
+
+ for _,cWeapon in pairs(checkedModel.weapons) do
+ if not includes(model.weapons, cWeapon, "name") then return false end
+ end
+
+ for _,cAbility in pairs(checkedModel.abilities) do
+ if not includes(model.abilities, cAbility) then return false end
+ end
+
+ for _,cAWeapon in pairs(model.assignedWeapons) do
+ if cAWeapon.name ~= weaponName and
+ not includes(checkedModel.assignedWeapons, cAWeapon, "name") then return false end
+ end
+
+ if adding and not includes(checkedModel.assignedWeapons, {name=weaponName}, "name") then return false end
+
+ modelID = key
+ return true
+ end)[1]
+end
+
+function sortModels(tbl, sortFunction)
+ local keys = {}
+
+ for key in pairs(tbl) do
+ table.insert(keys, key)
+ end
+
+ table.sort(keys, function(a, b)
+ return sortFunction(tbl[a], tbl[b])
+ end)
+
+ return keys
+end
+
+function uuid()
+ local template ='xxxxxxxx'
+ return string.gsub(template, '[xy]', function (c)
+ local v = (c == 'x') and math.random(0, 0xf) or math.random(8, 0xb)
+ return string.format('%x', v)
+ end)
+end
+
+function removeModelByID(unitID, modelID)
+ loadedData[unitID].models.models[modelID] = nil
+end
+
+function findModelWithSameWeapons(unitID, model, ignoreKey, weaponName, adding)
+ return filter(loadedData[unitID].models.models, function (checkedModel, key)
+ -- solves a few problems
+ if checkedModel.assignedWeapons == nil then checkedModel.assignedWeapons = {} end
+ if model.assignedWeapons == nil then model.assignedWeapons = {} end
+ if ignoreKey ~= nil and ignoreKey == key then return false end
+
+ if checkedModel == model or
+ checkedModel.name ~= model.name or
+ #checkedModel.weapons ~= #model.weapons or
+ #checkedModel.abilities ~= #model.abilities or
+ #checkedModel.assignedWeapons ~= (#model.assignedWeapons + (adding and 1 or -1)) then
+ return false
+ end
+
+ for _,cWeapon in pairs(checkedModel.weapons) do
+ if not includes(model.weapons, cWeapon, "name") then return false end
+ end
+
+ for _,cAbility in pairs(checkedModel.abilities) do
+ if not includes(model.abilities, cAbility) then return false end
+ end
+
+ for _,cAWeapon in pairs(model.assignedWeapons) do
+ if cAWeapon.name ~= weaponName and
+ not includes(checkedModel.assignedWeapons, cAWeapon, "name") then return false end
+ end
+
+ if adding and not includes(checkedModel.assignedWeapons, {name=weaponName}, "name") then return false end
+
+ modelID = key
+ return true
+ end)[1]
+end
+
+function sortModels(tbl, sortFunction)
+ local keys = {}
+
+ for key in pairs(tbl) do
+ table.insert(keys, key)
+ end
+
+ table.sort(keys, function(a, b)
+ return sortFunction(tbl[a], tbl[b])
+ end)
+
+ return keys
+end
\ No newline at end of file
diff --git a/These files are for the local mod/Yellow Storage.43ecc1.xml b/These files are for the local mod/Yellow Storage.43ecc1.xml
new file mode 100644
index 0000000..451ba6e
--- /dev/null
+++ b/These files are for the local mod/Yellow Storage.43ecc1.xml
@@ -0,0 +1,273 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ | UNIT NAME: |
+ |
+
+
+ | BATTLEFIELD ROLE: |
+ |
+
+
+ | CRUSADE FACTION: |
+ |
+
+
+ | SELECTABLE KEYWORDS: |
+ |
+
+
+
+
+
+ | POWER
LEVEL |
+ EXPERIENCE
POINTS |
+ CRUSADE
POINTS |
+
+
+ |
+
+
+ 0
+
+
+ |
+
+
+
+ 0
+
+
+ |
+
+
+
+ 0
+
+
+ |
+
+
+
+
+
+
+ | UNIT TYPE: |
+ |
+
+
+ | EQUIPMENT: |
+ |
+
+
+ | PSYCHIC POWERS: |
+
+ |
+
+
+ | WARLORD TRAIT: |
+ |
+
+
+ | RELIC: |
+ |
+
+
+
+
+ | OTHER UPGRADES AND SELECTABLE ABILITIES |
+
+
+ |
+
+
+
+
+
+ | COMBAT TALLIES |
+
+
+ |
+
+ TOTAL UNITS KILLED:
+
+
+ 0
+
+
+
+ |
+
+
+ BATTLES PLAYED:
+
+
+ 0
+
+
+
+ |
+
+
+ BATTLES SURVIVED:
+
+
+ 0
+
+
+
+ |
+
+
+
+
+ | RANK |
+
+
+ | BLOODED |
+ |
+ BATTLE-HARDENED |
+ |
+ HEROIC |
+ |
+ LEGENDARY |
+ |
+
+
+ | BATTLE HONORS: |
+ |
+
+
+ | BATTLE SCARS: |
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Faction:
+
+
+ Keywords:
+
+
+
+
+
+
+ | Name |
+ WC |
+ Range |
+ Description |
+
+
+
+
+
+
+
+
+ | Name |
+ Cast |
+ Deny |
+ Can Know |
+
+
+
+
+
+
+
+
+ | Name |
+ M |
+ WS |
+ BS |
+ S |
+ T |
+ W |
+ A |
+ Ld |
+ Sv |
+
+
+
+
+
+
+
+
+ | Name |
+ Range |
+ Type |
+ S |
+ AP |
+ D |
+ Abilities |
+
+
+
+
+
+
+
+
+ | Abilities |
+ Description |
+
+
+
+
+
+
+
+
+ | Rules |
+ Description |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app.js b/app.js
index c4559c0..ed2e24c 100644
--- a/app.js
+++ b/app.js
@@ -111,7 +111,7 @@ const file = new statik.Server('./site'),
pos += data[i].length;
} ;
- if (postURL.pathname === "/format_and_store_army" || postURL.pathname === "/getFormattedArmy") {
+ if (postURL.pathname === "/getFormattedArmy") {
try {
let zip = new AdmZip(buf),
zipEntries = zip.getEntries();
@@ -121,31 +121,6 @@ const file = new statik.Server('./site'),
fs.writeFile("output.json", JSON.stringify(result.roster.forces, null, 4), () =>{})
//parseRos(result.roster.forces);
let armyDataObj = Roster.parse(result.roster.forces);
-
- if (postURL.pathname === "/format_and_store_army") {
- armyDataObj.uiHeight = postURL.searchParams.get('uiHeight');
- armyDataObj.uiWidth = postURL.searchParams.get('uiWidth');
- armyDataObj.baseScript = buildScript(postURL.searchParams.get("modules").split(","));
-
- fs.writeFile(`${PATH_PREFIX}${uuid}.json`,
- JSON.stringify(armyDataObj, replacer)
- .replace(" & ", " and "),
- (err) => {
- let content,status;
-
- if (!err) {
- content = `{ "id": "${uuid}" }`;
- status = 200;
- }
- else {
- content = `{ "err": "${ERRORS.fileWrite}" }`;
- status = 500
- }
-
- sendHTTPResponse(res, content, status);
- });
- }
- else
sendHTTPResponse(res, JSON.stringify(armyDataObj, replacer), 200);
});
}
@@ -175,32 +150,7 @@ const file = new statik.Server('./site'),
postURL.searchParams.get('decorativeNames'),
buildScript(postURL.searchParams.get("modules").split(",")));
- /* if (postURL.pathname === "/format_and_store_army") {
- armyDataObj.uiHeight = postURL.searchParams.get('uiHeight');
- armyDataObj.uiWidth = postURL.searchParams.get('uiWidth');
- armyDataObj.baseScript = buildScript(postURL.searchParams.get("modules").split(","))
- fs.writeFile(`${PATH_PREFIX}${uuid}.json`,
- JSON.stringify(armyDataObj, replacer)
- .replace(" & ", " and "),
- (err) => {
- let content,status;
-
- if (!err) {
- content = `{ "id": "${uuid}" }`;
- status = 200;
- }
- else {
- content = `{ "err": "${ERRORS.fileWrite}" }`;
- status = 500
- }
-
- sendHTTPResponse(res, content, status);
- });
- }
-
- else
- sendHTTPResponse(res, JSON.stringify(armyDataObj, replacer).replace(" & ", " and "), 200); */
}
catch (err) {
sendHTTPResponse(res, `{ "err": "${ERRORS.unknown}" }`, 500);
@@ -282,8 +232,7 @@ setInterval(() => {
}
});
- //let stats = fs.statSync("/dir/file.txt");
- //let mtime = stats.mtime;
+
}, ONE_MINUTE);
@@ -368,12 +317,6 @@ function formatAndStoreXML(id, order, armyData, uiHeight, uiWidth, decorativeNam
.ele("VerticalLayout", { class: "unitContainer", childForceExpandHeight: "false", preferredHeight: unitData.height, spacing: "20" })
.import(unitData.fragment)
- /*
- ${unitName}
-
- ${unitData}
-
- */
}
storeFormattedXML(id, xml.end({ prettyPrint: false }).slice(27, -7), totalHeight, armyData, uiHeight, uiWidth, baseScript);
@@ -392,16 +335,7 @@ function getUnitXMLData(unit) {
return { fragment, height: maxHeight };
- /*
-
- ${numberString}${modelName}
- ${weapons}
- ${abilities}
-
- .
- .
- .
- */
+
}
function getModelXMLData(model, modelID, unit) {
@@ -435,19 +369,7 @@ function getModelXMLData(model, modelID, unit) {
container.att("preferredHeight", height);
return { fragment, height };
- /*
- ${numberString}${modelName}
-
-
- ${weapons?}
- ${data}
-
-
-
- ${abilities?}
- ${data}
-
- */
+
}
function getModelSectionData (name, dataList) {
@@ -462,12 +384,7 @@ function getModelSectionData (name, dataList) {
return { fragment, height };
- /*
-
- ${title}
- ${data}
-
- */
+
}
function combineAndSortWeapons(model, characteristicProfiles) {
diff --git a/bin/Roster.js b/bin/Roster.js
index 65def14..ee0b3cb 100644
--- a/bin/Roster.js
+++ b/bin/Roster.js
@@ -16,12 +16,18 @@ module.exports.parse = (data) => {
return false;
});
-
+
for (let unitData of armyUnitData) {
let unit = new Unit(unitData.$.name, unitData.$.customName, unitData.$.type === "model");
-
+
+ if (data[0].force[0].rules) {
+ let forceRules = data[0].force[0].rules[0].rule;
+ for (let forceRule of forceRules) {
+ unit.addFormattedRule(forceRule);
+ };
+ }
+
unit.handleSelectionDataRecursive(unitData, null, true);
-
units.set(unit.uuid, unit.update());
}
}
@@ -30,6 +36,6 @@ module.exports.parse = (data) => {
for (const uuid of units.keys())
order.push(uuid);
-
+
return { units, order }
}
\ No newline at end of file
diff --git a/bin/Unit.js b/bin/Unit.js
index 4f9f195..4880c38 100644
--- a/bin/Unit.js
+++ b/bin/Unit.js
@@ -26,6 +26,7 @@ module.exports = class Unit {
weapons = {};
//weapons = [];
rules = [];
+ formattedrules = {};
uuid = crypto.randomBytes(4).toString("hex");
unassignedWeapons = [];
pl = 0;
@@ -71,6 +72,16 @@ module.exports = class Unit {
desc: profileData.characteristics[0].characteristic[0]._.replace(/[\[\]"]/g, m => dangerousReplace[m])
};
}
+ addFormattedRule (rule) {
+ const trimmedName = rule.$.name.match(abilityTrimRegex).groups.ability,
+ dangerousReplace = { "[": "(", "]": ")", '"': '\\"' };
+
+ if (!this.formattedrules[trimmedName])
+ this.formattedrules[trimmedName] = {
+ name: trimmedName.replace(/[\[\]"]/g, m => dangerousReplace[m]),
+ desc: rule.description[0].replace(/[\[\]"]/g, m => dangerousReplace[m])
+ };
+ }
/**
* Adds the given data as an ability to the unit.
@@ -87,6 +98,8 @@ module.exports = class Unit {
};
}
+
+
addRule (ruleData) {
let trimmedName = ruleData.$.name.match(abilityTrimRegex).groups.ability;
@@ -352,8 +365,10 @@ module.exports = class Unit {
}
if (selectionData.rules && selectionData.rules[0] !== "")
- for (const rule of selectionData.rules[0].rule)
- this.addRule(rule);
+ for (const rule of selectionData.rules[0].rule){
+ this.addRule(rule)
+ this.addFormattedRule(rule)
+ };
if (selectionData.selections && selectionData.selections[0] !== "")
for (const selection of selectionData.selections[0].selection)
diff --git a/lua_modules/DataCard_Constants.lua b/lua_modules/DataCard_Constants.lua
index 501c528..522460d 100644
--- a/lua_modules/DataCard_Constants.lua
+++ b/lua_modules/DataCard_Constants.lua
@@ -7,6 +7,10 @@ local uiTemplates = {
${name} |
${desc} |
]],
+ formattedrules = [[
+ | ${name} |
+ ${desc} |
+
]],
models = [[
| ${name} |
${m} |
diff --git a/lua_modules/MatchedPlay.lua b/lua_modules/MatchedPlay.lua
index 4fbf26d..bc7d5ce 100644
--- a/lua_modules/MatchedPlay.lua
+++ b/lua_modules/MatchedPlay.lua
@@ -200,7 +200,6 @@ function onScriptingButtonDown(index, playerColor)
if isHoveringValidTarget then scriptingFunctions[index](playerColor, hoveredObject, player) end
end
-
function onObjectDrop(playerColor, droppedObject)
if not self.hasTag("leaderModel") then return end -- prevents firing on objects we don't want firing
if isCurrentlyCheckingCoherency and
@@ -423,7 +422,8 @@ function buildUI()
buildXMLForSection("abilities")
buildXMLForSection("models")
buildXMLForSection("weapons")
-
+ buildXMLForSection("formattedrules")
+
if unitData.psykerProfiles ~= nil then
buildXMLForSection("powersKnown")
buildXMLForSection("psykerProfiles")
diff --git a/lua_modules/MatchedPlay_Constants.lua b/lua_modules/MatchedPlay_Constants.lua
index 9a17c8c..ec1164f 100644
--- a/lua_modules/MatchedPlay_Constants.lua
+++ b/lua_modules/MatchedPlay_Constants.lua
@@ -71,6 +71,10 @@ local uiTemplates = {
${name} |
${desc} |
]],
+ formattedrules = [[
+ | ${name} |
+ ${desc} |
+
]],
models = [[
| ${name} |
${m} |
diff --git a/package-lock.json b/package-lock.json
index a6aae82..bf27c4e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,45 +1,875 @@
{
"name": "Elastic-Beanstalk-Sample-App",
"version": "0.50.0",
- "lockfileVersion": 1,
+ "lockfileVersion": 2,
"requires": true,
- "dependencies": {
- "@oozcitak/dom": {
- "version": "1.15.10",
- "resolved": "https://registry.npmjs.org/@oozcitak/dom/-/dom-1.15.10.tgz",
- "integrity": "sha512-0JT29/LaxVgRcGKvHmSrUTEvZ8BXvZhGl2LASRUgHqDTC1M5g1pLmVv56IYNyt3bG2CUjDkc67wnyZC14pbQrQ==",
- "requires": {
- "@oozcitak/infra": "1.0.8",
- "@oozcitak/url": "1.0.4",
- "@oozcitak/util": "8.3.8"
+ "packages": {
+ "": {
+ "name": "Elastic-Beanstalk-Sample-App",
+ "version": "0.50.0",
+ "dependencies": {
+ "adm-zip": "^0.5.5",
+ "aws-sdk": "latest",
+ "body-parser": "^1.19.0",
+ "ejs": "^3.1.3",
+ "express": "^4.17.1",
+ "node-static": "^0.7.11",
+ "uuid": "^8.3.0",
+ "xml2js": "^0.4.23"
+ },
+ "devDependencies": {
+ "@types/node": "^15.12.4"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/@types/node": {
+ "version": "15.12.4",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-15.12.4.tgz",
+ "integrity": "sha512-zrNj1+yqYF4WskCMOHwN+w9iuD12+dGm0rQ35HLl9/Ouuq52cEtd0CH9qMgrdNmi5ejC1/V7vKEXYubB+65DkA==",
+ "dev": true
+ },
+ "node_modules/accepts": {
+ "version": "1.3.7",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz",
+ "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==",
+ "dependencies": {
+ "mime-types": "~2.1.24",
+ "negotiator": "0.6.2"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/adm-zip": {
+ "version": "0.5.5",
+ "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.5.tgz",
+ "integrity": "sha512-IWwXKnCbirdbyXSfUDvCCrmYrOHANRZcc8NcRrvTlIApdl7PwE9oGcsYvNeJPAVY1M+70b4PxXGKIf8AEuiQ6w==",
+ "engines": {
+ "node": ">=6.0"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dependencies": {
+ "color-convert": "^1.9.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/array-flatten": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+ "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
+ },
+ "node_modules/async": {
+ "version": "0.9.2",
+ "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz",
+ "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0="
+ },
+ "node_modules/aws-sdk": {
+ "version": "2.1062.0",
+ "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1062.0.tgz",
+ "integrity": "sha512-QIU8jwi7Uqyvw2HjsXXXUZv3V/6TinUzLewrdl2EdvonqZCXhwMgnZx2F9I2x62IKH1RqnINwFWdoK+OTgcAjA==",
+ "dependencies": {
+ "buffer": "4.9.2",
+ "events": "1.1.1",
+ "ieee754": "1.1.13",
+ "jmespath": "0.16.0",
+ "querystring": "0.2.0",
+ "sax": "1.2.1",
+ "url": "0.10.3",
+ "uuid": "3.3.2",
+ "xml2js": "0.4.19"
+ },
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/aws-sdk/node_modules/sax": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz",
+ "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o="
+ },
+ "node_modules/aws-sdk/node_modules/uuid": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
+ "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==",
+ "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.",
+ "bin": {
+ "uuid": "bin/uuid"
+ }
+ },
+ "node_modules/aws-sdk/node_modules/xml2js": {
+ "version": "0.4.19",
+ "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz",
+ "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==",
+ "dependencies": {
+ "sax": ">=0.6.0",
+ "xmlbuilder": "~9.0.1"
+ }
+ },
+ "node_modules/aws-sdk/node_modules/xmlbuilder": {
+ "version": "9.0.7",
+ "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz",
+ "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
+ },
+ "node_modules/base64-js": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/body-parser": {
+ "version": "1.19.0",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz",
+ "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==",
+ "dependencies": {
+ "bytes": "3.1.0",
+ "content-type": "~1.0.4",
+ "debug": "2.6.9",
+ "depd": "~1.1.2",
+ "http-errors": "1.7.2",
+ "iconv-lite": "0.4.24",
+ "on-finished": "~2.3.0",
+ "qs": "6.7.0",
+ "raw-body": "2.4.0",
+ "type-is": "~1.6.17"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/buffer": {
+ "version": "4.9.2",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz",
+ "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==",
+ "dependencies": {
+ "base64-js": "^1.0.2",
+ "ieee754": "^1.1.4",
+ "isarray": "^1.0.0"
+ }
+ },
+ "node_modules/bytes": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz",
+ "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dependencies": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "dependencies": {
+ "color-name": "1.1.3"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
+ },
+ "node_modules/colors": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz",
+ "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==",
+ "engines": {
+ "node": ">=0.1.90"
+ }
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
+ },
+ "node_modules/content-disposition": {
+ "version": "0.5.3",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz",
+ "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==",
+ "dependencies": {
+ "safe-buffer": "5.1.2"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/content-disposition/node_modules/safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+ },
+ "node_modules/content-type": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
+ "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz",
+ "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie-signature": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
+ "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
+ },
+ "node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/debug/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+ },
+ "node_modules/depd": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
+ "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/destroy": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
+ "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA="
+ },
+ "node_modules/ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0="
+ },
+ "node_modules/ejs": {
+ "version": "3.1.6",
+ "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.6.tgz",
+ "integrity": "sha512-9lt9Zse4hPucPkoP7FHDF0LQAlGyF9JVpnClFLFH3aSSbxmyoqINRpp/9wePWJTUl4KOQwRL72Iw3InHPDkoGw==",
+ "dependencies": {
+ "jake": "^10.6.1"
+ },
+ "bin": {
+ "ejs": "bin/cli.js"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/encodeurl": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+ "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/events": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz",
+ "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=",
+ "engines": {
+ "node": ">=0.4.x"
+ }
+ },
+ "node_modules/express": {
+ "version": "4.17.1",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz",
+ "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==",
+ "dependencies": {
+ "accepts": "~1.3.7",
+ "array-flatten": "1.1.1",
+ "body-parser": "1.19.0",
+ "content-disposition": "0.5.3",
+ "content-type": "~1.0.4",
+ "cookie": "0.4.0",
+ "cookie-signature": "1.0.6",
+ "debug": "2.6.9",
+ "depd": "~1.1.2",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "finalhandler": "~1.1.2",
+ "fresh": "0.5.2",
+ "merge-descriptors": "1.0.1",
+ "methods": "~1.1.2",
+ "on-finished": "~2.3.0",
+ "parseurl": "~1.3.3",
+ "path-to-regexp": "0.1.7",
+ "proxy-addr": "~2.0.5",
+ "qs": "6.7.0",
+ "range-parser": "~1.2.1",
+ "safe-buffer": "5.1.2",
+ "send": "0.17.1",
+ "serve-static": "1.14.1",
+ "setprototypeof": "1.1.1",
+ "statuses": "~1.5.0",
+ "type-is": "~1.6.18",
+ "utils-merge": "1.0.1",
+ "vary": "~1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.10.0"
+ }
+ },
+ "node_modules/express/node_modules/safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+ },
+ "node_modules/filelist": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.2.tgz",
+ "integrity": "sha512-z7O0IS8Plc39rTCq6i6iHxk43duYOn8uFJiWSewIq0Bww1RNybVHSCjahmcC87ZqAm4OTvFzlzeGu3XAzG1ctQ==",
+ "dependencies": {
+ "minimatch": "^3.0.4"
+ }
+ },
+ "node_modules/finalhandler": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
+ "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
+ "dependencies": {
+ "debug": "2.6.9",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "on-finished": "~2.3.0",
+ "parseurl": "~1.3.3",
+ "statuses": "~1.5.0",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/forwarded": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/fresh": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+ "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/http-errors": {
+ "version": "1.7.2",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz",
+ "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==",
+ "dependencies": {
+ "depd": "~1.1.2",
+ "inherits": "2.0.3",
+ "setprototypeof": "1.1.1",
+ "statuses": ">= 1.5.0 < 2",
+ "toidentifier": "1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.4.24",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/ieee754": {
+ "version": "1.1.13",
+ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
+ "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg=="
+ },
+ "node_modules/inherits": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
+ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
+ },
+ "node_modules/ipaddr.js": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
+ },
+ "node_modules/jake": {
+ "version": "10.8.2",
+ "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.2.tgz",
+ "integrity": "sha512-eLpKyrfG3mzvGE2Du8VoPbeSkRry093+tyNjdYaBbJS9v17knImYGNXQCUV0gLxQtF82m3E8iRb/wdSQZLoq7A==",
+ "dependencies": {
+ "async": "0.9.x",
+ "chalk": "^2.4.2",
+ "filelist": "^1.0.1",
+ "minimatch": "^3.0.4"
+ },
+ "bin": {
+ "jake": "bin/cli.js"
+ },
+ "engines": {
+ "node": "*"
}
},
- "@oozcitak/infra": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/@oozcitak/infra/-/infra-1.0.8.tgz",
- "integrity": "sha512-JRAUc9VR6IGHOL7OGF+yrvs0LO8SlqGnPAMqyzOuFZPSZSXI7Xf2O9+awQPSMXgIWGtgUf/dA6Hs6X6ySEaWTg==",
- "requires": {
- "@oozcitak/util": "8.3.8"
+ "node_modules/jmespath": {
+ "version": "0.16.0",
+ "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz",
+ "integrity": "sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw==",
+ "engines": {
+ "node": ">= 0.6.0"
}
},
- "@oozcitak/url": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/@oozcitak/url/-/url-1.0.4.tgz",
- "integrity": "sha512-kDcD8y+y3FCSOvnBI6HJgl00viO/nGbQoCINmQ0h98OhnGITrWR3bOGfwYCthgcrV8AnTJz8MzslTQbC3SOAmw==",
- "requires": {
- "@oozcitak/infra": "1.0.8",
- "@oozcitak/util": "8.3.8"
+ "node_modules/media-typer": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+ "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/merge-descriptors": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
+ "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E="
+ },
+ "node_modules/methods": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+ "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+ "bin": {
+ "mime": "cli.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.48.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.48.0.tgz",
+ "integrity": "sha512-FM3QwxV+TnZYQ2aRqhlKBMHxk10lTbMt3bBkMAp54ddrNeVSfcQYOOKuGuy3Ddrm38I04If834fOUSq1yzslJQ==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.31",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.31.tgz",
+ "integrity": "sha512-XGZnNzm3QvgKxa8dpzyhFTHmpP3l5YNusmne07VUOXxou9CqUqYa/HBy124RqtVh/O2pECas/MOcsDgpilPOPg==",
+ "dependencies": {
+ "mime-db": "1.48.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+ "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/minimist": {
+ "version": "0.0.10",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz",
+ "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8="
+ },
+ "node_modules/ms": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
+ "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
+ },
+ "node_modules/negotiator": {
+ "version": "0.6.2",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz",
+ "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/node-static": {
+ "version": "0.7.11",
+ "resolved": "https://registry.npmjs.org/node-static/-/node-static-0.7.11.tgz",
+ "integrity": "sha512-zfWC/gICcqb74D9ndyvxZWaI1jzcoHmf4UTHWQchBNuNMxdBLJMDiUgZ1tjGLEIe/BMhj2DxKD8HOuc2062pDQ==",
+ "dependencies": {
+ "colors": ">=0.6.0",
+ "mime": "^1.2.9",
+ "optimist": ">=0.3.4"
+ },
+ "bin": {
+ "static": "bin/cli.js"
+ },
+ "engines": {
+ "node": ">= 0.4.1"
+ }
+ },
+ "node_modules/on-finished": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
+ "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/optimist": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz",
+ "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=",
+ "dependencies": {
+ "minimist": "~0.0.1",
+ "wordwrap": "~0.0.2"
+ }
+ },
+ "node_modules/parseurl": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/path-to-regexp": {
+ "version": "0.1.7",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
+ "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
+ },
+ "node_modules/proxy-addr": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+ "dependencies": {
+ "forwarded": "0.2.0",
+ "ipaddr.js": "1.9.1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/punycode": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
+ "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0="
+ },
+ "node_modules/qs": {
+ "version": "6.7.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
+ "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
+ "node_modules/querystring": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
+ "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=",
+ "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.",
+ "engines": {
+ "node": ">=0.4.x"
+ }
+ },
+ "node_modules/range-parser": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/raw-body": {
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz",
+ "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==",
+ "dependencies": {
+ "bytes": "3.1.0",
+ "http-errors": "1.7.2",
+ "iconv-lite": "0.4.24",
+ "unpipe": "1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
+ },
+ "node_modules/sax": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
+ "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
+ },
+ "node_modules/send": {
+ "version": "0.17.1",
+ "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz",
+ "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==",
+ "dependencies": {
+ "debug": "2.6.9",
+ "depd": "~1.1.2",
+ "destroy": "~1.0.4",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "fresh": "0.5.2",
+ "http-errors": "~1.7.2",
+ "mime": "1.6.0",
+ "ms": "2.1.1",
+ "on-finished": "~2.3.0",
+ "range-parser": "~1.2.1",
+ "statuses": "~1.5.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/serve-static": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz",
+ "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==",
+ "dependencies": {
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "parseurl": "~1.3.3",
+ "send": "0.17.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/setprototypeof": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
+ "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw=="
+ },
+ "node_modules/statuses": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
+ "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dependencies": {
+ "has-flag": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/toidentifier": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
+ "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==",
+ "engines": {
+ "node": ">=0.6"
}
},
- "@oozcitak/util": {
- "version": "8.3.8",
- "resolved": "https://registry.npmjs.org/@oozcitak/util/-/util-8.3.8.tgz",
- "integrity": "sha512-T8TbSnGsxo6TDBJx/Sgv/BlVJL3tshxZP7Aq5R1mSnM5OcHY2dQaxLMu2+E8u3gN0MLOzdjurqN4ZRVuzQycOQ=="
+ "node_modules/type-is": {
+ "version": "1.6.18",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+ "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+ "dependencies": {
+ "media-typer": "0.3.0",
+ "mime-types": "~2.1.24"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/url": {
+ "version": "0.10.3",
+ "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz",
+ "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=",
+ "dependencies": {
+ "punycode": "1.3.2",
+ "querystring": "0.2.0"
+ }
+ },
+ "node_modules/utils-merge": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+ "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=",
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/uuid": {
+ "version": "8.3.2",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
+ "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
+ "bin": {
+ "uuid": "dist/bin/uuid"
+ }
+ },
+ "node_modules/vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=",
+ "engines": {
+ "node": ">= 0.8"
+ }
},
+ "node_modules/wordwrap": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz",
+ "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/xml2js": {
+ "version": "0.4.23",
+ "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz",
+ "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==",
+ "dependencies": {
+ "sax": ">=0.6.0",
+ "xmlbuilder": "~11.0.0"
+ },
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
+ "node_modules/xmlbuilder": {
+ "version": "11.0.1",
+ "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
+ "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
+ "engines": {
+ "node": ">=4.0"
+ }
+ }
+ },
+ "dependencies": {
"@types/node": {
"version": "15.12.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-15.12.4.tgz",
- "integrity": "sha512-zrNj1+yqYF4WskCMOHwN+w9iuD12+dGm0rQ35HLl9/Ouuq52cEtd0CH9qMgrdNmi5ejC1/V7vKEXYubB+65DkA=="
+ "integrity": "sha512-zrNj1+yqYF4WskCMOHwN+w9iuD12+dGm0rQ35HLl9/Ouuq52cEtd0CH9qMgrdNmi5ejC1/V7vKEXYubB+65DkA==",
+ "dev": true
},
"accepts": {
"version": "1.3.7",
@@ -63,14 +893,6 @@
"color-convert": "^1.9.0"
}
},
- "argparse": {
- "version": "1.0.10",
- "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
- "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
- "requires": {
- "sprintf-js": "~1.0.2"
- }
- },
"array-flatten": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
@@ -82,14 +904,14 @@
"integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0="
},
"aws-sdk": {
- "version": "2.949.0",
- "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.949.0.tgz",
- "integrity": "sha512-n9vqtsLPmSvJcvYvBLBbI1n4GZokwc/5zgHZD7VxdioLNXo1nHQ3VUi4MiW+3kIN40NUNf+Gc5vpc82yNYCvsw==",
+ "version": "2.1062.0",
+ "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1062.0.tgz",
+ "integrity": "sha512-QIU8jwi7Uqyvw2HjsXXXUZv3V/6TinUzLewrdl2EdvonqZCXhwMgnZx2F9I2x62IKH1RqnINwFWdoK+OTgcAjA==",
"requires": {
"buffer": "4.9.2",
"events": "1.1.1",
"ieee754": "1.1.13",
- "jmespath": "0.15.0",
+ "jmespath": "0.16.0",
"querystring": "0.2.0",
"sax": "1.2.1",
"url": "0.10.3",
@@ -290,11 +1112,6 @@
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
},
- "esprima": {
- "version": "4.0.1",
- "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
- "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
- },
"etag": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
@@ -438,18 +1255,9 @@
}
},
"jmespath": {
- "version": "0.15.0",
- "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz",
- "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc="
- },
- "js-yaml": {
- "version": "3.14.0",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz",
- "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==",
- "requires": {
- "argparse": "^1.0.7",
- "esprima": "^4.0.0"
- }
+ "version": "0.16.0",
+ "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz",
+ "integrity": "sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw=="
},
"media-typer": {
"version": "0.3.0",
@@ -630,11 +1438,6 @@
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz",
"integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw=="
},
- "sprintf-js": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
- "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
- },
"statuses": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
@@ -653,11 +1456,6 @@
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
"integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw=="
},
- "tsc": {
- "version": "2.0.3",
- "resolved": "https://registry.npmjs.org/tsc/-/tsc-2.0.3.tgz",
- "integrity": "sha512-SN+9zBUtrpUcOpaUO7GjkEHgWtf22c7FKbKCA4e858eEM7Qz86rRDpgOU2lBIDf0fLCsEg65ms899UMUIB2+Ow=="
- },
"type-is": {
"version": "1.6.18",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
@@ -667,11 +1465,6 @@
"mime-types": "~2.1.24"
}
},
- "typescript": {
- "version": "4.3.4",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.4.tgz",
- "integrity": "sha512-uauPG7XZn9F/mo+7MrsRjyvbxFpzemRjKEZXS4AK83oP2KKOJPvb+9cO/gmnv8arWZvhnjVOXz7B49m1l0e9Ew=="
- },
"unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
@@ -719,18 +1512,6 @@
"version": "11.0.1",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
"integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="
- },
- "xmlbuilder2": {
- "version": "3.0.1",
- "resolved": "https://registry.npmjs.org/xmlbuilder2/-/xmlbuilder2-3.0.1.tgz",
- "integrity": "sha512-BpZMcl56Qz72wZqAlaMyhtfOR5DsWhZZ3j/vUJf5eMKkySgEvBAv7ztC+pa+oUa4Y5aGGzB24C+t5ZLJyarrog==",
- "requires": {
- "@oozcitak/dom": "1.15.10",
- "@oozcitak/infra": "1.0.8",
- "@oozcitak/util": "8.3.8",
- "@types/node": "*",
- "js-yaml": "3.14.0"
- }
}
}
}
diff --git a/test.txt b/test.txt
deleted file mode 100644
index 4f84a22..0000000
--- a/test.txt
+++ /dev/null
@@ -1 +0,0 @@
-sad
\ No newline at end of file