394 lines
11 KiB
Lua
394 lines
11 KiB
Lua
--- Data
|
|
-- @classmod Data
|
|
|
|
if _G.remote and _G.script then
|
|
error('Data Modules can only be required in the data stage', 2)
|
|
end
|
|
|
|
local Data = {
|
|
_class = 'data',
|
|
Sprites = require('stdlib/data/modules/sprites'),
|
|
Pipes = require('stdlib/data/modules/pipes'),
|
|
Util = require('stdlib/data/modules/util'),
|
|
_default_options = {
|
|
['silent'] = false,
|
|
['fail'] = false,
|
|
['verbose'] = false
|
|
}
|
|
}
|
|
setmetatable(Data, {__index = require('stdlib/core')})
|
|
|
|
local Is = require('stdlib/utils/is')
|
|
|
|
local _traceback = function() return '' end
|
|
local traceback = debug and debug.traceback or _traceback
|
|
|
|
local item_and_fluid_types = {
|
|
'item',
|
|
'ammo',
|
|
'armor',
|
|
'gun',
|
|
'capsule',
|
|
'repair-tool',
|
|
'mining-tool',
|
|
'item-with-entity-data',
|
|
'rail-planner',
|
|
'tool',
|
|
'blueprint',
|
|
'deconstruction-item',
|
|
'blueprint-book',
|
|
'selection-tool',
|
|
'item-with-tags',
|
|
'item-with-label',
|
|
'item-with-inventory',
|
|
'module',
|
|
'fluid'
|
|
}
|
|
|
|
-- 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/data/data).create_data_globals()
|
|
function Data.create_data_globals(files)
|
|
files =
|
|
files or
|
|
{
|
|
RECIPE = 'stdlib/data/recipe',
|
|
ITEM = 'stdlib/data/item',
|
|
FLUID = 'stdlib/data/fluid',
|
|
ENTITY = 'stdlib/data/entity',
|
|
TECHNOLOGY = 'stdlib/data/technology',
|
|
CATEGORY = 'stdlib/data/category',
|
|
DATA = 'stdlib/data/data'
|
|
}
|
|
Data.create_stdlib_globals(files)
|
|
|
|
return Data
|
|
end
|
|
|
|
--[Classes]--------------------------------------------------------------------
|
|
|
|
--- Is this a valid object
|
|
-- @tparam[opt] string class if present is the object a member of the class
|
|
-- @treturn self
|
|
function Data:valid(class)
|
|
if class then
|
|
return self._valid == class or false
|
|
else
|
|
return self._valid and true or false
|
|
end
|
|
end
|
|
|
|
--- Invalidates the object.
|
|
-- @tparam boolean should_continue if false then invalidate the object
|
|
-- @treturn self
|
|
function Data:continue(should_continue)
|
|
if not should_continue then
|
|
self._valid = false
|
|
end
|
|
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 and ((self.name and self.type) or self:valid()) 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
|
|
end
|
|
else
|
|
error('Could not extend data', 2)
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- Copies a recipe to a new recipe.
|
|
-- @tparam string new_name The new name for the recipe.
|
|
-- @tparam string mining_result
|
|
-- @treturn self
|
|
function Data:copy(new_name, mining_result)
|
|
Is.Assert.String(new_name, 'New name is required')
|
|
if self:valid() then
|
|
mining_result = mining_result or new_name
|
|
--local from = self.name
|
|
local copy = table.deepcopy(self)
|
|
copy.name = new_name
|
|
|
|
-- For Entities
|
|
-- Need to also check mining_results!!!!!!
|
|
if mining_result then
|
|
if copy.minable and copy.minable.result then
|
|
copy.minable.result = mining_result
|
|
end
|
|
end
|
|
|
|
-- For items
|
|
if copy.place_result then
|
|
copy.place_result = new_name
|
|
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):extend()
|
|
else
|
|
error('Cannot Copy, invalid prototype', 4)
|
|
end
|
|
end
|
|
|
|
function Data:Flags(has_flag_string)
|
|
if self:valid() then
|
|
if self.flags then
|
|
setmetatable(self.flags, Data._classes.string_array_mt)
|
|
if has_flag_string then
|
|
return self.flags:has(has_flag_string)
|
|
end
|
|
return self.flags
|
|
end
|
|
end
|
|
return self
|
|
end
|
|
|
|
function Data:add_flag(flag)
|
|
self:Flags():add(flag)
|
|
return self
|
|
end
|
|
|
|
function Data:remove_flag(flag)
|
|
self:Flags():remove(flag)
|
|
return self
|
|
end
|
|
|
|
function Data:has_flag(flag)
|
|
return self:Flags(flag)
|
|
end
|
|
|
|
--- 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:execute(func, ...)
|
|
if self:valid() then
|
|
func(self, ...)
|
|
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)
|
|
if self:valid() then
|
|
rawset(self, field, value)
|
|
end
|
|
return self
|
|
end
|
|
Data.set = Data.set_field
|
|
|
|
--- Get a field.
|
|
-- @tparam field
|
|
-- @treturn mixed the value of the field
|
|
-- @note Will error if the object is not valid
|
|
function Data:get_field(field)
|
|
if self:valid() then
|
|
return rawget(self, field)
|
|
end
|
|
end
|
|
|
|
--- 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:valid() then
|
|
for k, v in pairs(tab) do
|
|
rawset(self, k, v)
|
|
end
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- Iterate a string array and set to nil.
|
|
-- @tparam table tab string array of fields to remove.
|
|
-- @treturn self
|
|
function Data:remove_fields(tab)
|
|
if self:valid() then
|
|
for _, k in pairs(tab) do
|
|
rawset(self, k, 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: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: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:valid() then
|
|
return copy and table.deepcopy(self.icons) or self.icons
|
|
end
|
|
end
|
|
|
|
function Data:get_icon()
|
|
if self:valid() then
|
|
return self.icon
|
|
end
|
|
end
|
|
|
|
function Data:make_icons(...)
|
|
if self: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.deepcopy(icon)
|
|
end
|
|
end
|
|
return self
|
|
end
|
|
|
|
function Data:set_icon_at(index, values)
|
|
if self: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.name and self.type and self.name or ''
|
|
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)
|
|
Is.Assert(object, 'object string or table is required')
|
|
|
|
local new
|
|
if type(object) == 'table' then
|
|
Is.Assert(object.type and object.name, 'Name and Type are required')
|
|
new = object
|
|
new._extended = data.raw[object.type] and data.raw[object.type][object.name] == object
|
|
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 item_and_fluid_types)
|
|
if types then
|
|
for _, type in pairs(types) do
|
|
new = data.raw[type] and data.raw[type][object]
|
|
if new then
|
|
new._extended = true
|
|
break
|
|
end
|
|
end
|
|
else
|
|
error('object_type is missing for ' .. (self._class or 'Unknown') .. '/' .. (object or ''), 3)
|
|
end
|
|
end
|
|
|
|
if new then
|
|
new._valid = self._class or 'data'
|
|
new._opt = opts
|
|
new.flags = new.flags and setmetatable(new.flags, Data._classes.string_array_mt)
|
|
return setmetatable(new, self._mt):extend()
|
|
else
|
|
local trace = traceback()
|
|
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.'
|
|
|
|
trace = trace:gsub('stack traceback:\n', ''):gsub('.*%(%.%.%.tail calls%.%.%.%)\n', ''):gsub(' in main chunk.*$', '')
|
|
trace = trace:gsub('%_%_.*%_%_%/stdlib/data.*\n', ''):gsub('\n', '->'):gsub('\t', '')
|
|
trace = msg .. ' [' .. trace .. ']'
|
|
log(trace)
|
|
end
|
|
return self
|
|
end
|
|
Data:set_caller(Data.get)
|
|
|
|
Data._mt = {
|
|
__index = Data,
|
|
__call = Data.get,
|
|
__tostring = Data.tostring
|
|
}
|
|
|
|
return Data
|
|
|
|
-- 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"
|