Aleksei-bird 7c9c708c92 Первый фикс
Пачки некоторых позиций увеличены
2024-03-01 20:54:33 +03:00

523 lines
15 KiB
Lua

--- 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