Первый фикс

Пачки некоторых позиций увеличены
This commit is contained in:
2024-03-01 20:53:32 +03:00
commit 7c9c708c92
23653 changed files with 767936 additions and 0 deletions

View File

@@ -0,0 +1,41 @@
--- Category
-- @classmod Data.Category
local Category = {
__class = 'Category',
__index = require('__stdlib__/stdlib/data/data'),
__call = require('__stdlib__/stdlib/data/data').__call
}
setmetatable(Category, Category)
Category.category_types = {
['ammo-category'] = true,
['equipment-category'] = true,
['fuel-category'] = true,
['recipe-category'] = true,
['module-category'] = true,
['rail-category'] = true,
['resource-category'] = true
}
function Category:create()
return self
end
function Category:add(_)
return self
end
function Category:remove(_)
return self
end
function Category:replace(a, b)
if self:valid('category') then
self:remove(a)
self:add(b)
end
return self
end
return Category

View File

@@ -0,0 +1,522 @@
--- Data
-- @classmod Data
require('__stdlib__/stdlib/core') -- Calling core up here to setup any required global stuffs
if _G.remote and _G.script then
error('Data Modules can only be required in the data stage', 2)
end
local Table = require('__stdlib__/stdlib/utils/table')
local groups = require('__stdlib__/stdlib/data/modules/groups')
local Data = {
__class = 'Data',
__index = require('__stdlib__/stdlib/core'),
Sprites = require('__stdlib__/stdlib/data/modules/sprites'),
Pipes = require('__stdlib__/stdlib/data/modules/pipes'),
Util = require('__stdlib__/stdlib/data/modules/util'),
_default_options = {
['silent'] = false, -- Don't log if not present
['fail'] = false, -- Error instead of logging
['verbose'] = false, -- Extra logging info
['extend'] = true, -- Extend the data
['skip_string_validity'] = false, -- Skip checking for valid data
['items_and_fluids'] = true -- consider fluids valid for Item checks
}
}
setmetatable(Data, Data)
local inspect = _ENV.inspect
local rawtostring = _ENV.rawtostring
--))
--(( Local Functions ))--
-- This is the tracing function.
local function log_trace(self, object, object_type)
local msg = (self.__class and self.__class or '') .. (self.name and '/' .. self.name or '') .. ' '
msg = msg .. (object_type and (object_type .. '/') or '') .. tostring(object) .. ' does not exist.'
local trace = _ENV.data_traceback()
log(msg .. trace)
end
--)) END Local Functions ((--
--(( METHODS ))--
--- Is this a valid object
-- @tparam[opt] string type if present is the object this type
-- @treturn self
function Data:is_valid(type)
if type then
return rawget(self, 'valid') == type or false
else
return rawget(self, 'valid') and true or false
end
end
function Data:is_class(class)
if class then
return self.__class == class or false
else
return self.__class and true or false
end
end
function Data:print(...)
local arr = {}
for _, key in pairs { ... } do
arr[#arr + 1] = inspect(self[key])
end
print(Table.unpack(arr))
return self
end
function Data:log(tbl)
local reduce_spam = function(item, path)
-- if item == self.class then
-- return {item.__class, self.__class}
-- end
if item == self._object_mt then
return { self.__class, tostring(self) }
end
if path[#path] == 'parent' then
return { tostring(item), item.__class }
end
if path[#path] == 'class' then
return { self.__class, item.__class }
end
if path[#path] == inspect.METATABLE then
return { self.__class or item.__class, item.__class }
end
return item
end
log(inspect(tbl and tbl or self, { process = reduce_spam }))
return self
end
function Data:serpent()
log(serpent.block(self, { name = self.name, metatostring = false, nocode = true, comment = false }))
return self
end
function Data:error(msg)
error(msg or 'Forced Error')
return self
end
--- Changes the validity of the object.
-- @tparam boolean bool
-- @treturn self
function Data:continue(bool)
rawset(self, 'valid', (bool and rawget(self, '_raw') and self.type) or false)
return self
end
--- Changes the validity of the object if the passed function is true.
-- @tparam function func the function to test, self is passed as the first paramater
-- @treturn self
function Data:continue_if(fun, ...)
rawset(self, 'valid', (fun(self, ...) and rawget(self, '_raw') and self.type) or false)
return self
end
--- Extend object into the data table
-- @tparam[opt] boolean force Extend even if it is already extended
-- @treturn self
function Data:extend(force)
if self.valid and (self.options.extend or force) then
if not self.extended or force then
local t = data.raw[self.type]
if t == nil then
t = {}
data.raw[self.type] = t
end
t[self.name] = self._raw
self.extended = true
end
end
if force then
log('NOTICE: Force extend ' .. self.type .. '/' .. self.name)
elseif not self.options.extend and not self.extended then
log('NOTICE: Did not extend ' .. self.type .. '/' .. self.name)
end
if self.overwrite then
log('NOTICE: Overwriting ' .. self.type .. '/' .. self.name)
end
return self
end
--- Copies a recipe to a new recipe.
-- @tparam string new_name The new name for the recipe.
-- @tparam string result
-- @tparam[opt] table opts
-- @treturn self
function Data:copy(new_name, result, opts)
assert(type(new_name) == 'string', 'new_name must be a string')
if self:is_valid() then
result = result or new_name
local copy = Table.deep_copy(rawget(self, '_raw'))
copy.name = new_name
-- For Entities
-- Need to also check mining results!!!!!!
if copy.minable and copy.minable.result then
copy.minable.result = result
end
-- For items
if copy.place_result then
copy.place_result = result
end
-- rail planners
if copy.placeable_by and copy.placeable_by.item then
copy.placeable_by.item = result
end
-- For recipes, should also to check results!!
if copy.type == 'recipe' then
if copy.normal and copy.normal.result then
copy.normal.result = new_name
copy.expensive.result = new_name
else
if copy.result then
copy.result = new_name
end
end
end
return self(copy, nil, opts or self.options)
else
error('Cannot Copy, invalid prototype', 4)
end
end
--(( Flags ))--
function Data:Flags(create)
if create then
self.flags = Data.Unique_Array(self.flags)
end
return self.flags or Data.Unique_Array()
end
function Data:add_flag(flag)
self:Flags(true):add(flag)
return self
end
function Data:remove_flag(flag)
self:Flags(true):remove(flag)
return self
end
function Data:has_flag(flag)
return self:Flags():all(flag)
end
function Data:any_flag(flag)
return self:Flags():all(flag)
end
--)) Flags ((--
--- Run a function if the object is valid.
-- The object and any additional paramaters are passed to the function.
-- @tparam function func then function to run.
-- @treturn self
function Data:run_function(fun, ...)
if self:is_valid() then
fun(self, ...)
end
return self
end
Data.execute = Data.run_function
--- Run a function on a valid object and return its results.
-- @tparam function func the function to run. self is passed as the first paramter
-- @treturn boolean if the object was valid
-- @treturn the results from the passed function
function Data:get_function_results(fun, ...)
if self:is_valid() then
return true, fun(self, ...)
end
end
--- Set the unique array class to the field if the field is present
-- @tparam table tab
-- @treturn self
function Data:set_unique_array(tab)
if self:is_valid() and tab then
self.Unique_Array(tab)
end
return self
end
--- Add or change a field.
-- @tparam string field the field to change.
-- @tparam mixed value the value to set on the field.
-- @treturn self
function Data:set_field(field, value)
self[field] = value
return self
end
Data.set = Data.set_field
--- Iterate a dictionary table and set fields on the object. Existing fields are overwritten.
-- @tparam table tab dictionary table of fields to set.
-- @treturn self
function Data:set_fields(tab)
if self:is_valid() then
for field, value in pairs(tab) do
self[field] = value
end
end
return self
end
--- Get a field.
-- @tparam string field
-- @tparam mixed default_value return this if the field doesn't exist
-- @treturn nil|mixed the value of the field
function Data:get_field(field, default_value)
if self:is_valid() then
local has = self[field]
if has ~= nil then
return has
else
return default_value
end
end
end
--- Iterate an array of fields and return the values as paramaters
-- @tparam array arr
-- @tparam boolean as_dictionary Return the results as a dictionary table instead of parameters
-- @treturn mixed the parameters
-- @usage local icon, name = Data('stone-furnace', 'furnace'):get_fields({icon, name})
function Data:get_fields(arr, as_dictionary)
if self:is_valid() then
local values = {}
for _, name in pairs(arr) do
values[as_dictionary and name or #values + 1] = self[name]
end
return as_dictionary and values or Table.unpack(values)
end
end
--- Remove an indiviual field from the the object
-- @tparam string field The field to remove
-- @treturn self
function Data:remove_field(field)
if self:is_valid() then
self[field] = nil
end
return self
end
--- Iterate a string array and set to nil.
-- @tparam table arr string array of fields to remove.
-- @treturn self
function Data:remove_fields(arr)
if self:is_valid() then
for _, field in pairs(arr) do
self[field] = nil
end
end
return self
end
--- Change the item-subgroup and/or order.
-- @tparam[opt=nil] string subgroup, The subgroup to change to if valid.
-- @tparam[opt=nil] string order The order string to use
-- note if subgroup is non nil and subgroub is not valid order wil not be changed.
-- @treturn self
function Data:subgroup_order(subgroup, order)
if self:is_valid() then
if subgroup then
if data.raw['item-subgroup'][subgroup] then
self.subgroup = subgroup
else
order = false
end
end
if order and #order > 0 then
self.order = order
end
end
return self
end
--- Replace an icon
-- @tparam string icon
-- @tparam int size
function Data:replace_icon(icon, size)
if self:is_valid() then
if type(icon) == 'table' then
self.icons = icon
self.icon = nil
else
self.icon = icon
end
self.icon_size = size or self.icon_size
end
if not self.icon_size then
error('Icon present but icon size not detected')
end
return self
end
--- Get the icons
-- @tparam[opt=false] boolean copy return a copy of the icons table
-- @treturn table icons
function Data:get_icons(copy)
if self:is_valid() then
return copy and Table.deep_copy(self.icons) or self.icons
end
end
function Data:get_icon()
if self:is_valid() then
return self.icon
end
end
function Data:make_icons(...)
if self:is_valid() then
if not self.icons then
if self.icon then
self.icons = { { icon = self.icon, icon_size = self.icon_size } }
self.icon = nil
else
self.icons = {}
end
end
for _, icon in pairs { ... } do
self.icons[#self.icons + 1] = Table.deep_copy(icon)
end
end
return self
end
function Data:set_icon_at(index, values)
if self:is_valid() then
if self.icons then
for k, v in pairs(values or {}) do
self.icons[index].k = v
end
end
end
return self
end
--- Get the objects name.
-- @treturn string the objects name
function Data:tostring()
return self.valid and (self.name and self.type) and (self.type .. '/' .. self.name) or rawtostring(self)
end
function Data:pairs(source, opts)
local index, val
if not source and self.type then
source = data.raw[self.type]
else
local type = type(source)
source = type == 'string' and data.raw[source] or (assert(type == 'table', 'Source missing') and source)
end
---@cast source -false
local function _next()
index, val = next(source, index)
if index then
return index, self(val, nil, opts)
end
end
return _next, index, val
end
--- Returns a valid thing object reference. This is the main getter.
-- @tparam string|table object The thing to use, if string the thing must be in data.raw[type], tables are not verified
-- @tparam[opt] string object_type the raw type. Required if object is a string
-- @tparam[opt] table opts options to pass
-- @treturn Object
function Data:get(object, object_type, opts)
--assert(type(object) == 'string' or type(object) == 'table', 'object string or table is required')
-- Create our middle man container object
local new = {
class = self.class or self,
_raw = nil,
_products = nil,
_parent = nil,
valid = false,
extended = false,
overwrite = false,
options = Table.merge(Table.deep_copy(Data._default_options), opts or self.options or {})
}
if type(object) == 'table' then
assert(object.type and object.name, 'name and type are required')
new._raw = object
new.valid = object.type
--Is a data-raw that we are overwriting
local existing = data.raw[object.type] and data.raw[object.type][object.name]
new.extended = existing == object
new.overwrite = not new.extended and existing and true or false
elseif type(object) == 'string' then
--Get type from object_type, or fluid or item_and_fluid_types
local types = (object_type and { object_type }) or (self.__class == 'Item' and groups.item_and_fluid)
if types then
for _, type in pairs(types) do
new._raw = data.raw[type] and data.raw[type][object]
if new._raw then
new.valid = new._raw.type
new.extended = true
break
end
end
else
error('object_type is missing for ' .. (self.__class or 'Unknown') .. '/' .. (object or ''), 3)
end
end
setmetatable(new, self._object_mt)
if new.valid then
rawset(new, '_parent', new)
self.Unique_Array.set(new.flags)
self.Unique_Array.set(new.crafting_categories)
self.Unique_Array.set(new.mining_categories)
self.Unique_Array.set(new.inputs)
elseif not new.options.silent then
log_trace(new, object, object_type)
end
return new:extend()
end
Data.__call = Data.get
--)) END Methods ((--
-- This is the table set on new objects
Data._object_mt = {
--__class = "Data",
-- index from _raw if that is not available then retrieve from the class
__index = function(t, k)
return rawget(t, '_raw') and t._raw[k] or t.class[k]
end,
-- Only allow setting on valid _raw tables.
__newindex = function(t, k, v)
if rawget(t, 'valid') and rawget(t, '_raw') then
t._raw[k] = v
end
end,
-- Call the getter on itself
__call = function(t, ...)
return t:__call(...)
end,
-- use Core.tostring
__tostring = Data.tostring
}
return Data

View File

@@ -0,0 +1,73 @@
--- Developer
-- @script Developer
local Data = require('__stdlib__/stdlib/data/data')
local Developer = {
__index = Data
}
setmetatable(Developer, Developer)
local function make_no_controls()
local controls = {}
for name in pairs(data.raw['autoplace-control']) do
if name == 'grass' then
controls[name] = { size = 'very-high', frequency = 'very-high', richness = 'very-low' }
else
controls[name] = { size = 'none', frequency = 'very-low', richness = 'very-low' }
end
end
return controls
end
--- Make entities for easier mod testing.
-- @tparam string name The name of your mod
-- @usage
-- --data.lua
-- local Developer = require('__stdlib__/stdlib/data/develper/developer')
-- Developer.make_test_entities()
function Developer.make_test_entities()
log('Making developer debug entities')
if not data.raw['electric-energy-interface']['debug-energy-interface'] then
Data('electric-energy-interface', 'electric-energy-interface'):copy('debug-energy-interface'):set_fields {
flags = { 'placeable-off-grid' },
localised_name = 'Debug source',
icon = data.raw['item']['electric-energy-interface'].icon,
collision_mask = {},
selection_box = { { 0.0, -0.5 }, { 0.5, 0.5 } },
picture = Developer.Sprites.empty_picture()
}:remove_fields { 'minable', 'collision_box', 'vehicle_impact_sound', 'working_sound' }
end
if not data.raw['electric-pole']['debug-substation'] then
Data('substation', 'electric-pole'):copy('debug-substation'):set_fields {
localised_name = 'Debug power substation',
flags = { 'placeable-off-grid' },
icon = data.raw['item']['substation'].icon,
selection_box = { { -0.5, -0.5 }, { 0.0, 0.5 } },
collision_mask = {},
pictures = Developer.Sprites.empty_pictures(),
maximum_wire_distance = 64,
supply_area_distance = 64,
connection_points = Developer.Sprites.empty_connection_points(1)
}:remove_fields { 'minable', 'collision_box', 'vehicle_impact_sound', 'working_sound' }
end
local gen = data.raw['map-gen-presets']
gen['default']['debug'] = {
type = 'map-gen-presets',
name = 'debug',
localised_name = 'Debug',
localised_description = 'Default settings for a debug world',
order = 'z',
basic_settings = {
terrain_segmentation = 'very-low',
water = 'none',
autoplace_controls = make_no_controls(),
height = 128,
width = 128
},
}
end
return Developer

View File

@@ -0,0 +1,43 @@
--- Entity class
-- @classmod Data.Entity
local Data = require('__stdlib__/stdlib/data/data')
local Entity = {
__class = 'Entity',
__index = Data,
__call = Data.__call
}
setmetatable(Entity, Entity)
function Entity:get_minable_item()
local Item = require('__stdlib__/stdlib/data/item')
if self:is_valid() then
local m = self.minable
return Item(m and (m.result or (m.results and (m.results[1] or m.results.name))), nil, self.options)
end
return Item()
end
function Entity:is_player_placeable()
if self:is_valid() then
return self:Flags():any('player-creation', 'placeable-player')
end
return false
end
function Entity:change_lab_inputs(name, add)
if self:is_valid('lab') then
Entity.Unique_Array.set(self.inputs)
if add then
self.inputs:add(name)
else
self.inputs:remove(name)
end
else
log('Entity is not a lab.' .. _ENV.data_traceback())
end
return self
end
return Entity

View File

@@ -0,0 +1,16 @@
--- Fluid
-- @classmod Data.Fluid
local Data = require('__stdlib__/stdlib/data/data')
local Fluid = {
__class = 'Fluid',
__index = Data,
}
function Fluid:__call(fluid)
return self:get(fluid, 'fluid')
end
setmetatable(Fluid, Fluid)
return Fluid

View File

@@ -0,0 +1,44 @@
--- Item
-- @classmod Data.Item
local Data = require('__stdlib__/stdlib/data/data')
local Table = require('__stdlib__/stdlib/utils/table')
local Item = {
__class = 'Item',
__index = Data,
__call = Data.__call
}
setmetatable(Item, Item)
local function make_table(params)
if not params then
return Table.keys(data.raw.lab)
else
return type(params) == 'table' and params or { params }
end
end
local function change_inputs(name, lab_names, add)
lab_names = make_table(lab_names)
local Entity = require('__stdlib__/stdlib/data/entity')
for _, lab_name in pairs(lab_names) do
Entity(lab_name, 'lab'):change_lab_inputs(name, add)
end
end
function Item:add_to_labs(lab_names)
if self:is_valid() then
change_inputs(self.name, lab_names, true)
end
return self
end
function Item:remove_from_labs(lab_names)
if self:is_valid() then
change_inputs(self.name, lab_names, false)
end
return self
end
return Item

View File

@@ -0,0 +1,15 @@
// {type:class}
// {direction:topDown}
[Data{bg:red}]->[Recipe]
[Recipe]->[Raw]
[Ingredients]
[Results]
[Products]^[Ingredients]
[Products]^[Results]
[Recipe]->[Ingredients]
[Recipe]->[Results]
[Parent]<->[note: Get the damn parent]
[Ingredients|parent;child]
//[Ingredients]->[Parent]
[Results]->[Parent]
[Parent]->[Recipe]

View File

@@ -0,0 +1,199 @@
local table = require('__stdlib__/stdlib/utils/table')
local groups = {}
groups.category = {
'ammo-category',
'equipment-category',
'fuel-category',
'item-group',
'item-subgroup',
'module-category',
'rail-category',
'recipe-category',
'resource-category',
}
groups.equipment = {
'active-defense-equipment',
'battery-equipment',
'belt-immunity-equipment',
'energy-shield-equipment',
'generator-equipment',
'movement-bonus-equipment',
'night-vision-equipment',
'roboport-equipment',
'solar-panel-equipment',
}
groups.other = {
'ambient-sound',
'autoplace-control',
'combat-robot-count',
'damage-type',
'equipment-grid',
'font',
'gui-style',
'map-gen-presets',
'map-settings',
'market',
'noise-expression',
'noise-layer',
'optimized-decorative',
'recipe',
'technology',
'tile',
'train-path-achievement',
'trivial-smoke',
'tutorial',
}
groups.utility = {
'utility-constants',
'utility-sounds',
'utility-sprites'
}
groups.signal = {
'virtual-signal'
}
groups.achievement = {
'achievement',
'build-entity-achievement',
'construct-with-robots-achievement',
'deconstruct-with-robots-achievement',
'deliver-by-robots-achievement',
'dont-build-entity-achievement',
'dont-craft-manually-achievement',
'dont-use-entity-in-energy-production-achievement',
'finish-the-game-achievement',
'group-attack-achievement',
'kill-achievement',
'player-damaged-achievement',
'produce-achievement',
'produce-per-hour-achievement',
'research-achievement',
}
groups.item = {
'item',
'ammo',
'armor',
'blueprint-book',
'blueprint',
'capsule',
'deconstruction-item',
'gun',
'item-with-entity-data',
'item-with-inventory',
'item-with-label',
'item-with-tags',
'module',
'rail-planner',
'repair-tool',
'selection-tool',
'tool',
}
groups.entity = {
'accumulator',
'ammo-turret',
'arithmetic-combinator',
'arrow',
'artillery-flare',
'artillery-projectile',
'artillery-turret',
'artillery-wagon',
'assembling-machine',
'beacon',
'beam',
'boiler',
'car',
'cargo-wagon',
'character-corpse',
'cliff',
'combat-robot',
'constant-combinator',
'construction-robot',
'container',
'corpse',
'curved-rail',
'decider-combinator',
'deconstructible-tile-proxy',
'decorative',
'electric-energy-interface',
'electric-pole',
'electric-turret',
'entity-ghost',
'explosion',
'fire',
'fish',
'flame-thrower-explosion',
'fluid-turret',
'fluid-wagon',
'flying-text',
'furnace',
'gate',
'generator',
'god-controller',
'heat-pipe',
'infinity-container',
'inserter',
'item-entity',
'item-request-proxy',
'lab',
'lamp',
'land-mine',
'leaf-particle',
'loader',
'locomotive',
'logistic-container',
'logistic-robot',
'mining-drill',
'offshore-pump',
'particle',
'particle-source',
'pipe',
'pipe-to-ground',
'player',
'player-port',
'power-switch',
'programmable-speaker',
'projectile',
'pump',
'radar',
'rail-chain-signal',
'rail-remnants',
'rail-signal',
'reactor',
'resource',
'roboport',
'rocket-silo',
'rocket-silo-rocket',
'rocket-silo-rocket-shadow',
'simple-entity',
'simple-entity-with-force',
'simple-entity-with-owner',
'smoke',
'smoke-with-trigger',
'solar-panel',
'splitter',
'sticker',
'storage-tank',
'straight-rail',
'stream',
'tile-ghost',
'train-stop',
'transport-belt',
'tree',
'turret',
'underground-belt',
'unit',
'unit-spawner',
'wall'
}
groups.fluid = {
'fluid'
}
groups.item_and_fluid = table.array_combine(groups.item, groups.fluid)
return groups

View File

@@ -0,0 +1,93 @@
render layers
"tile-transition", "resource", "decorative", "remnants", "floor", "transport-belt-endings", "corpse",
"floor-mechanics", "item", "lower-object", "object", "higher-object-above", "higher-object-under",
"wires", "lower-radius-visualization", "radius-visualization", "entity-info-icon", "explosion",
"projectile", "smoke", "air-object", "air-entity-info-con", "light-effect", "selection-box", "arrow", "cursor"
collision masks
"ground-tile", "water-tile", "resource-layer", "floor-layer", "item-layer",
"object-layer", "player-layer", "ghost-layer", "doodad-layer", "not-colliding-with-itself"
-------------------------------------------------------------------------------
Data(name, type) Data:Get()
Data:valid(type)
Data:copy()
Data:Flags()
Data:extend()
Data:continue()
Data:continue_if(func)
Data:subgroup_order()
Data:run_function(func) Data:execute()
Data:get_function_results(func)
Recipe:set_field()
Recipe:set_fields()
Recipe:remove_field()
Recipe:remove_fields()
------------------------------------------------------------------------------
Recipe()
Recipe:Ingredients() -- wrapper to Products(type)
Recipe:Results() -- wrapper to Products(type)
Recipe:Products(recipe, type, difficulty)
:get(recipe, type, difficulty)
:add()
:remove()
:replace()
:convert()
:clear()
:recipe() -- return recipe object
:make_difficult()
:make_simple()
-- affects top level
:change_category()
.crafting_machine_tint
-- Wrappers to recipe difficulty data (set_field)
-- Maybe override set_field/s()
.ingredients, -- table
.results, -- table
.result, -- string
.result_count, -- number
-- string fields
.main_product,
-- number fields
.energy_required,
.requester_paste_multiplier,
.overload_multiplier,
.emissions_multiplier,
-- bool fields
.enabled,
.hidden,
.hide_from_stats,
.allow_decomposition,
.allow_as_intermediate,
.allow_intermediates,
.always_show_made_in,
.show_amount_in_title,
.always_show_products,
:set_hidden()
:set_enabled()
-- Wrappers to Products()
:clear_ingredients()
:replace_ingredients()
:replace_ingredient()
:add_ingredient()
:remove_ingredient()
:clear_results()
:replace_results()
:replace_result()
:add_result()
:remove_result()
-- Wrappers to Technology()
:add_unlock()
:remove_unlock()

View File

@@ -0,0 +1,208 @@
--- Pipes
-- @module Data.Pipes
local Pipes = {}
local Sprites = require('__stdlib__/stdlib/data/modules/sprites')
--Define pipe connection pipe pictures, not all entities use these. This function needs some work though.
function Pipes.pictures(pictures, shift_north, shift_south, shift_west, shift_east, replacements)
local new_pictures = {
north = shift_north and
{
filename = '__base__/graphics/entity/' .. pictures .. '/' .. pictures .. '-pipe-N.png',
priority = 'extra-high',
width = 35,
height = 18,
shift = shift_north
} or
Sprites.empty_picture(),
south = shift_south and
{
filename = '__base__/graphics/entity/' .. pictures .. '/' .. pictures .. '-pipe-S.png',
priority = 'extra-high',
width = 44,
height = 31,
shift = shift_south
} or
Sprites.empty_picture(),
west = shift_west and
{
filename = '__base__/graphics/entity/' .. pictures .. '/' .. pictures .. '-pipe-W.png',
priority = 'extra-high',
width = 19,
height = 37,
shift = shift_west
} or
Sprites.empty_picture(),
east = shift_east and
{
filename = '__base__/graphics/entity/' .. pictures .. '/' .. pictures .. '-pipe-E.png',
priority = 'extra-high',
width = 20,
height = 38,
shift = shift_east
} or
Sprites.empty_picture()
}
for direction, image in pairs(replacements or {}) do
if not (new_pictures[direction].filename == '__core__/graphics/empty.png') then
new_pictures[direction].filename = image.filename
new_pictures[direction].width = image.width
new_pictures[direction].height = image.height
new_pictures[direction].priority = image.priority or new_pictures[direction].priority
end
end
return new_pictures
end
--return pipe covers for true directions.
function Pipes.covers(n, s, w, e)
if (n == nil and s == nil and w == nil and e == nil) then
n, s, e, w = true, true, true, true
end
n =
n and
{
layers = {
{
filename = '__base__/graphics/entity/pipe-covers/pipe-cover-north.png',
priority = 'extra-high',
width = 64,
height = 64,
hr_version = {
filename = '__base__/graphics/entity/pipe-covers/hr-pipe-cover-north.png',
priority = 'extra-high',
width = 128,
height = 128,
scale = 0.5
}
},
{
filename = '__base__/graphics/entity/pipe-covers/pipe-cover-north-shadow.png',
priority = 'extra-high',
width = 64,
height = 64,
draw_as_shadow = true,
hr_version = {
filename = '__base__/graphics/entity/pipe-covers/hr-pipe-cover-north-shadow.png',
priority = 'extra-high',
width = 128,
height = 128,
scale = 0.5,
draw_as_shadow = true
}
}
}
} or
Sprites.empty_picture()
e =
e and
{
layers = {
{
filename = '__base__/graphics/entity/pipe-covers/pipe-cover-east.png',
priority = 'extra-high',
width = 64,
height = 64,
hr_version = {
filename = '__base__/graphics/entity/pipe-covers/hr-pipe-cover-east.png',
priority = 'extra-high',
width = 128,
height = 128,
scale = 0.5
}
},
{
filename = '__base__/graphics/entity/pipe-covers/pipe-cover-east-shadow.png',
priority = 'extra-high',
width = 64,
height = 64,
draw_as_shadow = true,
hr_version = {
filename = '__base__/graphics/entity/pipe-covers/hr-pipe-cover-east-shadow.png',
priority = 'extra-high',
width = 128,
height = 128,
scale = 0.5,
draw_as_shadow = true
}
}
}
} or
Sprites.empty_picture()
s =
s and
{
layers = {
{
filename = '__base__/graphics/entity/pipe-covers/pipe-cover-south.png',
priority = 'extra-high',
width = 64,
height = 64,
hr_version = {
filename = '__base__/graphics/entity/pipe-covers/hr-pipe-cover-south.png',
priority = 'extra-high',
width = 128,
height = 128,
scale = 0.5
}
},
{
filename = '__base__/graphics/entity/pipe-covers/pipe-cover-south-shadow.png',
priority = 'extra-high',
width = 64,
height = 64,
draw_as_shadow = true,
hr_version = {
filename = '__base__/graphics/entity/pipe-covers/hr-pipe-cover-south-shadow.png',
priority = 'extra-high',
width = 128,
height = 128,
scale = 0.5,
draw_as_shadow = true
}
}
}
} or
Sprites.empty_picture()
w =
w and
{
layers = {
{
filename = '__base__/graphics/entity/pipe-covers/pipe-cover-west.png',
priority = 'extra-high',
width = 64,
height = 64,
hr_version = {
filename = '__base__/graphics/entity/pipe-covers/hr-pipe-cover-west.png',
priority = 'extra-high',
width = 128,
height = 128,
scale = 0.5
}
},
{
filename = '__base__/graphics/entity/pipe-covers/pipe-cover-west-shadow.png',
priority = 'extra-high',
width = 64,
height = 64,
draw_as_shadow = true,
hr_version = {
filename = '__base__/graphics/entity/pipe-covers/hr-pipe-cover-west-shadow.png',
priority = 'extra-high',
width = 128,
height = 128,
scale = 0.5,
draw_as_shadow = true
}
}
}
} or
Sprites.empty_picture()
return { north = n, south = s, east = e, west = w }
end
return Pipes

View File

@@ -0,0 +1,56 @@
type, name
localised_name[opt]
localised_description[opt]
subgroup, order (needed when no main product)
recipe
category
icon/icons[opt] (or has main_product)
crafting_machine_tint = {
primary, secondary, tertiary
}
normal/expensive = {
ingredients
results, result, result_count[opt=1] (result ignored if results present) at least 1 result
main_product
energy_required > 0.001
emissions_multiplier
requester_paste_multiplier
overload_multiplier
enabled <boolean>
hidden <boolean>
hide_from_stats <boolean>
allow_decomposition <boolean>
allow_as_intermediate <boolean>
allow_intermediates <boolean>
always_show_made_in <boolean>
show_amount_in_title <boolean>
always_show_products <boolean>
}
Ingredients
{"name", amount} -- Assumes a type of "item"
{
type :: string: "item" or "fluid".
name :: string: Prototype name of the required item or fluid.
amount :: uint: Amount of the item or fluid.
minimum_temperature :: uint (optional): The minimum fluid temperature required. Has no effect if type is '"item"'.
maximum_temperature :: uint (optional): The maximum fluid temperature allowed. Has no effect if type is '"item"'.
}
Results
{
type :: string: "item" or "fluid".
name :: string: Prototype name of the result.
amount :: float (optional): If not specified, amount_min, amount_max and probability must all be specified.
temperature :: uint (optional): The fluid temperature of this product. Has no effect if type is '"item"'.
amount_min :: uint (optional):
amount_max :: uint (optional):
probability :: double (optional): A value in range [0, 1].
}
{
"name", amount -- assumes a type of item
}
result -- assumes type of item
result_count[opt = 1]

View File

@@ -0,0 +1,83 @@
--- Sprites
-- @module Data.Sprites
local Sprites = {}
function Sprites.extract_monolith(filename, x, y, w, h)
return {
type = 'monolith',
top_monolith_border = 0,
right_monolith_border = 0,
bottom_monolith_border = 0,
left_monolith_border = 0,
monolith_image = {
filename = filename,
priority = 'extra-high-no-scale',
width = w,
height = h,
x = x,
y = y
}
}
end
--- Quick to use empty picture.
-- @treturn table an empty pictures table
function Sprites.empty_picture()
return {
filename = '__core__/graphics/empty.png',
priority = 'extra-high',
width = 1,
height = 1
}
end
Sprites.empty_sprite = Sprites.empty_picture
--- Quick to use empty pictures.
-- @treturn table an empty pictures table
function Sprites.empty_pictures()
local empty = Sprites.empty_picture()
return {
filename = empty.filename,
width = empty.width,
height = empty.height,
line_length = 1,
frame_count = 1,
shift = { 0, 0 },
animation_speed = 1,
direction_count = 1
}
end
Sprites.empty_animation = Sprites.empty_pictures
--- Quick to use empty animation.
-- @treturn table an empty animations table
function Sprites.empty_animations()
return {
Sprites.empty_pictures()
}
end
--- Quick to use empty connections table.
-- @tparam int count how many connection points are needed
-- @treturn table an empty pictures table
function Sprites.empty_connection_points(count)
local points = {}
for i = 1, count or 1, 1 do
points[i] = {
shadow = {
copper = { 0, 0 },
green = { 0, 0 },
red = { 0, 0 }
},
wire = {
copper = { 0, 0 },
green = { 0, 0 },
red = { 0, 0 }
}
}
end
return points
end
return Sprites

View File

@@ -0,0 +1,73 @@
--- Data Utilities
-- @module Data.Util
local Util = {
__class = 'Util',
__index = require('__stdlib__/stdlib/core')
}
setmetatable(Util, Util)
local Is = require('__stdlib__/stdlib/utils/is')
local table = require('__stdlib__/stdlib/utils/table')
function Util.extend(proto_array)
Is.Assert.Table(proto_array, 'Missing table or array to extend')
data:extend(#proto_array > 0 and proto_array or { proto_array })
end
function Util.disable_control(control)
if data.raw['custom-input'] and data.raw['custom-input'][control] then
data.raw['custom-input'][control].enabled = false
end
end
function Util.extend_style(style)
data.raw['gui-style'].default[style.name] = style
end
function Util.extend_style_by_name(name, style)
data.raw['gui-style'].default[name] = style
end
--- Quickly duplicate an existing prototype into a new one.
-- @tparam string data_type The type of the object to duplicate
-- @tparam string orig_name The name of the object to duplicate
-- @tparam string new_name The new name to use.
-- @tparam[opt] string|boolean mining_result If true set mining_result to new_name, if truthy set mining_result to value
function Util.duplicate(data_type, orig_name, new_name, mining_result)
mining_result = type(mining_result) == 'boolean' and new_name or mining_result
if data.raw[data_type] and data.raw[data_type][orig_name] then
local proto = table.deep_copy(data.raw[data_type][orig_name])
proto.name = new_name
if mining_result then
if proto.minable and proto.minable.result then
proto.minable.result = mining_result
end
end
if proto.place_result then
proto.place_result = new_name
end
if proto.result then
proto.result = new_name
end
return (proto)
else
error('Unknown Prototype ' .. data_type .. '/' .. orig_name)
end
end
-- load the data portion of stdlib into globals, by default it loads everything into an ALLCAPS name.
-- Alternatively you can pass a dictionary of `[global names] -> [require path]`.
-- @tparam[opt] table files
-- @treturn Data
-- @usage
-- require('__stdlib__/stdlib/data/data).util.create_data_globals()
function Util.create_data_globals(files)
_ENV.STDLIB.create_stdlib_data_globals(files)
end
return Util

View File

@@ -0,0 +1,427 @@
--- Recipe class
-- @classmod Data.Recipe
local Data = require('__stdlib__/stdlib/data/data')
local Table = require('__stdlib__/stdlib/utils/table')
local Recipe = {
__class = 'Recipe',
__index = Data,
}
function Recipe:__call(recipe)
local new = self:get(recipe, 'recipe')
-- rawset(new, 'Ingredients', {})
-- rawset(new, 'Results', {})
return new
end
setmetatable(Recipe, Recipe)
-- Returns a formated ingredient or prodcut table
local function format(ingredient, result_count)
local Item = require('__stdlib__/stdlib/data/item')
local object
if type(ingredient) == 'table' then
if ingredient.valid and ingredient:is_valid() then
return ingredient
elseif ingredient.name then
if Item(ingredient.name, ingredient.type):is_valid() then
object = Table.deepcopy(ingredient)
if not object.amount and not (object.amount_min and object.amount_max and object.probability) then
error('Result table requires amount or probabilities')
end
end
elseif #ingredient > 0 then
-- Can only be item types not fluid
local item = Item(ingredient[1])
if item:is_valid() and item.type ~= 'fluid' then
object = {
type = 'item',
name = ingredient[1],
amount = ingredient[2] or 1
}
end
end
elseif type(ingredient) == 'string' then
-- Our shortcut so we need to check it
local item = Item(ingredient)
if item:is_valid() then
object = {
type = item.type == 'fluid' and 'fluid' or 'item',
name = ingredient,
amount = result_count or 1
}
end
end
return object
end
-- Format items for difficulties
-- If expensive is a boolean then return a copy of normal for expensive
local function get_difficulties(normal, expensive)
return format(normal), format((expensive == true and Table.deepcopy(normal)) or expensive)
end
--- Remove an ingredient from an ingredients table.
-- @tparam table ingredients
-- @tparam string name Name of the ingredient to remove
local function remove_ingredient(ingredients, name)
name = name.name
for i, ingredient in pairs(ingredients or {}) do
if ingredient[1] == name or ingredient.name == name then
ingredients[i] = nil
return true
end
end
end
--- Replace an ingredient.
-- @tparam table ingredients Ingredients table
-- @tparam string find ingredient to replace
-- @tparam concepts.ingredient replace
-- @tparam boolean replace_name_only Don't replace amounts
local function replace_ingredient(ingredients, find, replace, replace_name_only)
for i, ingredient in pairs(ingredients or {}) do
if ingredient[1] == find or ingredient.name == find then
if replace_name_only then
local amount = ingredient[2] or ingredient.amount
replace.amount = amount
end
ingredients[i] = replace
return true
end
end
end
--- Add an ingredient to a recipe.
-- @tparam string|Concepts.ingredient normal
-- @tparam[opt] string|Concepts.ingredient|boolean expensive
-- @treturn Recipe
function Recipe:add_ingredient(normal, expensive)
if self:is_valid() then
normal, expensive = get_difficulties(normal, expensive)
if self.normal then
if normal then
self.normal.ingredients = self.normal.ingredients or {}
self.normal.ingredients[#self.normal.ingredients + 1] = normal
end
if expensive then
self.expensive.ingredients = self.expensive.ingredients or {}
self.expensive.ingredients[#self.expensive.ingredients + 1] = expensive
end
elseif normal then
self.ingredients = self.ingredients or {}
self.ingredients[#self.ingredients + 1] = normal
end
end
return self
end
Recipe.add_ing = Recipe.add_ingredient
--- Remove one ingredient completely.
-- @tparam string normal
-- @tparam string|boolean expensive expensive recipe to remove, or if true remove normal recipe from both
-- @treturn Recipe
function Recipe:remove_ingredient(normal, expensive)
if self:is_valid() then
normal, expensive = get_difficulties(normal, expensive)
if self.normal then
if normal then
remove_ingredient(self.normal.ingredients, normal)
end
if expensive then
remove_ingredient(self.expensive.ingredients, expensive)
end
elseif normal then
remove_ingredient(self.ingredients, normal)
end
end
return self
end
Recipe.rem_ing = Recipe.remove_ingredient
--- Replace one ingredient with another.
-- @tparam string replace
-- @tparam string|ingredient normal
-- @tparam[opt] string|ingredient|boolean expensive
function Recipe:replace_ingredient(replace, normal, expensive)
assert(replace, 'Missing recipe to replace')
if self:is_valid() then
local n_string = type(normal) == 'string'
local e_string = type(expensive == true and normal or expensive) == 'string'
normal, expensive = get_difficulties(normal, expensive)
if self.normal then
if normal then
replace_ingredient(self.normal.ingredients, replace, normal, n_string)
end
if expensive then
replace_ingredient(self.expensive.ingredients, replace, expensive, e_string)
end
elseif normal then
replace_ingredient(self.ingredients, replace, normal, n_string)
end
end
return self
end
Recipe.rep_ing = Recipe.replace_ingredient
-- Currently does no checking
function Recipe:clear_ingredients()
if self:is_valid() then
if self.normal then
if self.normal.ingredients then
self.normal.ingredients = {}
end
if self.expensive.ingredients then
self.expensive.ingredients = {}
end
elseif self.ingredients then
self.ingredients = {}
end
end
return self
end
--- Converts a recipe to the difficulty recipe format.
-- @tparam[opt] number expensive_energy crafting energy_required for the expensive recipe
-- @treturn self
function Recipe:make_difficult(expensive_energy)
if self:is_valid('recipe') and not self.normal then
--convert all ingredients
local normal, expensive = {}, {}
for _, ingredient in ipairs(self.ingredients) do
local this = format(ingredient)
normal[#normal + 1] = this
expensive[#expensive + 1] = Table.deepcopy(this)
end
local r_normal, r_expensive = {}, {}
for _, ingredient in ipairs(self.results or { self.result }) do
local this = format(ingredient)
r_normal[#r_normal + 1] = this
r_expensive[#r_expensive + 1] = Table.deepcopy(this)
end
self.normal = {
enabled = self.enabled,
energy_required = self.energy_required,
ingredients = normal,
results = r_normal,
main_product = self.main_product
}
self.expensive = {
enabled = self.enabled,
energy_required = expensive_energy or self.energy_required,
ingredients = expensive,
results = r_expensive,
main_product = self.main_product
}
self.ingredients = nil
self.result = nil
self.results = nil
self.result_count = nil
self.energy_required = nil
self.enabled = nil
self.main_product = nil
end
return self
end
--- Change the recipe category.
-- @tparam string category_name The new crafting category
-- @treturn self
function Recipe:change_category(category_name)
if self:is_valid() then
local Category = require('__stdlib__/stdlib/data/category')
self.category = Category(category_name, 'recipe-category'):is_valid() and category_name or self.category
end
return self
end
Recipe.set_category = Recipe.change_category
--- Add to technology as a recipe unlock.
-- @tparam string tech_name Name of the technology to add the unlock too
-- @treturn self
function Recipe:add_unlock(tech_name)
if self:is_valid() then
local Tech = require('__stdlib__/stdlib/data/technology')
Tech.add_effect(self, tech_name) --self is passed as a valid recipe
end
return self
end
--- Remove the recipe unlock from the technology.
-- @tparam string tech_name Name of the technology to remove the unlock from
-- @treturn self
function Recipe:remove_unlock(tech_name)
if self:is_valid('recipe') then
local Tech = require('__stdlib__/stdlib/data/technology')
Tech.remove_effect(self, tech_name, 'unlock-recipe')
end
return self
end
--- Set the enabled status of the recipe.
-- @tparam boolean enabled Enable or disable the recipe
-- @treturn self
function Recipe:set_enabled(enabled)
if self:is_valid() then
if self.normal then
self.normal.enabled = enabled
self.expensive.enabled = enabled
else
self.enabled = enabled
end
end
return self
end
--- Convert result type to results type.
-- @treturn self
function Recipe:convert_results()
if self:is_valid('recipe') then
if self.normal then
if self.normal.result then
self.normal.results = {
format(self.normal.result, self.normal.result_count or 1)
}
self.normal.result = nil
self.normal.result_count = nil
end
if self.expensive.result then
self.expensive.results = {
format(self.expensive.result, self.expensive.result_count or 1)
}
self.expensive.result = nil
self.expensive.result_count = nil
end
elseif self.result then
self.results = {
format(self.result, self.result_count or 1)
}
self.result = nil
self.result_count = nil
end
end
return self
end
--- Set the main product of the recipe.
-- @tparam string|boolean main_product if boolean then use normal/expensive recipes passed as main product
-- @tparam[opt] Concepts.Product|string normal recipe
-- @tparam[opt] Concepts.Product|string expensive recipe
-- @treturn self
function Recipe:set_main_product(main_product, normal, expensive)
if self:is_valid('recipe') then
normal, expensive = get_difficulties(normal, expensive)
local normal_main, expensive_main
if main_product then
local Item = require('__stdlib__/stdlib/data/item')
if type(main_product) == 'string' and Item(main_product):is_valid() then
normal_main = normal and main_product
expensive_main = expensive and main_product
elseif type(main_product) == 'boolean' then
normal_main = normal and Item(normal.name):is_valid() and normal.name
expensive_main = expensive and Item(expensive.name):is_valid() and expensive.name
end
if self.normal then
self.normal.main_product = normal_main
self.expensive.main_product = expensive_main
else
self.main_product = normal_main
end
end
end
return self
end
--- Remove the main product of the recipe.
-- @tparam[opt=false] boolean for_normal
-- @tparam[opt=false] boolean for_expensive
function Recipe:remove_main_product(for_normal, for_expensive)
if self:is_valid('recipe') then
if self.normal then
if for_normal or (for_normal == nil and for_expensive == nil) then
self.normal.main_product = nil
end
if for_expensive or (for_normal == nil and for_expensive == nil) then
self.expensive.main_product = nil
end
elseif for_normal or (for_normal == nil and for_expensive == nil) then
self.main_product = nil
end
end
return self
end
--- Add a new product to results, converts if needed.
-- @tparam string|Concepts.product normal
-- @tparam[opt] string|Concepts.product|boolean expensive
-- @tparam[opt] string main_product
function Recipe:add_result(normal, expensive, main_product)
if self:is_valid() then
normal, expensive = get_difficulties(normal, expensive)
self:convert_results()
self:set_main_product(main_product, normal, expensive)
-- if self.normal then
-- if normal then
-- end
-- if expensive then
-- end
-- elseif normal then
-- end
end
return self
end
--- Remove a product from results, converts if needed.
-- @tparam[opt] string|Concepts.product normal
-- @tparam[opt] string|Concepts.product|boolean expensive
-- @tparam[opt] string main_product new main_product to use
function Recipe:remove_result(normal, expensive, main_product)
if self:is_valid() then
normal, expensive = get_difficulties(normal, expensive)
self:convert_results()
self:set_main_product(main_product, normal, expensive)
-- if self.normal then
-- if normal then
-- end
-- if expensive then
-- end
-- elseif normal then
-- end
end
return self
end
--- Remove a product from results, converts if needed.
-- @tparam string|Concepts.product result_name
-- @tparam[opt] string|Concepts.product normal
-- @tparam[opt] string|Concepts.product|boolean expensive
-- @tparam[opt] string main_product
function Recipe:replace_result(result_name, normal, expensive, main_product)
if self:is_valid() and normal or expensive then
result_name = format(result_name)
if result_name then
normal, expensive = get_difficulties(normal, expensive)
self:convert_results()
self:remove_result(result_name, expensive and result_name)
self:set_main_product(main_product, normal, expensive)
-- if self.normal then
-- if normal then
-- end
-- if expensive then
-- end
-- elseif normal then
-- end
end
end
return self
end
return Recipe

View File

@@ -0,0 +1,160 @@
--- Technology
-- @classmod Data.Technology
local Data = require('__stdlib__/stdlib/data/data')
local Technology = {
__class = 'Technology',
__index = Data,
}
function Technology:__call(tech)
return self:get(tech, 'technology')
end
setmetatable(Technology, Technology)
function Technology:add_effect(effect, unlock_type)
assert(effect)
--todo fix for non recipe types
local add_unlock =
function(technology, name)
local effects = technology.effects
effects[#effects + 1] = {
type = unlock_type,
recipe = name
}
end
if self:is_valid('technology') then
local Recipe = require('__stdlib__/stdlib/data/recipe')
unlock_type = (not unlock_type and 'unlock-recipe') or unlock_type
local r_name = type(effect) == 'table' and effect.name or effect
if unlock_type == 'unlock-recipe' or not unlock_type then
if Recipe(effect):is_valid() then
add_unlock(self, r_name)
end
end
elseif self:is_valid('recipe') then
unlock_type = 'unlock-recipe'
-- Convert to array and return first valid tech
local techs = type(effect) == 'string' and { effect } or effect
for _, name in pairs(techs) do
local tech = Technology(name)
if tech:is_valid('technology') then
self:set_enabled(false)
add_unlock(tech, self.name)
break
end
end
end
return self
end
function Technology:remove_effect(tech_name, unlock_type, name)
if self:is_valid('technology') then
return self, name, unlock_type ---@todo implement
elseif self:is_valid('recipe') then
if tech_name then
local tech = Technology(tech_name)
if tech:is_valid() then
for index, effect in pairs(tech.effects or {}) do
if effect.type == 'unlock-recipe' and effect.recipe == self.name then
table.remove(tech.effects, index)
end
end
end
else
for _, tech in pairs(data.raw['technology']) do
for index, effect in pairs(tech.effects or {}) do
if effect.type == 'unlock-recipe' and effect.recipe == self.name then
table.remove(tech.effects, index)
end
end
end
end
end
return self
end
function Technology:add_pack(new_pack, count)
if self:is_valid('technology') then
local Item = require('__stdlib__/stdlib/data/item')
if type(new_pack) == 'table' then
count = new_pack[2] or 1
new_pack = new_pack[1]
elseif type(new_pack) == 'string' then
count = count or 1
else
error('new_pack must be a table or string')
end
if Item(new_pack):is_valid() then
self.unit.ingredients = self.unit.ingredients or {}
local ing = self.unit.ingredients
ing[#ing + 1] = { new_pack, count }
end
end
return self
end
function Technology:remove_pack(pack)
if self:is_valid('technology') then
local ings = self.unit.ingredients
for i, ing in pairs(ings or {}) do
if ing[1] == pack then
table.remove(ings, i)
break
end
end
end
return self
end
function Technology:replace_pack(old_pack, new_pack, count)
if self:is_valid('technology') then
local ings = self.unit.ingredients
for i, ing in pairs(ings or {}) do
if ing[1] == old_pack then
ing[1] = new_pack
ing[2] = count or ing[2] or 1
break
end
end
end
return self
end
function Technology:add_prereq(tech_name)
if self:is_valid('technology') and Technology(tech_name):is_valid() then
self.prerequisites = self.prerequisites or {}
local pre = self.prerequisites
for _, existing in pairs(pre) do
if existing == tech_name then
return self
end
end
pre[#pre + 1] = tech_name
end
return self
end
function Technology:remove_prereq(tech_name)
if self:is_valid('technology') then
local pre = self.prerequisites or {}
for i = #pre, 1, -1 do
if pre[i] == tech_name then
table.remove(pre, i)
break
end
end
if #pre == 0 then
self.prerequisites = nil
end
end
return self
end
return Technology