diff --git a/export.sh b/export.sh index 67d981a..8b79de7 100755 --- a/export.sh +++ b/export.sh @@ -1,2 +1,2 @@ #!/bin/bash -git archive --format zip --prefix=mod/ --output ../DiscoScience.zip master +git archive --format zip --output ../DiscoScience_2.0.2.zip master ./src/ diff --git a/spec/mocks/prototypes.lua b/spec/mocks/prototypes.lua index 88b3533..9062e02 100644 --- a/spec/mocks/prototypes.lua +++ b/spec/mocks/prototypes.lua @@ -6,4 +6,6 @@ prototypes.get_technology_filtered = function() return game.mockTechPrototypes end +--FIXME: Need to mock the mod_data + return prototypes \ No newline at end of file diff --git a/changelog.txt b/src/changelog.txt similarity index 98% rename from changelog.txt rename to src/changelog.txt index 8709cda..2c21377 100644 --- a/changelog.txt +++ b/src/changelog.txt @@ -3,6 +3,10 @@ Version: 2.0.2 Date: 08. 12. 2024 Changes: - Changed prototype name to fix incompatibility with Developer Assistant mod + - Support for custom lab animation graphics + Bugfixes: + - Actually use the HR graphics + - Validate and normalize given colors --------------------------------------------------------------------------------------------------- Version: 2.0.1 Date: 12. 10. 2024 diff --git a/src/control.lua b/src/control.lua index a4bba38..a843c47 100644 --- a/src/control.lua +++ b/src/control.lua @@ -6,6 +6,7 @@ local labColoring = require("core.labColoring") -- constants local colorSwitchFrequency = 60 +storage = {} -- Track whether state is linked, in order to be able to avoid remote callbacks -- before proper initialization (when a dependent mod is initialised later in a game) @@ -26,8 +27,14 @@ local linkState = function () end local removeOldData = function () + ---@deprecated Old + ---@type nil storage.scalarState = nil + ---@deprecated Old + ---@type nil storage.labsByForce = nil + ---@deprecated Old + ---@type nil storage.labAnimations = nil end @@ -89,18 +96,14 @@ script.on_configuration_changed( ) script.on_event( - { - defines.events.on_script_trigger_effect - }, + defines.events.on_script_trigger_effect, function (event) labRenderers.addLab(event.target_entity) end ) script.on_event( - { - defines.events.on_object_destroyed - }, + defines.events.on_object_destroyed, function (event) if event.type ~= defines.target_type.entity then return end labRenderers.removeLab(event.useful_id) @@ -116,7 +119,7 @@ script.on_nth_tick( ) script.on_event( - {defines.events.on_tick}, + defines.events.on_tick, function (event) researchColor.validateIngredientColors() labColoring.updateRenderers(event, labRenderers, researchColor) diff --git a/src/core/labColoring.lua b/src/core/labColoring.lua index 233b5ae..02ddd28 100644 --- a/src/core/labColoring.lua +++ b/src/core/labColoring.lua @@ -60,6 +60,12 @@ labColoring.chooseNewDirection = function() end end +---@param lab LuaEntity +---@param colors Color.0 +---@param hq boolean +---@param playerPosition MapPosition +---@param labRenderers LabRenderer +---@param fcolor Color.0 labColoring.updateRenderer = function (lab, colors, hq, playerPosition, labRenderers, fcolor) local animation = labRenderers.getRenderObjects(lab) if lab.status == working or lab.status == low_power then @@ -79,12 +85,16 @@ labColoring.updateRenderer = function (lab, colors, hq, playerPosition, labRende end end +---@param event EventData +---@param labRenderers LabRenderer +---@param researchColor ResearchColor labColoring.updateRenderers = function (event, labRenderers, researchColor) - local hq = settings.global["discoscience-high-quality"].value + local hq = settings.global["discoscience-high-quality"].value --[[@as boolean]] labColoring.state.meanderingTick = max(0, labColoring.state.meanderingTick + labColoring.state.direction) local stride = getStride(hq) local offset = event.tick % stride local fcolor = {r=0, g=0, b=0, a=0} + ---@type table local forceInfo = {} for name, force in pairs(game.forces) do local forceResearchColors = researchColor.getColorsForResearch(force.current_research) diff --git a/src/core/labRenderers.lua b/src/core/labRenderers.lua index 95d0f1a..a3d17aa 100644 --- a/src/core/labRenderers.lua +++ b/src/core/labRenderers.lua @@ -1,3 +1,4 @@ +---@class LabRenderer local labRenderers = {} local draw_animation = rendering.draw_animation @@ -8,60 +9,115 @@ local random = math.random labRenderers.state = nil +---@param state LabRendererState labRenderers.linkState = function (state) labRenderers.state = state return state end +-- Data Validation +local labData = prototypes.mod_data["discoscience-lab-data"].data --[[@as table]] + +---@param lab data.EntityID +---@param data LabData +---@return LabData? validatedData return nil to get it removed from the data lookup +local function validateLabData(lab, data) + local labPrototype = prototypes.entity[lab] + -- These are logs so stale data in the mod-data isn't a crashable offense + if not labPrototype then + log("Given lab data for non-existent lab: "..lab.."\t"..serpent.line(data)) + return nil + end + if labPrototype.type ~= "lab" then + log("Given lab data for a non-lab entity: "..lab) + return nil + end + + --FIXME: This has to wait for 2.1: https://forums.factorio.com/132999 + -- if type(data.animation) ~= "string" + -- or not helpers.is_valid_animation_path(data.animation) then + -- error("Given animation name is not a valid animation: "..lab.." -> "..serpent.line(data.animation)) + -- end + + if not data.scale then + data.scale = 1 + elseif type(data.scale) ~= "number" then + error("Given animation scale is not a number: "..lab.." -> "..serpent.line(data.scale)) + end + + return data +end + +for lab, data in pairs(labData) do + labData[lab] = validateLabData(lab, data) +end + labRenderers.createInitialState = function() + ---@class LabRendererState + ---@field labs table + ---@field labAnimations table + ---@field labData table return { labs = {}, labAnimations = {}, - labScales = { - ["lab"] = 1, - } + labData = labData } end +---@deprecated Trying to move to mod data labRenderers.setLabScale = function (name, scale) - labRenderers.state.labScales[name] = scale + if not scale then + labRenderers.state.labData[name] = nil + return + end + + local labData = labRenderers.state.labData[name] + if not labData then + labRenderers.state.labData[name] = { + animation = "discoscience-lab-storm", + scale = scale, + } + else + labData.scale = scale + end end +---@param entity LuaEntity labRenderers.createAnimation = function (entity) - local scale = labRenderers.state.labScales[entity.name] - labRenderers.state.labAnimations[entity.unit_number] = draw_animation({ - animation = "discoscience-lab-storm", + local labData = labRenderers.state.labData[entity.name] + labRenderers.state.labAnimations[entity.unit_number--[[@as uint]]] = draw_animation({ + animation = labData.animation, surface = entity.surface, target = entity, - x_scale = scale, - y_scale = scale, + x_scale = labData.scale, + y_scale = labData.scale, render_layer = "higher-object-under", animation_offset = floor(random()*300), visible = false, }) end +---@param entity LuaEntity +---@return boolean labRenderers.isCompatibleLab = function (entity) if not entity.type == "lab" then return false end - for name, _ in pairs(labRenderers.state.labScales) do - if entity.name == name then return true end - end - return false + return labRenderers.state.labData[entity.name] and true or false end +---@param entity LuaEntity labRenderers.addLab = function (entity) - if not entity or not entity.valid then + if not entity or not entity.valid + or not labRenderers.isCompatibleLab(entity) then return end - if labRenderers.isCompatibleLab(entity) then - local labUnitNumber = entity.unit_number - if labRenderers.state.labs[labUnitNumber] then - return - end - labRenderers.state.labs[labUnitNumber] = entity - if not labRenderers.state.labAnimations[labUnitNumber] then - labRenderers.createAnimation(entity) - end + + local labUnitNumber = entity.unit_number--[[@as uint]] + if labRenderers.state.labs[labUnitNumber] then + return + end + labRenderers.state.labs[labUnitNumber] = entity + if not labRenderers.state.labAnimations[labUnitNumber] then + labRenderers.createAnimation(entity) end script.register_on_object_destroyed(entity) end @@ -70,14 +126,14 @@ labRenderers.reloadLabs = function () labRenderers.state.labs = {} labRenderers.state.labAnimations = {} rendering.clear("DiscoScience") - for surfaceIndex in pairs(game.surfaces) do - local surface = game.get_surface(surfaceIndex) + for _, surface in pairs(game.surfaces) do for index, lab in ipairs(surface.find_entities_filtered({type = "lab"})) do labRenderers.addLab(lab) end end end +---@param labUnitNumber uint labRenderers.removeLab = function (labUnitNumber) labRenderers.state.labAnimations[labUnitNumber] = nil labRenderers.state.labs[labUnitNumber] = nil @@ -87,6 +143,8 @@ labRenderers.getLabs = function() return labRenderers.state.labs end +---@param entity LuaEntity +---@return LuaRenderObject labRenderers.getRenderObjects = function(entity) local labUnitNumber = entity.unit_number if not labRenderers.state.labAnimations[labUnitNumber].valid then diff --git a/src/core/researchColor.lua b/src/core/researchColor.lua index 42aa093..827715c 100644 --- a/src/core/researchColor.lua +++ b/src/core/researchColor.lua @@ -1,45 +1,119 @@ +---@class ResearchColor local researchColor = {} -- constants +---@type Color.0[] researchColor.defaultColors = { {r = 1.0, g = 0.0, b = 1.0} } -- state -researchColor.state = {} - -researchColor.validated = false +-- researchColor.state = {} +---@param state ResearchColorState +---@return ResearchColorState researchColor.linkState = function (state) researchColor.state = state return state end +--- data validation +local ingredientColors = prototypes.mod_data["discoscience-science-colors"].data --[[@as table]] + +---@param color Color +---@return Color.0? +function researchColor.validateColor(color) + ---@type Color.0 + local possibleColor = { + r = color.r or color[1], + g = color.g or color[2], + b = color.b or color[3], + a = color.a or color[4], + } + local is_255, has_value, outside_bounds = false, false, false + for _, value in pairs(possibleColor) do + has_value = true + if value < 0 or value > 255 then + outside_bounds = true + break + end + if value > 1 then is_255 = true end + end + + if outside_bounds or not has_value then + return nil + end + + if is_255 then + for key, value in pairs(possibleColor) do + possibleColor[key] = value / 255 + end + end + + local alpha = possibleColor.a or 1 + + return { + r = (possibleColor.r or 0) * alpha, + g = (possibleColor.g or 0) * alpha, + b = (possibleColor.b or 0) * alpha, + } +end + +---@param item data.ItemID +---@param color Color +---@return Color.0? validated_color return nil to get it removed from the color lookup +local function validateSciencePack(item, color) + local item_prototype = prototypes.item[item] + + -- These are logs so stale data in the mod-data isn't a crashable offense + if not item_prototype then + log("Given a color for a non-existent item: "..item.." - "..serpent.line(color)) + return nil + end + if item_prototype.type ~= "tool" then + log("Given item was not a science pack: "..item.." - "..serpent.line(color)) + return nil + end + + local valid_color = researchColor.validateColor(color) + if not valid_color then + error("Given item color was not a valid color: "..item.." - "..serpent.line(color)) + end + return valid_color +end + +for item, color in pairs(ingredientColors) do + ingredientColors[item] = validateSciencePack(item, color) +end + + researchColor.createInitialState = function() + ---@class ResearchColorState + ---@field validated boolean + ---@field researchColors table + ---@field ingredientColors table return { validated = false, researchColors = {}, - ingredientColors = { - ["automation-science-pack"] = {r = 0.91, g = 0.16, b = 0.20}, - ["logistic-science-pack"] = {r = 0.29, g = 0.97, b = 0.31}, - ["chemical-science-pack"] = {r = 0.28, g = 0.93, b = 0.95}, - ["production-science-pack"] = {r = 0.83, g = 0.06, b = 0.92}, - ["military-science-pack"] = {r = 0.50, g = 0.10, b = 0.50}, - ["utility-science-pack"] = {r = 0.96, g = 0.93, b = 0.30}, - ["space-science-pack"] = {r = 0.80, g = 0.80, b = 0.80}, - ["agricultural-science-pack"] = {r = 0.84, g = 0.84, b = 0.15}, - ["metallurgic-science-pack"] = {r = 0.99, g = 0.50, b = 0.04}, - ["electromagnetic-science-pack"] = {r = 0.89, g = 0.00, b = 0.56}, - ["cryogenic-science-pack"] = {r = 0.14, g = 0.18, b = 0.74}, - ["promethium-science-pack"] = {r = 0.10, g = 0.10, b = 0.50}, - }, + ingredientColors = ingredientColors, } end +---@param name data.ItemID +---@param color Color.0 researchColor.setIngredientColor = function(name, color) - researchColor.state.ingredientColors[name] = color + if not color then + researchColor.state.ingredientColors[name] = nil + return + end + + local valid_color = researchColor.validateColor(color) + if not valid_color then error("Invalid color given") end + researchColor.state.ingredientColors[name] = valid_color end +---@param name data.ItemID +---@return Color.0 researchColor.getIngredientColor = function(name) return researchColor.state.ingredientColors[name] end @@ -49,7 +123,8 @@ researchColor.validateIngredientColors = function() return end researchColor.state.validated = true - local techPrototypes = prototypes.get_technology_filtered({}) + local techPrototypes = prototypes.get_technology_filtered({}) -- NOTE: HUH? Why not just prototypes.technology + ---@type table local notFound = {} for _, tech in pairs(techPrototypes) do for _, ingredient in pairs(tech.research_unit_ingredients) do @@ -75,7 +150,10 @@ researchColor.validateIngredientColors = function() end +---@param tech LuaTechnology +---@return Color.0[] researchColor.assembleColorsForResearch = function (tech) + ---@type Color.0[] local colors = {} for index, ingredient in pairs(tech.research_unit_ingredients) do local ingredientColor = researchColor.state.ingredientColors[ingredient.name] @@ -89,6 +167,8 @@ researchColor.assembleColorsForResearch = function (tech) return colors end +---@param tech LuaTechnology? +---@return Color.0[] researchColor.getColorsForResearch = function (tech) if not tech then return researchColor.defaultColors diff --git a/src/data.lua b/src/data.lua index 76f2c25..dc95017 100644 --- a/src/data.lua +++ b/src/data.lua @@ -1,11 +1,14 @@ +require("prototypes.modData") local labChanges = require("prototypes.labChanges") _G.DiscoScience = {} -_G.DiscoScience.prepareLab = function (lab) - labChanges.prepareLab(lab) -end +_G.DiscoScience.prepareLab = labChanges.prepareLab -labChanges.prepareLab(data.raw["lab"]["lab"]) +-- Note, this instance doesn't require the two arguments, but as an example it should. +labChanges.prepareLab(data.raw["lab"]["lab"], { + animation = "discoscience-lab-storm", + scale = 1, +}) data:extend{labChanges.labStorm} \ No newline at end of file diff --git a/src/graphics/lab-storm.png b/src/graphics/lab-storm.png deleted file mode 100644 index ec08211..0000000 Binary files a/src/graphics/lab-storm.png and /dev/null differ diff --git a/info.json b/src/info.json similarity index 96% rename from info.json rename to src/info.json index 030e8f1..ed83bbe 100644 --- a/info.json +++ b/src/info.json @@ -7,7 +7,7 @@ "homepage": "", "factorio_version": "2.0", "dependencies": [ - "base >= 2.0" + "base >= 2.0.56" ], "description": "Science labs light up with the colour of the ingredients they're consuming, instead of always blue. More advanced recipes make a fancier show! If it's slowing down your game, try turning off \"high quality\" in the mod options." } diff --git a/locale/en/errors.cfg b/src/locale/en/errors.cfg similarity index 100% rename from locale/en/errors.cfg rename to src/locale/en/errors.cfg diff --git a/locale/en/locale.cfg b/src/locale/en/locale.cfg similarity index 100% rename from locale/en/locale.cfg rename to src/locale/en/locale.cfg diff --git a/src/prototypes/labChanges.lua b/src/prototypes/labChanges.lua index 27ff092..0af6ea6 100644 --- a/src/prototypes/labChanges.lua +++ b/src/prototypes/labChanges.lua @@ -1,6 +1,26 @@ local labChanges = {} -labChanges.prepareLab = function (lab) +local labData = data.raw["mod-data"]["discoscience-lab-data"].data + +---@param lab data.LabPrototype +---@param data? LabData +labChanges.prepareLab = function (lab, data) + if not data then + data = { + animation = "discoscience-lab-storm", + scale = 1 + } + else + if not data.animation then + data.animation = "discoscience-lab-storm" + end + if not data.scale then + data.scale = 1 + end + end + + labData[lab.name] = data + lab.on_animation = lab.off_animation lab.created_effect = { type = "direct", @@ -20,28 +40,16 @@ labChanges.labStorm = { type = "animation", name = "discoscience-lab-storm", - filename = "__DiscoScience__/graphics/lab-storm.png", + filename = "__DiscoScience__/graphics/hr-lab-storm.png", blend_mode = "additive", draw_as_glow = true, - width = 106, - height = 100, + width = 216, + height = 194, frame_count = 33, line_length = 11, animation_speed = 1 / 3, - shift = util.by_pixel(-1, 1), - hr_version = - { - filename = "__DiscoScience__/graphics/hr-lab-storm.png", - blend_mode = "additive", - draw_as_glow = true, - width = 216, - height = 194, - frame_count = 33, - line_length = 11, - animation_speed = 1 / 3, - shift = util.by_pixel(0, 0), - scale = 0.5 - } + shift = util.by_pixel(0, 0), + scale = 0.5 } return labChanges \ No newline at end of file diff --git a/src/prototypes/modData.lua b/src/prototypes/modData.lua new file mode 100644 index 0000000..96515f6 --- /dev/null +++ b/src/prototypes/modData.lua @@ -0,0 +1,33 @@ +---@class LabData +---@field scale? double +---@field animation string Name of an animation prototype + +data:extend{ + { + type = "mod-data", + name = "discoscience-lab-data", + data_type = "ds-lab-data", + ---@type table + data = {} + }, + { + type = "mod-data", + name = "discoscience-science-colors", + data_type = "ds-science-colors", + ---@type table + data = { + ["automation-science-pack"] = {r = 0.91, g = 0.16, b = 0.20}, + ["logistic-science-pack"] = {r = 0.29, g = 0.97, b = 0.31}, + ["chemical-science-pack"] = {r = 0.28, g = 0.93, b = 0.95}, + ["production-science-pack"] = {r = 0.83, g = 0.06, b = 0.92}, + ["military-science-pack"] = {r = 0.50, g = 0.10, b = 0.50}, + ["utility-science-pack"] = {r = 0.96, g = 0.93, b = 0.30}, + ["space-science-pack"] = {r = 0.80, g = 0.80, b = 0.80}, + ["agricultural-science-pack"] = {r = 0.84, g = 0.84, b = 0.15}, + ["metallurgic-science-pack"] = {r = 0.99, g = 0.50, b = 0.04}, + ["electromagnetic-science-pack"] = {r = 0.89, g = 0.00, b = 0.56}, + ["cryogenic-science-pack"] = {r = 0.14, g = 0.18, b = 0.74}, + ["promethium-science-pack"] = {r = 0.10, g = 0.10, b = 0.50}, + } + } +} \ No newline at end of file diff --git a/thumbnail.png b/src/thumbnail.png similarity index 100% rename from thumbnail.png rename to src/thumbnail.png diff --git a/src/utils/colorMath.lua b/src/utils/colorMath.lua index 7eef42b..bcb025b 100644 --- a/src/utils/colorMath.lua +++ b/src/utils/colorMath.lua @@ -12,12 +12,20 @@ local random = math.random local colorMath = {} +---@param x double +---@param a Color.0 +---@param b Color.0 +---@param out Color.0 function colorMath.lerpColor(x, a, b, out) out.r = a.r + (b.r - a.r) * x out.g = a.g + (b.g - a.g) * x out.b = a.b + (b.b - a.b) * x end +---@param t double +---@param colors Color.0[] +---@param blendHardness double +---@param output Color.0 function colorMath.loopInterpolate(t, colors, blendHardness, output) local index1, x = modf(t) local index2 = index1 + 1 @@ -27,6 +35,7 @@ function colorMath.loopInterpolate(t, colors, blendHardness, output) colorMath.lerpColor(x, color1, color2, output) end +---@type (fun(tick:uint,colors:Color.0[],playerPosition:MapPosition,labPosition:MapPosition,fcolor:Color.0))[] colorMath.colorFunctions = { function (tick, colors, playerPosition, labPosition, fcolor) local r = distance(playerPosition, labPosition)