158 lines
6.5 KiB
Lua
158 lines
6.5 KiB
Lua
---@class RequiredAmount
|
|
---@field defined_by "amount" | "belts" | "lanes"
|
|
---@field amount number
|
|
---@field belt_proto FPBeltPrototype
|
|
|
|
---@class FPItem
|
|
---@field proto FPItemPrototype
|
|
---@field amount number
|
|
---@field required_amount RequiredAmount
|
|
---@field satisfied_amount number
|
|
---@field top_level boolean
|
|
---@field valid boolean
|
|
---@field id integer
|
|
---@field gui_position integer
|
|
---@field parent FPSubfactory | FPLine
|
|
---@field class "Product" | "Byproduct" | "Ingredient"
|
|
|
|
-- 'Class' representing an item in the general sense
|
|
Item = {}
|
|
Product, Byproduct, Ingredient = Item, Item, Item -- allows _G[class] to work for all items
|
|
|
|
-- Initialised by passing a prototype from the all_items global table
|
|
-- This is set up as a top-level item if a required_amount is given
|
|
function Item.init(proto, class, amount, required_amount)
|
|
-- Special case for non-product top level items
|
|
if required_amount == 0 then required_amount = {defined_by="amount", amount=0} end
|
|
|
|
return {
|
|
proto = proto,
|
|
amount = amount or 0, -- produced amount
|
|
required_amount = required_amount, -- is a table
|
|
satisfied_amount = 0, -- used with ingredient satisfaction
|
|
top_level = (required_amount ~= nil),
|
|
valid = true,
|
|
id = nil, -- set by collection
|
|
gui_position = nil, -- set by collection
|
|
parent = nil, -- set by parent
|
|
class = class
|
|
}
|
|
end
|
|
|
|
|
|
-- Returns the converted numeric required_amount for this (top level) item
|
|
function Item.required_amount(self)
|
|
local req_amount = self.required_amount
|
|
if req_amount.defined_by == "amount" then
|
|
return req_amount.amount
|
|
else -- defined_by == "belts"/"lanes"
|
|
-- If this is defined by lanes, only half of the throughput of a full belt needs to be considered
|
|
local multiplier = (req_amount.defined_by == "belts") and 1 or 0.5
|
|
local timescale = self.parent.timescale
|
|
return req_amount.amount * (req_amount.belt_proto.throughput * multiplier) * timescale
|
|
end
|
|
end
|
|
|
|
|
|
function Item.paste(self, object) -- this is implicitly only called on top level items
|
|
if object.class == "Product" or object.class == "Byproduct"
|
|
or object.class == "Ingredient" or object.class == "Fuel" then
|
|
local existing_item = Subfactory.get_by_name(self.parent, self.class, object.proto.name)
|
|
-- Avoid duplicate items, but allow pasting over the same item proto
|
|
if existing_item and existing_item.proto.name == object.proto.name
|
|
and not (self.proto.name == object.proto.name) then
|
|
return false, "already_exists"
|
|
end
|
|
|
|
-- Convert object into the appropriate top-level form if necessary
|
|
if not (object.top_level and object.class == self.class) then
|
|
local required_amount = {defined_by = "amount", amount = object.amount}
|
|
object = Item.init(object.proto, self.class, 0, required_amount)
|
|
end
|
|
|
|
-- Detect when this is called on a fake item and add instead of replacing
|
|
if not self.amount then Subfactory.add(self.parent, object)
|
|
else Subfactory.replace(self.parent, self, object) end
|
|
return true, nil
|
|
|
|
elseif object.class == "Line" then
|
|
local relevant_line = (object.subfloor) and object.subfloor.defining_line or object
|
|
for _, product in pairs(Line.get_in_order(relevant_line, "Product")) do
|
|
local fake_item = {proto={name=""}, parent=self.parent, class=self.class}
|
|
Item.paste(fake_item, product) -- avoid duplicating existing items
|
|
end
|
|
|
|
local top_floor = Subfactory.get(self.parent, "Floor", 1) -- line count can be 0
|
|
if object.subfloor then -- if the line has a subfloor, paste its contents on the top floor
|
|
local fake_line = {parent=top_floor, class="Line", gui_position=top_floor.Line.count}
|
|
for _, line in pairs(Floor.get_in_order(object.subfloor, "Line")) do
|
|
Line.paste(fake_line, line)
|
|
fake_line.gui_position = fake_line.gui_position + 1
|
|
end
|
|
else -- if the line has no subfloor, just straight paste it onto the top floor
|
|
local fake_line = {parent=top_floor, class="Line", gui_position=top_floor.Line.count}
|
|
Line.paste(fake_line, object)
|
|
end
|
|
return true, nil
|
|
else
|
|
return false, "incompatible_class"
|
|
end
|
|
end
|
|
|
|
|
|
function Item.pack(self)
|
|
return {
|
|
proto = prototyper.util.simplify_prototype(self.proto, self.proto.type),
|
|
amount = self.amount, -- conserve for cloning non-product items
|
|
required_amount = (self.top_level) and {
|
|
defined_by = self.required_amount.defined_by,
|
|
amount = self.required_amount.amount,
|
|
belt_proto = (self.required_amount.defined_by ~= "amount")
|
|
and prototyper.util.simplify_prototype(self.required_amount.belt_proto, nil)
|
|
} or nil,
|
|
top_level = self.top_level,
|
|
class = self.class
|
|
}
|
|
end
|
|
|
|
function Item.unpack(packed_self)
|
|
return packed_self
|
|
end
|
|
|
|
|
|
-- Needs validation: proto, required_amount
|
|
function Item.validate(self)
|
|
self.proto = prototyper.util.validate_prototype_object(self.proto, "type")
|
|
self.valid = (not self.proto.simplified)
|
|
|
|
-- Validate the belt_proto if the item proto is still valid, ie not simplified
|
|
local req_amount = self.required_amount
|
|
if req_amount and req_amount.defined_by ~= "amount" then
|
|
local belt_throughput = req_amount.belt_proto.throughput
|
|
req_amount.belt_proto = prototyper.util.validate_prototype_object(req_amount.belt_proto, nil)
|
|
self.valid = (not req_amount.belt_proto.simplified) and self.valid
|
|
|
|
-- If the proto has to be simplified, conserve the throughput, so repair can convert it to an amount-spec
|
|
if req_amount.belt_proto.simplified then req_amount.belt_proto.throughput = belt_throughput end
|
|
end
|
|
|
|
return self.valid
|
|
end
|
|
|
|
-- Needs repair: required_amount
|
|
-- This will only be called on top level items, so they can be treated as such
|
|
function Item.repair(self, _)
|
|
-- If the item-proto is still simplified, validate couldn't repair it, so it has to be removed
|
|
if self.proto.simplified then return false end
|
|
|
|
-- If the item is fine, the belt_proto has to be the things that is invalid. Thus, we will repair
|
|
-- this item by converting it to be defined by amount, so it can be preserved in some form
|
|
self.required_amount = {
|
|
defined_by = "amount",
|
|
amount = Item.required_amount(self)
|
|
}
|
|
self.valid = true
|
|
|
|
return self.valid
|
|
end
|