---@class FPModuleSet ---@field modules FPCollection ---@field module_count integer ---@field module_limit integer ---@field empty_slots integer ---@field total_effects ModuleEffects ---@field valid boolean ---@field parent FPMachine | FPBeacon ---@field class "ModuleSet" -- 'Class' representing a group of modules in a machine or beacon ModuleSet = {} function ModuleSet.init(parent) return { modules = Collection.init(), module_count = 0, module_limit = parent.proto.module_limit, empty_slots = parent.proto.module_limit, total_effects = nil, -- summarized by calling function valid = true, parent = parent, class = "ModuleSet" } end function ModuleSet.add(self, proto, amount) local object = Module.init(proto, amount, self) local dataset = Collection.add(self.modules, object) ModuleSet.count_modules(self) return dataset end function ModuleSet.remove(self, dataset) Collection.remove(self.modules, dataset) ModuleSet.count_modules(self) end function ModuleSet.replace(self, dataset, object) object.parent = self local replacement = Collection.replace(self.modules, dataset, object) ModuleSet.count_modules(self) return replacement end function ModuleSet.clear(self) self.modules = Collection.init() ModuleSet.count_modules(self) end function ModuleSet.get(self, dataset_id) return Collection.get(self.modules, dataset_id) end function ModuleSet.get_by_name(self, name) return Collection.get_by_name(self.modules, name) end function ModuleSet.get_all(self) return Collection.get_all(self.modules) end function ModuleSet.get_in_order(self, reverse) return Collection.get_in_order(self.modules, reverse) end function ModuleSet.get_module_kind_amount(self) return self.modules.count end function ModuleSet.normalize(self, features) self.module_limit = self.parent.proto.module_limit if features.compatibility then ModuleSet.verify_compatibility(self) end if features.trim then ModuleSet.trim(self) end if features.sort then ModuleSet.sort(self) end if features.effects then ModuleSet.summarize_effects(self) end ModuleSet.count_modules(self) end function ModuleSet.count_modules(self) local count = 0 for _, module in pairs(self.modules.datasets) do count = count + module.amount end self.module_count = count self.empty_slots = self.module_limit - self.module_count end function ModuleSet.verify_compatibility(self) local modules_to_remove = {} for _, module in ipairs(ModuleSet.get_in_order(self)) do if not ModuleSet.check_compatibility(self, module.proto) then table.insert(modules_to_remove, module) end end -- Actually remove incompatible modules; counts updated by calling function for _, module in pairs(modules_to_remove) do ModuleSet.remove(self, module) end end function ModuleSet.trim(self) local module_count, module_limit = self.module_count, self.module_limit -- Return if the module count is within limits if module_count <= module_limit then return end local modules_to_remove = {} -- Traverse modules in reverse to trim them off the end for _, module in ipairs(ModuleSet.get_in_order(self, true)) do -- Remove a whole module if it brings the count to >= limit if (module_count - module.amount) >= module_limit then table.insert(modules_to_remove, module) module_count = module_count - module.amount else -- Otherwise, diminish the amount on the module appropriately and break local new_amount = module.amount - (module_count - module_limit) Module.set_amount(module, new_amount) break end end -- Actually remove superfluous modules; counts updated by calling function for _, module in pairs(modules_to_remove) do ModuleSet.remove(self, module) end end -- Sorts modules in a deterministic fashion so they are in the same order for every line function ModuleSet.sort(self) local modules_by_name = {} for _, module in pairs(ModuleSet.get_all(self)) do modules_by_name[module.proto.name] = module end local next_position = 1 for _, category in ipairs(global.prototypes.modules) do for _, module_proto in ipairs(category.members) do local module = modules_by_name[module_proto.name] if module then module.gui_position = next_position next_position = next_position + 1 end end end end function ModuleSet.summarize_effects(self) local effects = {consumption = 0, speed = 0, productivity = 0, pollution = 0} for _, module in pairs(self.modules.datasets) do for name, effect in pairs(module.total_effects) do effects[name] = effects[name] + effect end end self.total_effects = effects _G[self.parent.class].summarize_effects(self.parent) end function ModuleSet.check_compatibility(self, module_proto) return _G[self.parent.class].check_module_compatibility(self.parent, module_proto) end function ModuleSet.compile_filter(self) local compatible_modules = {} for module_name, module_proto in pairs(MODULE_NAME_MAP) do if ModuleSet.check_compatibility(self, module_proto) then table.insert(compatible_modules, module_name) end end local existing_modules = {} for _, module in pairs(self.modules.datasets) do table.insert(existing_modules, module.proto.name) end return {{filter="name", name=compatible_modules}, {filter="name", mode="and", invert=true, name=existing_modules}} end function ModuleSet.paste(self, module) if not ModuleSet.check_compatibility(self, module.proto) then return false, "incompatible" elseif self.empty_slots == 0 then return false, "no_empty_slots" end local desired_amount = math.min(module.amount, self.empty_slots) local existing_module = ModuleSet.get_by_name(self, module.proto.name) if existing_module then Module.set_amount(existing_module, existing_module.amount + desired_amount) else ModuleSet.add(self, module.proto, desired_amount) end ModuleSet.normalize(self, {sort=true, effects=true}) return true, nil end function ModuleSet.pack(self) return { modules = Collection.pack(self.modules, Module), -- count, limit, and empty_slots restored by ensuing validation class = self.class } end function ModuleSet.unpack(packed_self) local self = packed_self self.modules = Collection.unpack(packed_self.modules, self, Module) return self end -- Needs validation: modules function ModuleSet.validate(self) self.valid = Collection.validate_datasets(self.modules, Module) if self.valid and self.parent.valid then if not self.module_count or not self.empty_slots then -- when validating an unpacked ModuleSet self.module_limit = self.parent.proto.module_limit ModuleSet.count_modules(self) end -- .normalize doesn't remove incompatible modules here, the above validation already marks them ModuleSet.normalize(self, {trim=true, sort=true, effects=true}) end return self.valid end -- Needs repair: modules function ModuleSet.repair(self, _) Collection.repair_datasets(self.modules, nil, Module) ModuleSet.normalize(self, {trim=true, sort=true, effects=true}) self.valid = true -- repairing invalid modules removes them, making this set valid return true end