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

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