diff --git a/betterContracts.lua b/betterContracts.lua index b3b33bd..ffa59d0 100644 --- a/betterContracts.lua +++ b/betterContracts.lua @@ -20,9 +20,10 @@ -- v1.2.4.0 26.08.2022 allow for other (future) mission types, -- fix distorted menu page for different screen aspect ratios, -- show fruit type to harvest in contracts list +-- v.1.2.4.1 05.09.2022 indicate leased equipment for active missions +-- allow clear/new contracts button only for master user +-- lazyNPC / maxActive contracts now configurable --======================================================================================================= -InitRoyalUtility(Utils.getFilename("lib/utility/", g_currentModDirectory)) -InitRoyalMod(Utils.getFilename("lib/rmod/", g_currentModDirectory)) SC = { FERTILIZER = 1, -- prices index LIQUIDFERT = 2, @@ -35,7 +36,7 @@ SC = { BALING = 4, TRANSP = 5, SUPPLY = 6, - OTHER = 7, + OTHER = 7, -- Gui controls: CONTROLS = { npcbox = "npcbox", @@ -64,23 +65,21 @@ SC = { helpsort = "helpsort" } } ----@class BetterContracts : RoyalMod -BetterContracts = RoyalMod.new(false, false) --params bool debug, bool sync - function debugPrint(text, ...) if BetterContracts.debug then Logging.info(text,...) end end +source(Utils.getFilename("RoyalMod.lua", g_currentModDirectory.."scripts/")) -- RoyalMod support functions +source(Utils.getFilename("Utility.lua", g_currentModDirectory.."scripts/")) -- RoyalMod utility functions +source(Utils.getFilename("TableUtility.lua", g_currentModDirectory.."scripts/"))-- RoyalMod table utility functions +---@class BetterContracts : RoyalMod +BetterContracts = RoyalMod.new(false, true) --params bool debug, bool sync + function BetterContracts:initialize() debugPrint("[%s] initialize(): %s", self.name,self.initialized) if self.initialized ~= nil then return end -- run only once - -- check for debug switch in modSettings/ self.modSettings= getUserProfileAppPath().."modSettings/" - if not self.debug and - fileExists(self.modSettings.."BetterContracts.debug") then - self.debug = true - end g_missionManager.missionMapNumChannels = 6 self.missionUpdTimeout = 15000 self.missionUpdTimer = 0 -- will also update on frame open of contracts page @@ -105,32 +104,32 @@ function BetterContracts:initialize() ]] -- mission.type to BC category: harvest, spread, simple, mow, transp, supply -- self.typeToCat = {4, 3, 3, 2, 1, 3, 2, 2, 5, 6} - self.typeToCat = {} - local function addMapping(name, category) - local missionType = g_missionManager:getMissionType(name) - if missionType ~= nil then - self.typeToCat[missionType.typeId] = category - end - end - addMapping("mow_bale", SC.BALING) - addMapping("plow", SC.SIMPLE) - addMapping("cultivate", SC.SIMPLE) - addMapping("sow", SC.SPREAD) - addMapping("harvest", SC.HARVEST) - addMapping("weed", SC.SIMPLE) - addMapping("spray", SC.SPREAD) - addMapping("fertilize", SC.SPREAD) - addMapping("transport", SC.TRANSP) - addMapping("supplyTransport", SC.SUPPLY) - addMapping("deadwood", SC.OTHER) - addMapping("treeTransport", SC.OTHER) + self.typeToCat = {} + local function addMapping(name, category) + local missionType = g_missionManager:getMissionType(name) + if missionType ~= nil then + self.typeToCat[missionType.typeId] = category + end + end + addMapping("mow_bale", SC.BALING) + addMapping("plow", SC.SIMPLE) + addMapping("cultivate", SC.SIMPLE) + addMapping("sow", SC.SPREAD) + addMapping("harvest", SC.HARVEST) + addMapping("weed", SC.SIMPLE) + addMapping("spray", SC.SPREAD) + addMapping("fertilize", SC.SPREAD) + addMapping("transport", SC.TRANSP) + addMapping("supplyTransport", SC.SUPPLY) + addMapping("deadwood", SC.OTHER) + addMapping("treeTransport", SC.OTHER) self.harvest = {} -- harvest missions 1 self.spread = {} -- sow, spray, fertilize 2 self.simple = {} -- plow, cultivate, weed 3 self.mow_bale = {} -- mow/ bale 4 self.transp = {} -- transport 5 self.supply = {} -- supplyTransport mod 6 - self.other = {} -- deadwood, treeTrans 7 + self.other = {} -- deadwood, treeTrans 7 self.IdToCont = {} -- to find a contract from its mission id self.fieldToMission = {} -- to find a contract from its field number self.catHarvest = "BEETHARVESTING BEETVEHICLES CORNHEADERS COTTONVEHICLES CUTTERS POTATOHARVESTING POTATOVEHICLES SUGARCANEHARVESTING SUGARCANEVEHICLES" @@ -147,6 +146,27 @@ function BetterContracts:initialize() {"sortprof", g_i18n:getText("SC_sortProf")}, {"sortpmin", g_i18n:getText("SC_sortpMin")} } + self.npcProb = { + harvest = 1.0, + plowCultivate = 0.3, + sow = 0.3, + fertilize = 0.3 + } + self.npcType = {} + self.lazyNPC = false -- adjust NPC field work activity + self.maxActive = 3 -- max active contracts + + if g_server ~= nil then + readconfig(self) + debugPrint("%s read config: maxActive %d, lazyNPC %s",self.name, self.maxActive, self.lazyNPC) + -- to allow multiple missions: + if self.maxActive > 0 then + MissionManager.ACTIVE_CONTRACT_LIMIT = self.maxActive + else -- allow unlimited active missions + MissionManager.hasFarmReachedMissionLimit = + Utils.overwrittenFunction(nil, function() return false end) + end + end local mods = {"FS22_RefreshContracts","FS22_Contracts_Plus"} if g_modIsLoaded["FS22_RefreshContracts"] then self.needsRefreshContractsConflictsPrevention = true @@ -157,14 +177,16 @@ function BetterContracts:initialize() if g_modIsLoaded["FS22_SupplyTransportContracts"] then self.supplyTransport = true end + -- to load own mission vehicles: Utility.overwrittenFunction(MissionManager, "loadMissionVehicles", BetterContracts.loadMissionVehicles) -- fix AbstractMission: Utility.overwrittenFunction(AbstractMission, "new", abstractMissionNew) - -- fix Harvest NPC Mission: - Utility.overwrittenFunction(FieldManager, "updateNPCField", NPCHarvest) - + -- adjust NPC activity for missions: + if self.lazyNPC then -- always false on an MP client + Utility.overwrittenFunction(FieldManager, "updateNPCField", NPCHarvest) + end -- get addtnl mission values from server: Utility.appendedFunction(HarvestMission, "writeStream", BetterContracts.writeStream) Utility.appendedFunction(HarvestMission, "readStream", BetterContracts.readStream) @@ -173,17 +195,14 @@ function BetterContracts:initialize() Utility.appendedFunction(AbstractMission, "writeUpdateStream", BetterContracts.writeUpdateStream) Utility.appendedFunction(AbstractMission, "readUpdateStream", BetterContracts.readUpdateStream) -- functions for ingame menu contracts frame: - InGameMenuContractsFrame.onFrameOpen = Utils.overwrittenFunction(InGameMenuContractsFrame.onFrameOpen, onFrameOpen) - InGameMenuContractsFrame.onFrameClose = Utils.appendedFunction(InGameMenuContractsFrame.onFrameClose, onFrameClose) - InGameMenuContractsFrame.updateFarmersBox = Utils.appendedFunction(InGameMenuContractsFrame.updateFarmersBox, updateFarmersBox) - InGameMenuContractsFrame.populateCellForItemInSection = Utils.appendedFunction(InGameMenuContractsFrame.populateCellForItemInSection, populateCell) - InGameMenuContractsFrame.updateList = Utils.overwrittenFunction(InGameMenuContractsFrame.updateList, updateList) - InGameMenuContractsFrame.sortList = Utils.overwrittenFunction(InGameMenuContractsFrame.sortList, sortList) - -- to allow multiple missions: - MissionManager.hasFarmReachedMissionLimit = - Utils.overwrittenFunction(nil, - function() return false end - ) + Utility.overwrittenFunction(InGameMenuContractsFrame, "onFrameOpen", onFrameOpen) + Utility.appendedFunction(InGameMenuContractsFrame, "onFrameClose", onFrameClose) + Utility.appendedFunction(InGameMenuContractsFrame, "updateFarmersBox", updateFarmersBox) + Utility.appendedFunction(InGameMenuContractsFrame, "populateCellForItemInSection", populateCell) + Utility.overwrittenFunction(InGameMenuContractsFrame, "updateList", updateList) + Utility.overwrittenFunction(InGameMenuContractsFrame, "sortList", sortList) + Utility.appendedFunction(InGameMenuContractsFrame, "startContract", startContract) + Utility.appendedFunction(InGameMenu, "updateButtonsPanel", updateButtonsPanel) if self.debug then addConsoleCommand("printBetterContracts", "Print detail stats for all available missions.", "consoleCommandPrint", self) addConsoleCommand("gsFieldGenerateMission", "Force generating a new mission for given field", "consoleGenerateFieldMission", g_missionManager) @@ -193,6 +212,31 @@ function BetterContracts:initialize() end end +function readconfig(self) + -- check for config file in modSettings/ + self.configFile = self.modSettings .. self.name..'.xml' + if not fileExists(self.configFile) then -- copy initial config file to /modSettings + copyFile(self.directory.."config.xml", self.configFile, true) + Logging.info("[%s] wrote initial config file %s", self.name, self.configFile) + end + -- read config parms: + local xmlFile = loadXMLFile("conf", self.configFile) + local key = self.name + if not self.debug then + self.debug = Utils.getNoNil(getXMLBool(xmlFile, key.."#debug"), false) + end + self.maxActive = Utils.getNoNil(getXMLInt(xmlFile, key.."#maxActive"), 3) + self.lazyNPC = Utils.getNoNil(getXMLBool(xmlFile, key.."#lazyNPC"), false) + if self.lazyNPC then + key = key..".lazyNPC" + self.npcType.harvest = Utils.getNoNil(getXMLBool(xmlFile, key.."#harvest"), false) + self.npcType.plowCultivate =Utils.getNoNil(getXMLBool(xmlFile, key.."#plowCultivate"), false) + self.npcType.sow = Utils.getNoNil(getXMLBool(xmlFile, key.."#sow"), false) + self.npcType.fertilize = Utils.getNoNil(getXMLBool(xmlFile, key.."#fertilize"), false) + end + delete(xmlFile) +end + function BetterContracts:onMissionInitialize(baseDirectory, missionCollaborators) MissionManager.AI_PRICE_MULTIPLIER = 1.5 MissionManager.MISSION_GENERATION_INTERVAL = 3600000 -- every 1 game hour @@ -265,7 +309,7 @@ function BetterContracts:onPostLoadMap(mapNode, mapFile) local rewd = self.frCon.contractsList.cellDatabase.autoCell1:getDescendantByName("reward") local profit = rewd:clone(self.frCon.contractsList.cellDatabase.autoCell1) profit.name = "profit" - profit:setPosition(-110/1920, -12/1080 *g_aspectScaleY) -- + profit:setPosition(-110/1920 *g_aspectScaleX, -12/1080 *g_aspectScaleY) -- --profit:setTextColor(1, 1, 1, 1) profit.textBold = false profit:setVisible(false) @@ -335,6 +379,26 @@ function BetterContracts:onPostLoadMap(mapNode, mapFile) self.initialized = true end +function BetterContracts:onWriteStream(streamId) + -- write to a client when it joins + local num = g_missionManager.numTransportTriggers + debugPrint("** writing maxActive %d ", self.maxActive) + streamWriteUInt8(streamId, self.maxActive) + streamWriteBool(streamId, self.debug) +end +function BetterContracts:onReadStream(streamId) + -- client reads our config when it joins + self.maxActive = streamReadUInt8(streamId) + self.debug = streamReadBool(streamId) + debugPrint("** read maxActive %d, debug %s", self.maxActive, self.debug) + -- to allow multiple missions: + if self.maxActive > 0 then + MissionManager.ACTIVE_CONTRACT_LIMIT = self.maxActive + else -- allow unlimited active missions + MissionManager.hasFarmReachedMissionLimit = + Utils.overwrittenFunction(nil, function() return false end) + end +end function BetterContracts:onUpdate(dt) local self = BetterContracts self.missionUpdTimer = self.missionUpdTimer + dt @@ -482,18 +546,18 @@ function BetterContracts:addMission(m) reward = rew } table.insert(self.transp, cont) - elseif cat == SC.OTHER then - cont = { - miss = m, - worktime = 0, - profit = rew, - permin = 0, - reward = rew - } - table.insert(self.other, cont) + elseif cat == SC.OTHER then + cont = { + miss = m, + worktime = 0, + profit = rew, + permin = 0, + reward = rew + } + table.insert(self.other, cont) else Logging.warning("[%s]: Unknown cat %s in addMission(m)", self.name, cat) - end + end return {cat, cont} end function getPalletType(m) @@ -514,6 +578,9 @@ function BetterContracts.readStream(self, streamId, connection) self.depositedLiters = streamReadFloat32(streamId) end function BetterContracts.writeUpdateStream(self, streamId, connection, dirtyMask) + if self.status == AbstractMission.STATUS_RUNNING then + streamWriteBool(streamId, self.spawnedVehicles) + end local fieldPercent, depo = 0., 0. if self.fieldPercentageDone then fieldPercent = self.fieldPercentageDone end if self.depositedLiters then depo = self.depositedLiters end @@ -521,6 +588,9 @@ function BetterContracts.writeUpdateStream(self, streamId, connection, dirtyMask streamWriteFloat32(streamId, depo) end function BetterContracts.readUpdateStream(self, streamId, timestamp, connection) + if self.status == AbstractMission.STATUS_RUNNING then + self.spawnedVehicles = streamReadBool(streamId) + end self.fieldPercentageDone = streamReadFloat32(streamId) self.depositedLiters = streamReadFloat32(streamId) end @@ -535,32 +605,65 @@ function NPCHarvest(self, superf, field, allowUpdates) superf(self, field, allowUpdates) return end + local npc = BetterContracts.npcType + local prob= BetterContracts.npcProb local fruitDesc, harvestReadyState, maxHarvestState, area, total local x, z = FieldUtil.getMeasurementPositionOfField(field) if field.fruitType ~= nil then - -- leave a withered field for plow/ grubber missions fruitDesc = g_fruitTypeManager:getFruitTypeByIndex(field.fruitType) + -- leave a withered field for plow/ grubber missions + --[[ local withered = fruitDesc.witheredState if withered ~= nil then area, total = FieldUtil.getFruitArea(x - 1, z - 1, x + 1, z - 1, x - 1, z + 1, FieldUtil.FILTER_EMPTY, FieldUtil.FILTER_EMPTY, field.fruitType, withered, withered, 0, 0, 0, false) - if area > 0.5*total and math.random() < 0.5 then return end + if area > 0.5*total and math.random() < 0.3 then return end end - - -- don't let NPCs harvest - harvestReadyState = fruitDesc.maxHarvestingGrowthState - if fruitDesc.maxPreparingGrowthState > -1 then - harvestReadyState = fruitDesc.maxPreparingGrowthState + ]] + if npc.harvest then + -- don't let NPCs harvest + harvestReadyState = fruitDesc.maxHarvestingGrowthState + if fruitDesc.maxPreparingGrowthState > -1 then + harvestReadyState = fruitDesc.maxPreparingGrowthState + end + maxHarvestState = FieldUtil.getMaxHarvestState(field, field.fruitType) + if maxHarvestState == harvestReadyState then return end end - maxHarvestState = FieldUtil.getMaxHarvestState(field, field.fruitType) - if maxHarvestState == harvestReadyState then return end - -- leave a cut field for plow/ grubber mission - area, total = FieldUtil.getFruitArea(x - 1, z - 1, x + 1, z - 1, x - 1, z + 1, FieldUtil.FILTER_EMPTY, FieldUtil.FILTER_EMPTY, field.fruitType, fruitDesc.cutState, fruitDesc.cutState, 0, 0, 0, false) - if area > 0.5 * total and g_currentMission.snowSystem.height < SnowSystem.MIN_LAYER_HEIGHT and math.random() < 0.3 then return end - else + if npc.plowCultivate then + -- leave a cut field for plow/ grubber mission + area, total = FieldUtil.getFruitArea(x - 1, z - 1, x + 1, z - 1, x - 1, z + 1, FieldUtil.FILTER_EMPTY, FieldUtil.FILTER_EMPTY, field.fruitType, fruitDesc.cutState, fruitDesc.cutState, 0, 0, 0, false) + if area > 0.5 * total and + g_currentMission.snowSystem.height < SnowSystem.MIN_LAYER_HEIGHT and + math.random() < prob.plowCultivate then return end + end + elseif npc.sow then -- leave empty (plowed/grubbered) field for sow mission - if self:getFruitIndexForField(field) ~= nil and math.random() < 0.5 then return end + if self:getFruitIndexForField(field) ~= nil and + math.random() < prob.sow then return end end superf(self, field, allowUpdates) end +function updateButtonsPanel(menu, page) + -- called by TabbedMenu.onPageChange(), after page:onFrameOpen() + local inGameMenu = BetterContracts.gameMenu + if page.id == "pageContracts" and inGameMenu.newButton ~= nil then + local disable = g_currentMission.missionDynamicInfo.isMultiplayer and + not g_currentMission.isMasterUser + -- disable if MP and not masterUser: + inGameMenu.newButton:setDisabled(disable) + inGameMenu.clearButton:setDisabled(disable) + end +end +function startContract() + -- overwrite dialog info box + local farmId = g_currentMission:getFarmId() + if g_missionManager:hasFarmReachedMissionLimit(farmId) + and BetterContracts.maxActive ~= 3 then + g_gui:showInfoDialog({ + visible = true, + text = g_i18n:getText("bc_enoughMissions"), + dialogType = DialogElement.TYPE_INFO + }) + end +end \ No newline at end of file diff --git a/config.xml b/config.xml new file mode 100644 index 0000000..3712716 --- /dev/null +++ b/config.xml @@ -0,0 +1,8 @@ + + + + + + + diff --git a/lib/rmod/Main.lua b/lib/rmod/Main.lua deleted file mode 100644 index 23bc8f0..0000000 --- a/lib/rmod/Main.lua +++ /dev/null @@ -1,13 +0,0 @@ ---- Royal Mod - ----@author Royal Modding ----@version 1.5.0.0 ----@date 03/12/2020 - ---- Initialize RoyalMod ----@param rmodDirectory string -function InitRoyalMod(rmodDirectory) - source(Utils.getFilename("RoyalMod.lua", rmodDirectory)) - Logging.devInfo("Royal Mod loaded successfully by " .. g_currentModName) - return true -end diff --git a/lib/rmod/RoyalMod.lua b/lib/rmod/RoyalMod.lua deleted file mode 100644 index f5668f9..0000000 --- a/lib/rmod/RoyalMod.lua +++ /dev/null @@ -1,615 +0,0 @@ ---- Royal Mod - ----@author Royal Modding ----@version 1.5.0.0 ----@date 03/12/2020 --- Changelog: --- v1.0.0.0 03.12.2020 initial by Royal-Modding --- v1.1.0.0 30.12.2021: (Mmtrx) commented out vehicleTypeManagerFinalizeTypes, different in FS22 - ----@class RoyalMod ----@field onWriteStream fun(self: RoyalMod, streamId: integer) ----@field onReadStream fun(self: RoyalMod, streamId: integer) ----@field onUpdateTick fun(self: RoyalMod, dt: number) ----@field onWriteUpdateStream fun(self: RoyalMod, streamId: integer, connection: Connection, dirtyMask: integer) ----@field onReadUpdateStream fun(self: RoyalMod, streamId: integer, timestamp: number, connection: Connection) ----@field onLoadMap fun(self: RoyalMod, mapNode: integer, mapFile: string) ----@field onDeleteMap fun(self: RoyalMod) ----@field onDraw fun(self: RoyalMod) ----@field onUpdate fun(self: RoyalMod, dt: number) ----@field onMouseEvent fun(self: RoyalMod, posX: number, posY: number, isDown: boolean, isUp: boolean, button: integer) ----@field onKeyEvent fun(self: RoyalMod, unicode: integer, sym: integer, modifier: integer, isDown: boolean) ----@field initialize fun(self: RoyalMod) ----@field onValidateVehicleTypes fun(self: RoyalMod, vtm: VehicleTypeManager, addSpecialization: fun(specName: string), addSpecializationBySpecialization: fun(specName: string, requiredSpecName: string), addSpecializationByVehicleType: fun(specName: string, requiredVehicleTypeName: string), addSpecializationByFunction: fun(specName: string, func: function)) ----@field onMissionInitialize fun(self: RoyalMod, baseDirectory: string, missionCollaborators: MissionCollaborators) ----@field onSetMissionInfo fun(self: RoyalMod, missionInfo: MissionInfo, missionDynamicInfo: table) ----@field onLoad fun(self: RoyalMod) ----@field onPreLoadMap fun(self: RoyalMod, mapFile: string) ----@field onCreateStartPoint fun(self: RoyalMod, startPointNode: integer) ----@field onPostLoadMap fun(self: RoyalMod, mapNode: integer, mapFile: string) ----@field onLoadSavegame fun(self: RoyalMod, savegameDirectory: string, savegameIndex: integer) ----@field onPreLoadVehicles fun(self: RoyalMod, xmlFile: integer, resetVehicles: boolean) ----@field onPreLoadItems fun(self: RoyalMod, xmlFile: integer) ----@field onPreLoadOnCreateLoadedObjects fun(self: RoyalMod, xmlFile: integer) ----@field onLoadFinished fun(self: RoyalMod) ----@field onStartMission fun(self: RoyalMod) ----@field onMissionStarted fun(self: RoyalMod) ----@field onPreDeleteMap fun(self: RoyalMod) ----@field onPreSaveSavegame fun(self: RoyalMod, savegameDirectory: string, savegameIndex: integer) ----@field onPostSaveSavegame fun(self: RoyalMod, savegameDirectory: string, savegameIndex: integer) ----@field onLoadHelpLine fun(self: RoyalMod): string ---@field directory string mod directory ---@field userProfileDirectory string user profile directory ---@field name string mod name ---@field mod table g_modManager mod object ---@field version string mod version ---@field author string mod author ---@field modEnv table mod scripting environment ---@field gameEnv table game scripting environment ---@field super table mod super class ---@field debug boolean mod debug state -RoyalMod = {} - ----@param debug boolean defines if debug is enabled ----@param mpSync boolean defines if mp sync is enabled ----@return RoyalMod -function RoyalMod.new(debug, mpSync) - ---@type RoyalMod - local mod = {} - mod.directory = g_currentModDirectory - mod.userProfileDirectory = getUserProfileAppPath() - mod.name = g_currentModName - mod.modManagerMod = g_modManager:getModByName(mod.name) - mod.version = mod.modManagerMod.version - mod.author = mod.modManagerMod.author - mod.modEnv = getfenv() - mod.gameEnv = getfenv(0) -- is redirected to modEnv in FS22 - mod.super = {} - mod.debug = debug - - if mod.debug then - g_showDevelopmentWarnings = true - g_addTestCommands = true - --mod.gameEnv["g_isDevelopmentConsoleScriptModTesting"] = true - end - - mod.super.oldFunctions = {} - - ---@param error string - mod.super.errorHandle = function(error) - Logging.error("RoyalMod caught error from %s (%s)", mod.name, mod.version) - Logging.error(error) - end - - mod.super.getSavegameDirectory = function() - if g_currentMission ~= nil and g_currentMission.missionInfo ~= nil then - if g_currentMission.missionInfo.savegameDirectory ~= nil then - return string.format("%s/", g_currentMission.missionInfo.savegameDirectory) - end - - if g_currentMission.missionInfo.savegameIndex ~= nil then - return string.format("%ssavegame%d/", mod.userProfileDirectory, g_currentMission.missionInfo.savegameIndex) - end - end - return mod.userProfileDirectory - end - - if mpSync then - mod.super.sync = Object:new(g_server ~= nil, g_client ~= nil, Class(nil, Object)) - - ---@param self Object - ---@param streamId integer - mod.super.sync.writeStream = function(self, streamId) - self:superClass().writeStream(self, streamId) - if mod.onWriteStream ~= nil then - local time = netGetTime() - local offset = streamGetWriteOffset(streamId) - xpcall(mod.onWriteStream, mod.super.errorHandle, mod, streamId) - offset = streamGetWriteOffset(streamId) - offset - Logging.devInfo("[%s] Written %.0f bits (%.0f bytes) in %s ms", mod.name, offset, offset / 8, netGetTime() - time) - end - end - - ---@param self Object - ---@param streamId integer - mod.super.sync.readStream = function(self, streamId) - self:superClass().readStream(self, streamId) - if mod.onReadStream ~= nil then - local time = netGetTime() - local offset = streamGetReadOffset(streamId) - xpcall(mod.onReadStream, mod.super.errorHandle, mod, streamId) - offset = streamGetReadOffset(streamId) - offset - Logging.devInfo("[%s] Read %.0f bits (%.0f bytes) in %s ms", mod.name, offset, offset / 8, netGetTime() - time) - end - end - - ---@param self Object - ---@param dt number - mod.super.sync.updateTick = function(self, dt) - self:superClass().updateTick(self, dt) - if mod.onUpdateTick ~= nil then - xpcall(mod.onUpdateTick, mod.super.errorHandle, mod, dt) - end - end - - ---@param self Object - ---@param streamId integer - ---@param connection Connection - ---@param dirtyMask integer - mod.super.sync.writeUpdateStream = function(self, streamId, connection, dirtyMask) - self:superClass().writeUpdateStream(self, streamId, connection, dirtyMask) - if mod.onWriteUpdateStream ~= nil then - xpcall(mod.onWriteUpdateStream, mod.super.errorHandle, mod, streamId, connection, dirtyMask) - end - end - - ---@param self Object - ---@param streamId integer - ---@param timestamp number - ---@param connection Connection - mod.super.sync.readUpdateStream = function(self, streamId, timestamp, connection) - self:superClass().readUpdateStream(self, streamId, timestamp, connection) - if mod.onReadUpdateStream ~= nil then - xpcall(mod.onReadUpdateStream, mod.super.errorHandle, mod, streamId, timestamp, connection) - end - end - end - - ---@param _ table - ---@param mapFile string - mod.super.loadMap = function(_, mapFile) - if mod.onLoadMap ~= nil then - xpcall(mod.onLoadMap, mod.super.errorHandle, mod, mod.mapNode, mapFile) - end - end - - ---@param _ table - mod.super.deleteMap = function(_) - if mod.onDeleteMap ~= nil then - xpcall(mod.onDeleteMap, mod.super.errorHandle, mod) - end - end - - ---@param _ table - mod.super.draw = function(_) - if mod.onDraw ~= nil then - xpcall(mod.onDraw, mod.super.errorHandle, mod) - end - end - - ---@param _ table - ---@param dt number - mod.super.update = function(_, dt) - if mod.onUpdate ~= nil then - xpcall(mod.onUpdate, mod.super.errorHandle, mod, dt) - end - end - - ---@param _ table - ---@param posX number - ---@param posY number - ---@param isDown boolean - ---@param isUp boolean - ---@param button integer - mod.super.mouseEvent = function(_, posX, posY, isDown, isUp, button) - if mod.onMouseEvent ~= nil then - xpcall(mod.onMouseEvent, mod.super.errorHandle, mod, posX, posY, isDown, isUp, button) - end - end - - ---@param _ table - ---@param unicode integer - ---@param sym integer - ---@param modifier integer - ---@param isDown boolean - mod.super.keyEvent = function(_, unicode, sym, modifier, isDown) - if mod.onKeyEvent ~= nil then - xpcall(mod.onKeyEvent, mod.super.errorHandle, mod, unicode, sym, modifier, isDown) - end - end - - --g_vehicleTypeManager = TypeManager.new("vehicle", "vehicleTypes", "dataS/vehicleTypes.xml", g_specializationManager) - - mod.super.oldFunctions.VehicleTypeManagerValidateTypes = TypeManager.validateTypes - TypeManager.validateTypes = function(self, ...) - ---@type table - local global = mod.gameEnv["g_i18n"].texts - ---@type string - for key, text in pairs(g_i18n.texts) do - if global[key] == nil then - global[key] = text - end - end - if mod.initialize ~= nil then - --- g_currentMission is still nil here - --- All mods are loaded here - xpcall(mod.initialize, mod.super.errorHandle, mod) - end - mod.super.oldFunctions.VehicleTypeManagerValidateTypes(self, ...) - end - - --[[ - mod.super.oldFunctions.VehicleTypeManagerfinalizeTypes = g_vehicleTypeManager.finalizeTypes - g_vehicleTypeManager.finalizeTypes = function(self, ...) - local specs = {} - local specsBySpec = {} - local specsByType = {} - local specsByFunc = {} - - local specAddAllowed = true - - if mod.onValidateVehicleTypes ~= nil then - --- Add your specialization to vehicle types here - xpcall( - mod.onValidateVehicleTypes, - mod.super.errorHandle, - mod, - self, - ---@param specName string - function(specName) - if not specAddAllowed then - Logging.devError("[%s] addSpecialization is no more allowed", mod.name) - return - end - table.insert(specs, {name = string.format("%s.%s", mod.name, specName), addedTo = {}}) - end, - ---@param specName string - ---@param requiredSpecName string - function(specName, requiredSpecName) - if not specAddAllowed then - Logging.devError("[%s] addSpecializationBySpecialization is no more allowed", mod.name) - return - end - table.insert(specsBySpec, {name = string.format("%s.%s", mod.name, specName), requiredSpecName = requiredSpecName, addedTo = {}}) - end, - ---@param specName string - ---@param requiredVehicleTypeName string - function(specName, requiredVehicleTypeName) - if not specAddAllowed then - Logging.devError("[%s] addSpecializationByVehicleType is no more allowed", mod.name) - return - end - table.insert(specsByType, {name = string.format("%s.%s", mod.name, specName), requiredVehicleTypeName = requiredVehicleTypeName, addedTo = {}}) - end, - ---@param specName string - ---@param func function - function(specName, func) - if not specAddAllowed then - Logging.devError("[%s] addSpecializationByFunction is no more allowed", mod.name) - return - end - table.insert(specsByFunc, {name = string.format("%s.%s", mod.name, specName), func = func, addedTo = {}}) - end - ) - end - - specAddAllowed = false - - -- remove invalid specs - - for i, spec in pairs(specs) do - if g_specializationManager:getSpecializationByName(spec.name) == nil then - Logging.devError("[%s] Can't find specialization %s", mod.name, spec.name) - table.remove(specs, i) - end - end - - for i, spec in pairs(specsBySpec) do - if g_specializationManager:getSpecializationByName(spec.name) == nil then - Logging.devError("[%s] Can't find specialization %s", mod.name, spec.name) - table.remove(specsBySpec, i) - end - end - - for i, spec in pairs(specsByType) do - if g_specializationManager:getSpecializationByName(spec.name) == nil then - Logging.devError("[%s] Can't find specialization %s", mod.name, spec.name) - table.remove(specsByType, i) - end - end - - for i, spec in pairs(specsByFunc) do - if g_specializationManager:getSpecializationByName(spec.name) == nil then - Logging.devError("[%s] Can't find specialization %s", mod.name, spec.name) - table.remove(specsByFunc, i) - end - end - - local vehicleTypesCount = 0 - - for typeName, typeEntry in pairs(self:getVehicleTypes()) do - vehicleTypesCount = vehicleTypesCount + 1 - - -- add "global" specializations - for _, spec in pairs(specs) do - if typeEntry.specializationsByName[spec.name] == nil then - if g_specializationManager:getSpecializationObjectByName(spec.name).prerequisitesPresent(typeEntry.specializations) then - self:addSpecialization(typeName, spec.name) - table.insert(spec.addedTo, typeName) - else - Logging.devError("[%s] Not all prerequisites of specialization %s are fulfilled by %s", mod.name, spec.name, typeName) - end - end - end - - -- add specializations by function - for _, spec in pairs(specsByFunc) do - if spec.func(typeEntry) then - if typeEntry.specializationsByName[spec.name] == nil then - if g_specializationManager:getSpecializationObjectByName(spec.name).prerequisitesPresent(typeEntry.specializations) then - self:addSpecialization(typeName, spec.name) - table.insert(spec.addedTo, typeName) - else - Logging.devError("[%s] Not all prerequisites of specialization %s are fulfilled by %s", mod.name, spec.name, typeName) - end - end - end - end - - -- add specializations by required specialization - for name, _ in pairs(typeEntry.specializationsByName) do - for _, spec in pairs(specsBySpec) do - if name == spec.requiredSpecName then - if typeEntry.specializationsByName[spec.name] == nil then - if g_specializationManager:getSpecializationObjectByName(spec.name).prerequisitesPresent(typeEntry.specializations) then - self:addSpecialization(typeName, spec.name) - table.insert(spec.addedTo, typeName) - else - Logging.devError("[%s] Not all prerequisites of specialization %s are fulfilled by %s", mod.name, spec.name, typeName) - end - end - end - end - end - - -- add specializations by required vehicle type - for _, spec in pairs(specsByType) do - if typeName == spec.requiredVehicleTypeName then - if typeEntry.specializationsByName[spec.name] == nil then - if g_specializationManager:getSpecializationObjectByName(spec.name).prerequisitesPresent(typeEntry.specializations) then - self:addSpecialization(typeName, spec.name) - table.insert(spec.addedTo, typeName) - else - Logging.devError("[%s] Not all prerequisites of specialization %s are fulfilled by %s", mod.name, spec.name, typeName) - end - end - end - end - end - - for _, spec in pairs(specs) do - if #spec.addedTo <= 25 then - Logging.devInfo("[%s] %s added to [%s]", mod.name, spec.name, table.concat(spec.addedTo, ", ")) - else - Logging.devInfo("[%s] %s added to %s vehicle types out of %s", mod.name, spec.name, #spec.addedTo, vehicleTypesCount) - end - end - - for _, spec in pairs(specsBySpec) do - if #spec.addedTo <= 25 then - Logging.devInfo("[%s] %s added to [%s]", mod.name, spec.name, table.concat(spec.addedTo, ", ")) - else - Logging.devInfo("[%s] %s added to %s vehicle types out of %s", mod.name, spec.name, #spec.addedTo, vehicleTypesCount) - end - end - - for _, spec in pairs(specsByType) do - if #spec.addedTo <= 25 then - Logging.devInfo("[%s] %s added to [%s]", mod.name, spec.name, table.concat(spec.addedTo, ", ")) - else - Logging.devInfo("[%s] %s added to %s vehicle types out of %s", mod.name, spec.name, #spec.addedTo, vehicleTypesCount) - end - end - - for _, spec in pairs(specsByFunc) do - if #spec.addedTo <= 25 then - Logging.devInfo("[%s] %s added to [%s]", mod.name, spec.name, table.concat(spec.addedTo, ", ")) - else - Logging.devInfo("[%s] %s added to %s vehicle types out of %s", mod.name, spec.name, #spec.addedTo, vehicleTypesCount) - end - end - - mod.super.oldFunctions.VehicleTypeManagerfinalizeTypes(self, ...) - end - ]] - - mod.super.oldFunctions.Mission00new = Mission00.new - ---@param self Mission00 - ---@param baseDirectory string - ---@param customMt? table - ---@param missionCollaborators MissionCollaborators - ---@return Mission00 - Mission00.new = function(self, baseDirectory, customMt, missionCollaborators, ...) - if mod.onMissionInitialize ~= nil then - --- g_currentMission is still nil here - xpcall(mod.onMissionInitialize, mod.super.errorHandle, mod, baseDirectory, missionCollaborators) - end - return mod.super.oldFunctions.Mission00new(self, baseDirectory, customMt, missionCollaborators, ...) - end - - mod.super.oldFunctions.Mission00setMissionInfo = Mission00.setMissionInfo - ---@param self Mission00 - ---@param missionInfo FSCareerMissionInfo - ---@param missionDynamicInfo table - Mission00.setMissionInfo = function(self, missionInfo, missionDynamicInfo, ...) - g_currentMission:addLoadFinishedListener(mod.super) - g_currentMission:registerObjectToCallOnMissionStart(mod.super) - if mod.onSetMissionInfo ~= nil then - --- g_currentMission is no more nil here - xpcall(mod.onSetMissionInfo, mod.super.errorHandle, mod, missionInfo, missionDynamicInfo) - end - mod.super.oldFunctions.Mission00setMissionInfo(self, missionInfo, missionDynamicInfo, ...) - end - - mod.super.oldFunctions.Mission00load = Mission00.load - ---@param self Mission00 - Mission00.load = function(self, ...) - if mod.onLoad ~= nil then - xpcall(mod.onLoad, mod.super.errorHandle, mod) - end - mod.super.oldFunctions.Mission00load(self, ...) - end - - mod.super.oldFunctions.FSBaseMissionloadMap = FSBaseMission.loadMap - ---@param self FSBaseMission - ---@param mapFile string - ---@param addPhysics boolean - ---@param asyncCallbackFunction function - ---@param asyncCallbackObject table - ---@param asyncCallbackArguments table - FSBaseMission.loadMap = function(self, mapFile, addPhysics, asyncCallbackFunction, asyncCallbackObject, asyncCallbackArguments, ...) - if mod.onPreLoadMap ~= nil then - xpcall(mod.onPreLoadMap, mod.super.errorHandle, mod, mapFile) - end - mod.super.oldFunctions.FSBaseMissionloadMap(self, mapFile, addPhysics, asyncCallbackFunction, asyncCallbackObject, asyncCallbackArguments, ...) - end - - mod.super.oldFunctions.Mission00onCreateStartPoint = Mission00.onCreateStartPoint - ---@param self Mission00 - ---@param startPointNode integer - Mission00.onCreateStartPoint = function(self, startPointNode, ...) - mod.super.oldFunctions.Mission00onCreateStartPoint(self, startPointNode, ...) - if mod.super.sync ~= nil then - g_currentMission:addOnCreateLoadedObject(mod.super.sync) - mod.super.sync:register(true) - end - if mod.onCreateStartPoint ~= nil then - xpcall(mod.onCreateStartPoint, mod.super.errorHandle, mod, startPointNode) - end - end - - mod.super.oldFunctions.BaseMissionloadMapFinished = BaseMission.loadMapFinished - ---@param self BaseMission - ---@param mapNode integer - ---@param arguments table - ---@param callAsyncCallback boolean - BaseMission.loadMapFinished = function(self, mapNode, failedReason, arguments, callAsyncCallback, ...) - mod.mapNode = mapNode - local mapFile, _, _, _ = unpack(arguments) - mod.super.oldFunctions.BaseMissionloadMapFinished(self, mapNode, failedReason, arguments, callAsyncCallback, ...) - if mod.onPostLoadMap ~= nil then - xpcall(mod.onPostLoadMap, mod.super.errorHandle, mod, mapNode, mapFile) - end - end - - mod.super.oldFunctions.Mission00loadMission00Finished = Mission00.loadMission00Finished - ---@param self Mission00 - ---@param mapNode integer - ---@param arguments table - Mission00.loadMission00Finished = function(self, mapNode, arguments, ...) - if mod.onLoadSavegame ~= nil then - xpcall(mod.onLoadSavegame, mod.super.errorHandle, mod, mod.super.getSavegameDirectory(), g_currentMission.missionInfo.savegameIndex) - end - mod.super.oldFunctions.Mission00loadMission00Finished(self, mapNode, arguments, ...) - end - - mod.super.oldFunctions.Mission00loadVehicles = Mission00.loadVehicles - ---@param self Mission00 - ---@param xmlFile integer - ---@param resetVehicles boolean - Mission00.loadVehicles = function(self, xmlFile, resetVehicles, ...) - if mod.onPreLoadVehicles ~= nil then - xpcall(mod.onPreLoadVehicles, mod.super.errorHandle, mod, xmlFile, resetVehicles) - end - mod.super.oldFunctions.Mission00loadVehicles(self, xmlFile, resetVehicles, ...) - end - - mod.super.oldFunctions.Mission00loadItems = Mission00.loadItems - ---@param self Mission00 - ---@param xmlFile integer - Mission00.loadItems = function(self, xmlFile, ...) - if mod.onPreLoadItems ~= nil then - xpcall(mod.onPreLoadItems, mod.super.errorHandle, mod, xmlFile) - end - mod.super.oldFunctions.Mission00loadItems(self, xmlFile, ...) - end - - mod.super.oldFunctions.Mission00loadOnCreateLoadedObjects = Mission00.loadOnCreateLoadedObjects - ---@param self Mission00 - ---@param xmlFile integer - Mission00.loadOnCreateLoadedObjects = function(self, xmlFile, ...) - if mod.onPreLoadOnCreateLoadedObjects ~= nil then - xpcall(mod.onPreLoadOnCreateLoadedObjects, mod.super.errorHandle, mod, xmlFile) - end - mod.super.oldFunctions.Mission00loadOnCreateLoadedObjects(self, xmlFile, ...) - end - - -- not called on clients in MP games - mod.super.onLoadFinished = function() - if mod.onLoadFinished ~= nil then - xpcall(mod.onLoadFinished, mod.super.errorHandle, mod) - end - end - - mod.super.oldFunctions.Mission00onStartMission = Mission00.onStartMission - ---@param self Mission00 - Mission00.onStartMission = function(self, ...) - if mod.onStartMission ~= nil then - xpcall(mod.onStartMission, mod.super.errorHandle, mod) - end - mod.super.oldFunctions.Mission00onStartMission(self, ...) - end - - mod.super.onMissionStarted = function(...) - if mod.onMissionStarted ~= nil then - xpcall(mod.onMissionStarted, mod.super.errorHandle, mod) - end - end - - mod.super.oldFunctions.Mission00delete = Mission00.delete - ---@param self Mission00 - Mission00.delete = function(self, ...) - if mod.onPreDeleteMap ~= nil then - xpcall(mod.onPreDeleteMap, mod.super.errorHandle, mod) - end - mod.super.oldFunctions.Mission00delete(self, ...) - end - - mod.super.oldFunctions.FSBaseMissionsaveSavegame = FSBaseMission.saveSavegame - ---@param self FSBaseMission - FSBaseMission.saveSavegame = function(self, ...) - if mod.onPreSaveSavegame ~= nil then - -- before all vhicles, items and onCreateObjects are saved - xpcall(mod.onPreSaveSavegame, mod.super.errorHandle, mod, mod.super.getSavegameDirectory(), g_currentMission.missionInfo.savegameIndex) - end - mod.super.oldFunctions.FSBaseMissionsaveSavegame(self, ...) - if mod.onPostSaveSavegame ~= nil then - -- after all vhicles, items and onCreateObjects are saved - xpcall(mod.onPostSaveSavegame, mod.super.errorHandle, mod, mod.super.getSavegameDirectory(), g_currentMission.missionInfo.savegameIndex) - end - end - - mod.super.oldFunctions.HelpLineManagerloadMapData = HelpLineManager.loadMapData - ---@param self HelpLineManager - ---@param xmlFile integer - ---@param missionInfo FSCareerMissionInfo - ---@return boolean - HelpLineManager.loadMapData = function(self, xmlFile, missionInfo) - if mod.super.oldFunctions.HelpLineManagerloadMapData(self, xmlFile, missionInfo) then - if mod.onLoadHelpLine ~= nil then - local success, hlFilename = xpcall(mod.onLoadHelpLine, mod.super.errorHandle, mod) - if success and hlFilename ~= nil and type(hlFilename) == "string" and hlFilename ~= "" then - self:loadFromXML(hlFilename) - for ci = 1, #self.categories do - local category = self.categories[ci] - for pi = 1, #category.pages do - local page = category.pages[pi] - for ii = 1, #page.items do - local item = page.items[ii] - if item.type == HelpLineManager.ITEM_TYPE.IMAGE then - if item.value:sub(1, 10) == "$rmModDir/" then - item.value = "$" .. mod.directory .. item.value:sub(11) - end - end - end - end - end - end - end - return true - end - end - - addModEventListener(mod.super) - return mod -end diff --git a/lib/utility/Array.lua b/lib/utility/Array.lua deleted file mode 100644 index 2174e63..0000000 --- a/lib/utility/Array.lua +++ /dev/null @@ -1,48 +0,0 @@ ---- Royal Utility - ----@author Royal Modding ----@version 2.1.1.0 ----@date 26/02/2021 - ----@alias Array table Table with numeric indexes only, always ordered and sequential - ---- Array utilities class built with performances in mind (with 'array' we mean tables with numeric indexes only, always ordered and sequential) ----@class ArrayUtility -ArrayUtility = ArrayUtility or {} - ---- Remove matching elements from an array ----@param array Array ----@param removeFunc fun(array: Array, index: number, moveAt: number): boolean | "function(array, index, moveAt) local element = array[index] return true end" ----@return number removedCount count of removed elements -function ArrayUtility.remove(array, removeFunc) - local removedCount = 0 - local moveAt, length = 1, #array - for index = 1, length do - if removeFunc(array, index, moveAt) then - array[index] = nil - removedCount = removedCount + 1 - else - -- move kept element's value to moveAt's position, if it's not already there - if (index ~= moveAt) then - array[moveAt] = array[index] - array[index] = nil - end - -- increment position of where we'll place the next kept value - moveAt = moveAt + 1 - end - end - return removedCount -end - ---- Remove element at the given index from an array ----@param array Array ----@param index number ----@return Array -function ArrayUtility.removeAt(array, index) - ArrayUtility.remove( - array, - function(_, i) - return index == i - end - ) -end diff --git a/lib/utility/Debug.lua b/lib/utility/Debug.lua deleted file mode 100644 index 27a8f19..0000000 --- a/lib/utility/Debug.lua +++ /dev/null @@ -1,337 +0,0 @@ ---- Royal Utility - ----@author Royal Modding ----@version 2.1.1.0 ----@date 05/01/2021 - ----@class DebugUtility -DebugUtility = DebugUtility or {} - ---- Render a table (for debugging purpose) ----@param posX number ----@param posY number ----@param textSize number ----@param inputTable table ----@param maxDepth integer|nil ----@param hideFunc boolean|nil -function DebugUtility.renderTable(posX, posY, textSize, inputTable, maxDepth, hideFunc) - inputTable = inputTable or {tableIs = "nil"} - hideFunc = hideFunc or false - maxDepth = maxDepth or 2 - - local function renderTableRecursively(x, t, depth, i) - if depth >= maxDepth then - return i - end - for k, v in pairs(t) do - local vType = type(v) - if not hideFunc or vType ~= "function" then - local offset = i * textSize * 1.05 - setTextAlignment(RenderText.ALIGN_RIGHT) - renderText(x, posY - offset, textSize, tostring(k) .. " :") - setTextAlignment(RenderText.ALIGN_LEFT) - if vType ~= "table" then - renderText(x, posY - offset, textSize, " " .. tostring(v)) - end - i = i + 1 - if vType == "table" then - i = renderTableRecursively(x + textSize * 1.8, v, depth + 1, i) - end - end - end - return i - end - - local i = 0 - setTextColor(1, 1, 1, 1) - setTextBold(false) - textSize = getCorrectTextSize(textSize) - for k, v in pairs(inputTable) do - local vType = type(v) - if not hideFunc or vType ~= "function" then - local offset = i * textSize * 1.05 - setTextAlignment(RenderText.ALIGN_RIGHT) - renderText(posX, posY - offset, textSize, tostring(k) .. " :") - setTextAlignment(RenderText.ALIGN_LEFT) - if vType ~= "table" then - renderText(posX, posY - offset, textSize, " " .. tostring(v)) - end - i = i + 1 - if vType == "table" then - i = renderTableRecursively(posX + textSize * 1.8, v, 1, i) - end - end - end -end - ---- Render a node hierarchy (for debugging purpose) ----@param posX number ----@param posY number ----@param textSize number ----@param inputNode integer ----@param maxDepth integer|nil -function DebugUtility.renderNodeHierarchy(posX, posY, textSize, inputNode, maxDepth) - if inputNode == nil or inputNode == 0 then - return - end - if type(inputNode) == "number" and entityExists(inputNode) then - maxDepth = maxDepth or math.huge - - local function renderNodeHierarchyRecursively(x, node, depth, i) - if depth >= maxDepth then - return i - end - local offset = i * textSize * 1.05 - local _, className = EntityUtility.getObjectClass(node) - renderText(x, posY - offset, textSize, string.format("%s (%s)", getName(node), className)) - i = i + 1 - for ni = 0, getNumOfChildren(node) - 1 do - i = renderNodeHierarchyRecursively(x + textSize * 1.8, getChildAt(node, ni), depth + 1, i) - end - return i - end - - local i = 1 - setTextColor(1, 1, 1, 1) - setTextBold(false) - textSize = getCorrectTextSize(textSize) - local _, className = EntityUtility.getObjectClass(inputNode) - renderText(posX, posY, textSize, string.format("%s (%s)", getName(inputNode), className)) - for ni = 0, getNumOfChildren(inputNode) - 1 do - i = renderNodeHierarchyRecursively(posX + textSize * 1.8, getChildAt(inputNode, ni), 1, i) - end - end -end - ---- Draw a rectangle (for debugging purpose) ----@param node integer ref node ----@param minX number minX ----@param maxX number maxX ----@param minZ number minZ ----@param maxZ number maxZ ----@param yOffset number height offset ----@param alignToGround boolean alignToGround ----@param r number r ----@param g number g ----@param b number b ----@param ar number r color if active ----@param ag number g color if active ----@param ab number b color if active ----@param active boolean active? -function DebugUtility.drawDebugRectangle(node, minX, maxX, minZ, maxZ, yOffset, alignToGround, r, g, b, ar, ag, ab, active) - if active then - r, g, b = ar, ag, ab - end - - local leftFrontX, leftFrontY, leftFrontZ = localToWorld(node, minX, yOffset, maxZ) - local rightFrontX, rightFrontY, rightFrontZ = localToWorld(node, maxX, yOffset, maxZ) - - local leftBackX, leftBackY, leftBackZ = localToWorld(node, minX, yOffset, minZ) - local rightBackX, rightBackY, rightBackZ = localToWorld(node, maxX, yOffset, minZ) - - if alignToGround then - leftFrontY = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, leftFrontX, 0, leftFrontZ) + yOffset + 0.1 - rightFrontY = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, rightFrontX, 0, rightFrontZ) + yOffset + 0.1 - leftBackY = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, leftBackX, 0, leftBackZ) + yOffset + 0.1 - rightBackY = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, rightBackX, 0, rightBackZ) + yOffset + 0.1 - end - - drawDebugLine(leftFrontX, leftFrontY, leftFrontZ, r, g, b, rightFrontX, rightFrontY, rightFrontZ, r, g, b) - drawDebugLine(rightFrontX, rightFrontY, rightFrontZ, r, g, b, rightBackX, rightBackY, rightBackZ, r, g, b) - drawDebugLine(rightBackX, rightBackY, rightBackZ, r, g, b, leftBackX, leftBackY, leftBackZ, r, g, b) - drawDebugLine(leftBackX, leftBackY, leftBackZ, r, g, b, leftFrontX, leftFrontY, leftFrontZ, r, g, b) -end - ---- Draw a cube (for debugging purpose) ----@param node integer|table ref node or ref position ----@param size number size ----@param r number r ----@param g number g ----@param b number b ----@param ar number r color if active ----@param ag number g color if active ----@param ab number b color if active ----@param active boolean active? -function DebugUtility.drawDebugCube(node, size, r, g, b, ar, ag, ab, active) - if active then - r, g, b = ar, ag, ab - end - - local x, y, z = 0, 0, 0 - if type(node) == "table" then - x = node[1] - y = node[2] - z = node[3] - else - x, y, z = getWorldTranslation(node) - end - - local offsets = size / 2 - local corners = {} - corners[1] = {x + offsets, y + offsets, z + offsets} - corners[2] = {x + offsets, y + offsets, z - offsets} - corners[3] = {x + offsets, y - offsets, z - offsets} - corners[4] = {x + offsets, y - offsets, z + offsets} - corners[5] = {x - offsets, y + offsets, z + offsets} - corners[6] = {x - offsets, y + offsets, z - offsets} - corners[7] = {x - offsets, y - offsets, z - offsets} - corners[8] = {x - offsets, y - offsets, z + offsets} - - DebugUtility.drawDebugLine(corners[1], corners[2], 0, 0, 0) - DebugUtility.drawDebugLine(corners[2], corners[3], 0, 0, 0) - DebugUtility.drawDebugLine(corners[3], corners[4], 0, 0, 0) - DebugUtility.drawDebugLine(corners[4], corners[1], 0, 0, 0) - DebugUtility.drawDebugLine(corners[1], corners[5], 0, 0, 0) - DebugUtility.drawDebugLine(corners[2], corners[6], 0, 0, 0) - DebugUtility.drawDebugLine(corners[3], corners[7], 0, 0, 0) - DebugUtility.drawDebugLine(corners[4], corners[8], 0, 0, 0) - DebugUtility.drawDebugLine(corners[5], corners[6], 0, 0, 0) - DebugUtility.drawDebugLine(corners[6], corners[7], 0, 0, 0) - DebugUtility.drawDebugLine(corners[7], corners[8], 0, 0, 0) - DebugUtility.drawDebugLine(corners[8], corners[5], 0, 0, 0) - - DebugUtility.drawDebugTriangle(corners[3], corners[2], corners[1], r, g, b) - DebugUtility.drawDebugTriangle(corners[1], corners[4], corners[3], r, g, b) - DebugUtility.drawDebugTriangle(corners[4], corners[1], corners[5], r, g, b) - DebugUtility.drawDebugTriangle(corners[5], corners[8], corners[4], r, g, b) - DebugUtility.drawDebugTriangle(corners[8], corners[5], corners[6], r, g, b) - DebugUtility.drawDebugTriangle(corners[6], corners[7], corners[8], r, g, b) - DebugUtility.drawDebugTriangle(corners[7], corners[6], corners[2], r, g, b) - DebugUtility.drawDebugTriangle(corners[2], corners[3], corners[7], r, g, b) - DebugUtility.drawDebugTriangle(corners[2], corners[6], corners[5], r, g, b) - DebugUtility.drawDebugTriangle(corners[5], corners[1], corners[2], r, g, b) - DebugUtility.drawDebugTriangle(corners[4], corners[8], corners[7], r, g, b) - DebugUtility.drawDebugTriangle(corners[7], corners[3], corners[4], r, g, b) -end - ---- Draw a triangle (for debugging purpose) ----@param c1 table first corner position {x, y, z} ----@param c2 table second corner position {x, y, z} ----@param c3 table third corner position {x, y, z} ----@param r number r ----@param g number g ----@param b number b -function DebugUtility.drawDebugTriangle(c1, c2, c3, r, g, b) - drawDebugTriangle(c1[1], c1[2], c1[3], c2[1], c2[2], c2[3], c3[1], c3[2], c3[3], r, g, b, 1, false) -end - ---- Draw a triangle (for debugging purpose) ----@param p1 table first point position {x, y, z} ----@param p2 table second point position {x, y, z} ----@param r number r ----@param g number g ----@param b number b -function DebugUtility.drawDebugLine(p1, p2, r, g, b) - drawDebugLine(p1[1], p1[2], p1[3], r, g, b, p2[1], p2[2], p2[3], r, g, b) -end - ---- Render an AnimCurve (for debugging purpose) ----@param x number x position ----@param y number y position ----@param w number width ----@param h number height ----@param curve table AnimCurve object ----@param numPointsToShow? integer number of points to render -function DebugUtility.renderAnimCurve(x, y, w, h, curve, numPointsToShow) - local graph = curve.debugGraph - local numPoints = numPointsToShow or #curve.keyframes - local minTime = 0 - local maxTime = curve.maxTime - if graph == nil then - if numPointsToShow == nil then - graph = Graph:new(numPoints, x, y, w, h, 0, 0.0001, true, "", Graph.STYLE_LINES) - graph:setColor(1, 0, 0, 1) - for i, kf in ipairs(curve.keyframes) do - local v = curve:get(kf.time) - graph:setValue(i, v) - graph:setXPosition(i, (kf.time - minTime) / (maxTime - minTime)) - graph.maxValue = math.max(graph.maxValue, v) - end - else - graph = Graph:new(numPoints + 1, x, y, w, h, 0, 0.0001, true, "", Graph.STYLE_LINES) - graph:setColor(1, 0, 0, 1) - for s = 1, numPoints + 1 do - local i = s - 1 - local v = curve:get(minTime + (maxTime - minTime) * (i / numPoints)) - graph:setValue(s, v) - graph.maxValue = math.max(graph.maxValue, v) - end - end - curve.debugGraph = graph - end - graph:draw() -end - ---- Get the loading speed meter object ----@return LoadingSpeedMeter loadingSpeedMeter -function DebugUtility.getVehicleLoadingSpeedMeter() - if DebugUtility.loadingSpeedMeter == nil then - ---@class LoadingSpeedMeter - DebugUtility.loadingSpeedMeter = {} - DebugUtility.loadingSpeedMeter.vehicles = {} - DebugUtility.loadingSpeedMeter.filters = {} - --- Add a new filter - ---@param filterFunction function | 'function(vehicleData) return true, "meter name" end' - DebugUtility.loadingSpeedMeter.addFilter = function(filterFunction) - table.insert(DebugUtility.loadingSpeedMeter.filters, filterFunction) - end - Utility.overwrittenFunction( - Vehicle, - "load", - function(self, superFunc, vehicleData, asyncCallbackFunction, asyncCallbackObject, asyncCallbackArguments) - local smEnabled = false - local smName = "" - for _, filter in ipairs(DebugUtility.loadingSpeedMeter.filters) do - smEnabled, smName = filter(vehicleData) - if smEnabled then - break - end - end - - if smEnabled then - DebugUtility.loadingSpeedMeter.vehicles[self] = {} - DebugUtility.loadingSpeedMeter.vehicles[self].smName = smName - DebugUtility.loadingSpeedMeter.vehicles[self].totalStartTime = getTimeSec() - end - - local state = superFunc(self, vehicleData, asyncCallbackFunction, asyncCallbackObject, asyncCallbackArguments) - - if smEnabled then - DebugUtility.loadingSpeedMeter.vehicles[self].totalTime = getTimeSec() - DebugUtility.loadingSpeedMeter.vehicles[self].totalStartTime - print(string.format("[%s] Pre time: %.4f ms", DebugUtility.loadingSpeedMeter.vehicles[self].smName, (DebugUtility.loadingSpeedMeter.vehicles[self].preLoadTime or 0) * 1000)) - print(string.format("[%s] Load time: %.4f ms", DebugUtility.loadingSpeedMeter.vehicles[self].smName, (DebugUtility.loadingSpeedMeter.vehicles[self].loadTime or 0) * 1000)) - print(string.format("[%s] Post time: %.4f ms", DebugUtility.loadingSpeedMeter.vehicles[self].smName, (DebugUtility.loadingSpeedMeter.vehicles[self].postLoadTime or 0) * 1000)) - print(string.format("[%s] Total time: %.4f ms", DebugUtility.loadingSpeedMeter.vehicles[self].smName, (DebugUtility.loadingSpeedMeter.vehicles[self].totalTime or 0) * 1000)) - DebugUtility.loadingSpeedMeter.vehicles[self] = nil - end - return state - end - ) - Utility.overwrittenStaticFunction( - SpecializationUtil, - "raiseEvent", - function(superFunc, vehicle, eventName, ...) - if DebugUtility.loadingSpeedMeter.vehicles[vehicle] ~= nil then - if eventName == "onPreLoad" then - DebugUtility.loadingSpeedMeter.vehicles[vehicle].preLoadStartTime = getTimeSec() - superFunc(vehicle, eventName, ...) - DebugUtility.loadingSpeedMeter.vehicles[vehicle].preLoadTime = getTimeSec() - DebugUtility.loadingSpeedMeter.vehicles[vehicle].preLoadStartTime - end - if eventName == "onLoad" then - DebugUtility.loadingSpeedMeter.vehicles[vehicle].loadStartTime = getTimeSec() - superFunc(vehicle, eventName, ...) - DebugUtility.loadingSpeedMeter.vehicles[vehicle].loadTime = getTimeSec() - DebugUtility.loadingSpeedMeter.vehicles[vehicle].loadStartTime - end - if eventName == "onPostLoad" then - DebugUtility.loadingSpeedMeter.vehicles[vehicle].postLoadStartTime = getTimeSec() - superFunc(vehicle, eventName, ...) - DebugUtility.loadingSpeedMeter.vehicles[vehicle].postLoadTime = getTimeSec() - DebugUtility.loadingSpeedMeter.vehicles[vehicle].postLoadStartTime - end - else - superFunc(vehicle, eventName, ...) - end - end - ) - end - return DebugUtility.loadingSpeedMeter -end diff --git a/lib/utility/DelayedCallBack.lua b/lib/utility/DelayedCallBack.lua deleted file mode 100644 index 6353b9d..0000000 --- a/lib/utility/DelayedCallBack.lua +++ /dev/null @@ -1,63 +0,0 @@ ---- Royal Utility - ----@author Royal Modding ----@version 2.1.1.0 ----@date 08/03/17 - ----@class DelayedCallBack -DelayedCallBack = {} - ----@param callback function ----@param callbackObject any ----@return DelayedCallBack -function DelayedCallBack:new(callback, callbackObject) - if DelayedCallBack_mt == nil then - DelayedCallBack_mt = Class(DelayedCallBack) - end - - ---@type DelayedCallBack - local dcb = setmetatable({}, DelayedCallBack_mt) - dcb.callBack = callback - dcb.callbackObject = callbackObject - dcb.callbackCalled = true - dcb.delay = 0 - dcb.timer = 0 - dcb.skipOneFrame = false - return dcb -end - ----@param dt number -function DelayedCallBack:update(dt) - if not self.callbackCalled then - if not self.skipOneFrame then - self.timer = self.timer + dt - end - if self.timer >= self.delay then - self:callCallBack() - end - if self.skipOneFrame then - self.timer = self.timer + dt - end - end -end - ----@param delay number -function DelayedCallBack:call(delay, ...) - self.callbackCalled = false - self.callbackParams = {...} - if delay == nil or delay == 0 then - self:callCallBack() - else - self.delay = delay - self.timer = 0 - end -end - -function DelayedCallBack:callCallBack() - if self.callbackObject ~= nil then - self.callBack(self.callbackObject, unpack(self.callbackParams)) - else - self.callBack(unpack(self.callbackParams)) - end - self.callbackCalled = true -end diff --git a/lib/utility/Entity.lua b/lib/utility/Entity.lua deleted file mode 100644 index 66d7ca0..0000000 --- a/lib/utility/Entity.lua +++ /dev/null @@ -1,174 +0,0 @@ ---- Royal Utility - ----@author Royal Modding ----@version 2.1.1.0 ----@date 05/01/2021 - ----@class EntityUtility -EntityUtility = EntityUtility or {} - ---- Get the class id and name of an onject ----@param objectId integer ----@return integer classId class id ----@return string className class name -function EntityUtility.getObjectClass(objectId) - if objectId == nil then - return nil, nil - end - for name, id in pairs(ClassIds) do - if getHasClassId(objectId, id) then - return id, name - end - end -end - ---- Determines whether a node is a child of a given node ----@param childNode integer ----@param parentNode integer ----@return boolean -function EntityUtility.isChildOf(childNode, parentNode) - if childNode == nil or childNode == 0 or parentNode == nil or parentNode == 0 then - return false - end - local pNode = getParent(childNode) - while pNode ~= 0 do - if pNode == parentNode then - return true - end - pNode = getParent(pNode) - end - return false -end - ---- Get the node index relative to root node ----@param nodeId integer id of node ----@param rootId integer id of root node ----@return string nodeIndex index of node -function EntityUtility.nodeToIndex(nodeId, rootId) - local index = "" - if nodeId ~= nil and entityExists(nodeId) and rootId ~= nil and entityExists(rootId) and EntityUtility.isChildOf(nodeId, rootId) then - index = tostring(getChildIndex(nodeId)) - local pNode = getParent(nodeId) - while pNode ~= rootId and pNode ~= 0 do - index = string.format("%s|%s", getChildIndex(pNode), index) - pNode = getParent(pNode) - end - end - return index -end - ---- Get a node id by an index ----@param nodeIndex string index of node ----@param rootId integer id of root node ----@return integer nodeId id of node -function EntityUtility.indexToNode(nodeIndex, rootId) - if nodeIndex == nil or rootId == nil or not entityExists(rootId) then - return nil - end - local objectId = rootId - local indexes = StringUtility.split(nodeIndex, "|") - for _, index in pairs(indexes) do - index = tonumber(index) - if type(index) == "number" then - if getNumOfChildren(objectId) >= index then - objectId = getChildAt(objectId, index) - else - return nil - end - else - return nil - end - end - return objectId -end - ---- Queries a node hierarchy ----@param inputNode integer ----@param func fun(node: integer, name: string, depth: integer) -function EntityUtility.queryNodeHierarchy(inputNode, func) - if not type(inputNode) == "number" or not entityExists(inputNode) or func == nil then - return - end - local function queryNodeHierarchyRecursively(node, depth) - func(node, getName(node), depth) - for i = 0, getNumOfChildren(node) - 1 do - queryNodeHierarchyRecursively(getChildAt(node, i), depth + 1) - end - end - local depth = 1 - func(inputNode, getName(inputNode), depth) - for i = 0, getNumOfChildren(inputNode) - 1 do - queryNodeHierarchyRecursively(getChildAt(inputNode, i), depth + 1) - end -end - ---- Get the hash of a node hierarchy ----@param node integer ----@param parent integer ----@param md5 boolean ----@return string hash hash of the node hierarchy -function EntityUtility.getNodeHierarchyHash(node, parent, md5) - if not type(node) == "number" or not entityExists(node) or not type(parent) == "number" or not entityExists(parent) then - return string.format("Invalid hash node:%s parent:%s", node, parent) - end - local hash = "" - local nodeCount = 0 - - local floatsToString = function(...) - local ret = {} - for i, v in ipairs({...}) do - local tV = string.format("%.1f", v) - if tV == "-0.0" then - tV = "0.0" - end - ret[i] = tV - end - return table.concat(ret, "|") - end - - local isDyna = false - - EntityUtility.queryNodeHierarchy( - node, - function(n, name) - local rbt = getRigidBodyType(n) - if rbt == "Dynamic" then - isDyna = true - end - local pos = "" - local rot = "" - if not isDyna then - pos = floatsToString(getWorldTranslation(n)) - rot = floatsToString(getWorldRotation(n)) - end - local sca = floatsToString(getScale(n)) - local index = EntityUtility.nodeToIndex(node, parent) - local vis = getVisibility(n) - hash = string.format("%s>->%s->%s->%s->%s->%s->%s->%s", hash, name, pos, rot, sca, index, rbt, vis) - nodeCount = nodeCount + 1 - end - ) - if md5 then - return getMD5(string.format("%s%s_dMs5AsHZWy", hash, nodeCount)) - else - return string.format("%s___%s", hash, nodeCount) - end -end - ---- Queries node parents (return false to break the loop) ----@param inputNode integer ----@param func function | "function(node, name, depth) return true end" -function EntityUtility.queryNodeParents(inputNode, func) - if not type(inputNode) == "number" or not entityExists(inputNode) or func == nil then - return - end - local depth = 1 - local pNode = inputNode - while pNode ~= 0 do - if not func(pNode, getName(pNode), depth) then - break - end - pNode = getParent(pNode) - depth = depth + 1 - end -end diff --git a/lib/utility/FadeEffect.lua b/lib/utility/FadeEffect.lua deleted file mode 100644 index 79cf574..0000000 --- a/lib/utility/FadeEffect.lua +++ /dev/null @@ -1,153 +0,0 @@ ---- Royal Utility - ----@author Royal Modding ----@version 2.1.1.0 ----@date 17/02/2017 - ----@class FadeEffect -FadeEffect = {} - -FadeEffect.STATES = {} -FadeEffect.STATES.FADEIN = 1 -FadeEffect.STATES.STAY = 2 -FadeEffect.STATES.FADEOUT = 3 -FadeEffect.STATES.IDLE = 4 - -FadeEffect.ALIGNS = {} -FadeEffect.ALIGNS.LEFT = 0 -FadeEffect.ALIGNS.TOP = 0 -FadeEffect.ALIGNS.CENTER = 1 -FadeEffect.ALIGNS.RIGHT = 2 -FadeEffect.ALIGNS.BOTTOM = 2 - ----@class FadeEffectSettings -FadeEffect.defaultSettings = { - color = { - r = 1, - g = 1, - b = 1 - }, - position = { - x = 0.5, - y = 0.5 - }, - align = { - x = FadeEffect.ALIGNS.CENTER, - y = FadeEffect.ALIGNS.CENTER - }, - bold = true, - size = 0.025, - text = "Fade Effect", - shadow = false, - shadowPosition = { - x = 0, - y = 0 - }, - initialAlpha = 0, - statesTime = {1, 1, 1}, - statesAlpha = {1, 1, 0}, - loop = false -} - ----@param settings FadeEffectSettings ----@return FadeEffect -function FadeEffect:new(settings) - if FadeEffect_mt == nil then - FadeEffect_mt = Class(FadeEffect) - end - - ---@type FadeEffect - local fe = setmetatable({}, FadeEffect_mt) - - ---@type FadeEffectSettings - fe.settings = {} - - for k, v in pairs(self.defaultSettings) do - fe.settings[k] = v - end - - for k, v in pairs(settings) do - fe.settings[k] = v - end - - fe:play(fe.settings.text) - fe.state = FadeEffect.STATES.IDLE - - return fe -end - -function FadeEffect:alignText() - self.settings.position.alignedX = self.settings.position.x - self.settings.position.alignedY = self.settings.position.y - if self.settings.align.x == FadeEffect.ALIGNS.CENTER then - self.settings.position.alignedX = self.settings.position.x - (getTextWidth(self.settings.size, self.settings.text) / 2) - end - if self.settings.align.x == FadeEffect.ALIGNS.RIGHT then - self.settings.position.alignedX = self.settings.position.x - getTextWidth(self.settings.size, self.settings.text) - end - if self.settings.align.y == FadeEffect.ALIGNS.CENTER then - self.settings.position.alignedY = self.settings.position.y - (getTextHeight(self.settings.size, self.settings.text) / 2) - end - if self.settings.align.y == FadeEffect.ALIGNS.TOP then - self.settings.position.alignedY = self.settings.position.y - getTextHeight(self.settings.size, self.settings.text) - end -end - ----@param text string -function FadeEffect:setText(text) - self.settings.text = text - self:alignText() -end - ----@param text string -function FadeEffect:play(text) - if text ~= nil then - self.settings.text = text - self:alignText() - end - self.alpha = self.settings.initialAlpha - self.initialAlpha = self.settings.initialAlpha - self.state = FadeEffect.STATES.FADEIN - self.tmpStateTime = 0 -end - -function FadeEffect:stop() - self.state = FadeEffect.STATES.IDLE -end - -function FadeEffect:draw() - if self.state ~= FadeEffect.STATES.IDLE then - setTextBold(self.settings.bold) - if self.settings.shadow then - setTextColor(0, 0, 0, self.alpha) - renderText(self.settings.position.alignedX + self.settings.shadowPosition.x, self.settings.position.alignedY - self.settings.shadowPosition.y, self.settings.size, self.settings.text) - end - setTextColor(self.settings.color.r, self.settings.color.g, self.settings.color.b, self.alpha) - renderText(self.settings.position.alignedX, self.settings.position.alignedY, self.settings.size, self.settings.text) - setTextBold(false) - setTextColor(1, 1, 1, 1) - end -end - ----@param dt number -function FadeEffect:update(dt) - if self.state ~= FadeEffect.STATES.IDLE then - local stateTime = self.settings.statesTime[self.state] * 1000 - if (self.tmpStateTime + dt) >= stateTime then - self.tmpStateTime = (self.tmpStateTime + dt - stateTime) - self.alpha = self.settings.statesAlpha[self.state] - self.initialAlpha = self.alpha - self.state = self.state + 1 - if self.settings.loop and self.state == FadeEffect.STATES.IDLE then - self.state = 1 - end - else - self.tmpStateTime = self.tmpStateTime + dt - end - if self.initialAlpha == self.settings.statesAlpha[self.state] then - self.alpha = self.settings.statesAlpha[self.state] - else - self.alpha = math.abs(self.initialAlpha - (1 / stateTime) * self.tmpStateTime) - end - end -end diff --git a/lib/utility/Gameplay.lua b/lib/utility/Gameplay.lua deleted file mode 100644 index 21fc540..0000000 --- a/lib/utility/Gameplay.lua +++ /dev/null @@ -1,84 +0,0 @@ ---- Royal Utility - ----@author Royal Modding ----@version 2.1.1.0 ----@date 05/01/2021 - ----@class GameplayUtility -GameplayUtility = GameplayUtility or {} - ---- Get value of a trunk (splitshape) ----@param id integer ----@param splitType table ----@return number, number, number, number -function GameplayUtility.getTrunkValue(id, splitType) - if splitType == nil then - splitType = g_splitTypeManager:getSplitTypeByIndex(getSplitType(id)) - end - - if splitType == nil or splitType.pricePerLiter <= 0 then - return 0 - end - - local volume = getVolume(id) - local qualityScale = 1 - local lengthScale = 1 - local defoliageScale = 1 - local sizeX, sizeY, sizeZ, numConvexes, numAttachments = getSplitShapeStats(id) - - if sizeX ~= nil and volume > 0 then - local bvVolume = sizeX * sizeY * sizeZ - local volumeRatio = bvVolume / volume - local volumeQuality = 1 - math.sqrt(MathUtil.clamp((volumeRatio - 3) / 7, 0, 1)) * 0.95 -- ratio <= 3: 100%, ratio >= 10: 5% - local convexityQuality = 1 - MathUtil.clamp((numConvexes - 2) / (6 - 2), 0, 1) * 0.95 - -- 0-2: 100%:, >= 6: 5% - - local maxSize = math.max(sizeX, math.max(sizeY, sizeZ)) - -- 1m: 60%, 6-11m: 120%, 19m: 60% - if maxSize < 11 then - lengthScale = 0.6 + math.min(math.max((maxSize - 1) / 5, 0), 1) * 0.6 - else - lengthScale = 1.2 - math.min(math.max((maxSize - 11) / 8, 0), 1) * 0.6 - end - - local minQuality = math.min(convexityQuality, volumeQuality) - local maxQuality = math.max(convexityQuality, volumeQuality) - qualityScale = minQuality + (maxQuality - minQuality) * 0.3 -- use 70% of min quality - - defoliageScale = 1 - math.min(numAttachments / 15, 1) * 0.8 -- #attachments 0: 100%, >=15: 20% - end - - -- Only take 33% into account of the quality criteria on easy difficulty - qualityScale = MathUtil.lerp(1, qualityScale, g_currentMission.missionInfo.economicDifficulty / 3) - - defoliageScale = MathUtil.lerp(1, defoliageScale, g_currentMission.missionInfo.economicDifficulty / 3) - - local sellPriceMultiplier = g_currentMission.missionInfo.sellPriceMultiplier - - return volume * 1000 * splitType.pricePerLiter * qualityScale * defoliageScale * lengthScale * sellPriceMultiplier, qualityScale, defoliageScale, lengthScale -end - ---- Get the farm color ----@param farmId number ----@return number[] -function GameplayUtility.getFarmColor(farmId) - local farm = g_farmManager:getFarmById(farmId) - if farm ~= nil then - local color = Farm.COLORS[farm.color] - if color ~= nil then - return color - end - end - return {1, 1, 1, 1} -end - ---- Get the farm name ----@param farmId number ----@return string -function GameplayUtility.getFarmName(farmId) - local farm = g_farmManager:getFarmById(farmId) - if farm ~= nil then - return farm.name - end - return nil -end diff --git a/lib/utility/Interpolator.lua b/lib/utility/Interpolator.lua deleted file mode 100644 index 7a1923a..0000000 --- a/lib/utility/Interpolator.lua +++ /dev/null @@ -1,113 +0,0 @@ ---- Royal Utility - ----@author Royal Modding ----@version 2.1.1.0 ----@date 11/01/2021 - ---- Interpolators utilities class ----@class InterpolatorUtility -InterpolatorUtility = InterpolatorUtility or {} - -function InterpolatorUtility.exponential11Interpolator1(first, second, alpha) - alpha = 1 - alpha - alpha = alpha ^ 2 - return (first[1] + alpha * (second[1] - first[1])) -end - -function InterpolatorUtility.exponential11InvertedInterpolator1(first, second, alpha) - alpha = alpha ^ 2 - return (second[1] + alpha * (first[1] - second[1])) -end - -function InterpolatorUtility.exponential12Interpolator1(first, second, alpha) - alpha = 1 - alpha - alpha = alpha ^ 3 - return (first[1] + alpha * (second[1] - first[1])) -end - -function InterpolatorUtility.exponential12InvertedInterpolator1(first, second, alpha) - alpha = alpha ^ 3 - return (second[1] + alpha * (first[1] - second[1])) -end - -function InterpolatorUtility.exponential13Interpolator1(first, second, alpha) - alpha = 1 - alpha - alpha = alpha ^ 4 - return (first[1] + alpha * (second[1] - first[1])) -end - -function InterpolatorUtility.exponential13InvertedInterpolator1(first, second, alpha) - alpha = alpha ^ 4 - return (second[1] + alpha * (first[1] - second[1])) -end - -function InterpolatorUtility.exponential14Interpolator1(first, second, alpha) - alpha = 1 - alpha - alpha = alpha ^ 8 - return (first[1] + alpha * (second[1] - first[1])) -end - -function InterpolatorUtility.exponential14InvertedInterpolator1(first, second, alpha) - alpha = alpha ^ 8 - return (second[1] + alpha * (first[1] - second[1])) -end - -function InterpolatorUtility.exponential21Interpolator1(first, second, alpha) - alpha = 1 - alpha - alpha = (2 ^ alpha) - 1 - return (first[1] + alpha * (second[1] - first[1])) -end - -function InterpolatorUtility.exponential21InvertedInterpolator1(first, second, alpha) - alpha = (2 ^ alpha) - 1 - return (second[1] + alpha * (first[1] - second[1])) -end - -function InterpolatorUtility.exponential22Interpolator1(first, second, alpha) - alpha = 1 - alpha - alpha = ((3 ^ alpha) - 1) / 2 - return (first[1] + alpha * (second[1] - first[1])) -end - -function InterpolatorUtility.exponential22InvertedInterpolator1(first, second, alpha) - alpha = ((3 ^ alpha) - 1) / 2 - return (second[1] + alpha * (first[1] - second[1])) -end - -function InterpolatorUtility.exponential23Interpolator1(first, second, alpha) - alpha = 1 - alpha - alpha = ((4 ^ alpha) - 1) / 3 - return (first[1] + alpha * (second[1] - first[1])) -end - -function InterpolatorUtility.exponential23InvertedInterpolator1(first, second, alpha) - alpha = ((4 ^ alpha) - 1) / 3 - return (second[1] + alpha * (first[1] - second[1])) -end - -function InterpolatorUtility.exponential24Interpolator1(first, second, alpha) - alpha = 1 - alpha - alpha = ((8 ^ alpha) - 1) / 7 - return (first[1] + alpha * (second[1] - first[1])) -end - -function InterpolatorUtility.exponential24InvertedInterpolator1(first, second, alpha) - alpha = ((8 ^ alpha) - 1) / 7 - return (second[1] + alpha * (first[1] - second[1])) -end - -function InterpolatorUtility.catmullRomInterpolator1(first, second, beforeFirst, afterSecond, alpha) - alpha = 1 - alpha - local alpha2 = alpha * alpha - local alpha3 = alpha2 * alpha - - if beforeFirst == nil then - beforeFirst = {2 * first[1] - second[1]} - end - - if afterSecond == nil then - afterSecond = {2 * second[1] - first[1]} - end - - return 0.5 * ((2 * first[1]) + (-beforeFirst[1] + second[1]) * alpha + (2 * beforeFirst[1] - 5 * first[1] + 4 * second[1] - afterSecond[1]) * alpha2 + (-beforeFirst[1] + 3 * first[1] - 3 * second[1] + afterSecond[1]) * alpha3) -end diff --git a/lib/utility/Main.lua b/lib/utility/Main.lua deleted file mode 100644 index 2fb791d..0000000 --- a/lib/utility/Main.lua +++ /dev/null @@ -1,22 +0,0 @@ ---- Royal Utility - ----@author Royal Modding ----@version 2.1.1.0 ----@date 21/11/2020 - ---- Initialize RoyalUtility ----@param utilityDirectory string -function InitRoyalUtility(utilityDirectory) - source(Utils.getFilename("Utility.lua", utilityDirectory)) - source(Utils.getFilename("Debug.lua", utilityDirectory)) - source(Utils.getFilename("Entity.lua", utilityDirectory)) - source(Utils.getFilename("Gameplay.lua", utilityDirectory)) - source(Utils.getFilename("String.lua", utilityDirectory)) - source(Utils.getFilename("Table.lua", utilityDirectory)) - source(Utils.getFilename("Interpolator.lua", utilityDirectory)) - source(Utils.getFilename("Array.lua", utilityDirectory)) - source(Utils.getFilename("FadeEffect.lua", utilityDirectory)) - source(Utils.getFilename("DelayedCallBack.lua", utilityDirectory)) - Logging.devInfo("Royal Utility loaded successfully by " .. g_currentModName) - return true -end diff --git a/lib/utility/String.lua b/lib/utility/String.lua deleted file mode 100644 index 9fc16a9..0000000 --- a/lib/utility/String.lua +++ /dev/null @@ -1,114 +0,0 @@ ---- Royal Utility - ----@author Royal Modding ----@version 2.1.1.0 ----@date 05/01/2021 - ---- String utilities class ----@class StringUtility -StringUtility = StringUtility or {} - ---- Chars available for randomizing ----@type string[] -StringUtility.randomCharset = { - "0", - "1", - "2", - "3", - "4", - "5", - "6", - "7", - "8", - "9", - "A", - "B", - "C", - "D", - "E", - "F", - "G", - "H", - "I", - "J", - "K", - "L", - "M", - "N", - "O", - "P", - "Q", - "R", - "S", - "T", - "U", - "V", - "W", - "X", - "Y", - "Z", - "a", - "b", - "c", - "d", - "e", - "f", - "g", - "h", - "i", - "j", - "k", - "l", - "m", - "n", - "o", - "p", - "q", - "r", - "s", - "t", - "u", - "v", - "w", - "x", - "y", - "z" -} - ---- Get random string ----@param length number ----@return string -function StringUtility.random(length) - length = length or 1 - if length <= 0 then - return "" - end - return StringUtility.random(length - 1) .. StringUtility.randomCharset[math.random(1, #StringUtility.randomCharset)] -end - ---- Split a string ----@param s string ----@param sep string ----@return string[] -function StringUtility.split(s, sep) - sep = sep or ":" - local fields = {} - local pattern = string.format("([^%s]+)", sep) - s:gsub( - pattern, - function(c) - fields[#fields + 1] = c - end - ) - return fields -end - ---- Get translated text ----@param key string text key prefixed with "$l10n_" ----@return string text translated text -function StringUtility.parseI18NText(key) - if key == nil then - return "" - end - return g_i18n:convertText(key) -end diff --git a/lib/utility/Table.lua b/lib/utility/Table.lua deleted file mode 100644 index dbdaad0..0000000 --- a/lib/utility/Table.lua +++ /dev/null @@ -1,236 +0,0 @@ ---- Royal Utility - ----@author Royal Modding ----@version 2.1.1.0 ----@date 05/01/2021 - ---- Table utilities class ----@class TableUtility -TableUtility = TableUtility or {} - ---- Clone a table ----@param t table ----@return table -function TableUtility.clone(t) - local copy = {} - for k, v in pairs(t) do - if type(v) == "table" then - v = TableUtility.clone(v) - end - copy[k] = v - end - return copy -end - ---- Overwrite a table ----@param t table ----@param newTable table ----@return table -function TableUtility.overwrite(t, newTable) - t = t or {} - for k, v in pairs(newTable) do - if type(v) == "table" then - TableUtility.overwrite(t[k], v) - else - t[k] = v - end - end -end - ---- Get if an element with the given value exists ----@param t table ----@param value any ----@return boolean -function TableUtility.contains(t, value) - for _, v in pairs(t) do - if v == value then - return true - end - end - return false -end - ---- Map a table to a new table ----@param t table source table ----@param func function | "function(e) return { f1 = e.f1, f2 = e.f2 } end" mapping function ----@return table mapped mapped table -function TableUtility.map(t, func) - local mapped = {} - for k, v in pairs(t) do - mapped[k] = func(v) - end - return mapped -end - ---- Get if a matching element exists ----@param t table ----@param func function | "function(e) return true end" ----@return boolean -function TableUtility.f_contains(t, func) - for _, v in pairs(t) do - if func(v) then - return true - end - end - return false -end - ---- Get the index of element ----@param t table ----@param value any ----@return integer|nil -function TableUtility.indexOf(t, value) - for k, v in pairs(t) do - if v == value then - return k - end - end - return nil -end - ---- Get the index of matching element ----@param t table ----@param func function | "function(e) return true end" ----@return integer|nil -function TableUtility.f_indexOf(t, func) - for k, v in pairs(t) do - if func(v) then - return k - end - end - return nil -end - ---- Get the matching element ----@param t table ----@param func function | "function(e) return true end" ----@return any|nil -function TableUtility.f_find(t, func) - for _, v in pairs(t) do - if func(v) then - return v - end - end - return nil -end - ---- Get a new table with matching elements ----@param t table ----@param func function | "function(e) return true end" ----@return table -function TableUtility.f_filter(t, func) - local new = {} - for _, v in pairs(t) do - if func(v) then - table.insert(new, v) - end - end - return new -end - ---- Remove matching element ----@param t table ----@param value any ----@return boolean -function TableUtility.removeValue(t, value) - for k, v in pairs(t) do - if v == value then - table.remove(t, k) - return true - end - end - return false -end - ---- Remove matching elements ----@param t table ----@param func function | "function(e) return true end" -function TableUtility.f_remove(t, func) - for k, v in pairs(t) do - if func(v) then - table.remove(t, k) - end - end -end - ---- Count occurrences ----@param t table ----@return integer -function TableUtility.count(t) - local c = 0 - if t ~= nil then - for _ in pairs(t) do - c = c + 1 - end - end - return c -end - ---- Count occurrences ----@param t table ----@param func function | "function(e) return true end" ----@return integer -function TableUtility.f_count(t, func) - local c = 0 - if t ~= nil then - for _, v in pairs(t) do - if func(v) then - c = c + 1 - end - end - end - return c -end - ---- Concat and return nil if the sring is empty ----@param t table ----@param sep string ----@param i integer ----@param j integer ----@return string|nil -function TableUtility.concatNil(t, sep, i, j) - local res = table.concat(t, sep, i, j) - if res == "" then - res = nil - end - return res -end - ----@param table1 table ----@param table2 table ----@return boolean -function TableUtility.equals(table1, table2) - if table1 == table2 then - return true - end - - local table1Type = type(table1) - - local table2Type = type(table2) - - if table1Type ~= table2Type then - return false - end - - if table1Type ~= "table" then - return false - end - - local keySet = {} - - for key1, value1 in pairs(table1) do - local value2 = table2[key1] - if value2 == nil or TableUtility.equals(value1, value2) == false then - return false - end - keySet[key1] = true - end - - for key2, _ in pairs(table2) do - if not keySet[key2] then - return false - end - end - - return true -end diff --git a/lib/utility/UtilityArray.lua b/lib/utility/UtilityArray.lua deleted file mode 100644 index 1662ea8..0000000 --- a/lib/utility/UtilityArray.lua +++ /dev/null @@ -1,46 +0,0 @@ ---- Royal Utility - ----@author Royal Modding ----@version 1.8.1.0 ----@date 26/02/2021 - ----@alias Array table tables with numeric indexes only, always ordered and sequential - ---- Array utilities class built with performances in mind (with 'array' we mean tables with numeric indexes only, always ordered and sequential) ----@class ArrayUtility -ArrayUtility = ArrayUtility or {} - ---- Remove matching elements from an array ----@param array Array ----@param removeFunc fun(array: Array, index: number, moveAt: number): boolean | "function(array, index, moveAt) local element = array[index] return true end" ----@return Array -function ArrayUtility.remove(array, removeFunc) - local moveAt, length = 1, #array - for index = 1, length do - if removeFunc(array, index, moveAt) then - array[index] = nil - else - -- move kept element's value to moveAt's position, if it's not already there - if (index ~= moveAt) then - array[moveAt] = array[index] - array[index] = nil - end - -- increment position of where we'll place the next kept value - moveAt = moveAt + 1 - end - end - return array -end - ---- Remove element at the given index from an array ----@param array Array ----@param index number ----@return Array -function ArrayUtility.removeAt(array, index) - ArrayUtility.remove( - array, - function(_, i) - return index == i - end - ) -end diff --git a/lib/utility/UtilityDebug.lua b/lib/utility/UtilityDebug.lua deleted file mode 100644 index 7b7282c..0000000 --- a/lib/utility/UtilityDebug.lua +++ /dev/null @@ -1,334 +0,0 @@ ---- Royal Utility - ----@author Royal Modding ----@version 1.8.1.0 ----@date 05/01/2021 - ---- Render a table (for debugging purpose) ----@param posX number ----@param posY number ----@param textSize number ----@param inputTable table ----@param maxDepth integer|nil ----@param hideFunc boolean|nil -function Utility.renderTable(posX, posY, textSize, inputTable, maxDepth, hideFunc) - inputTable = inputTable or {tableIs = "nil"} - hideFunc = hideFunc or false - maxDepth = maxDepth or 2 - - local function renderTableRecursively(x, t, depth, i) - if depth >= maxDepth then - return i - end - for k, v in pairs(t) do - local vType = type(v) - if not hideFunc or vType ~= "function" then - local offset = i * textSize * 1.05 - setTextAlignment(RenderText.ALIGN_RIGHT) - renderText(x, posY - offset, textSize, tostring(k) .. " :") - setTextAlignment(RenderText.ALIGN_LEFT) - if vType ~= "table" then - renderText(x, posY - offset, textSize, " " .. tostring(v)) - end - i = i + 1 - if vType == "table" then - i = renderTableRecursively(x + textSize * 1.8, v, depth + 1, i) - end - end - end - return i - end - - local i = 0 - setTextColor(1, 1, 1, 1) - setTextBold(false) - textSize = getCorrectTextSize(textSize) - for k, v in pairs(inputTable) do - local vType = type(v) - if not hideFunc or vType ~= "function" then - local offset = i * textSize * 1.05 - setTextAlignment(RenderText.ALIGN_RIGHT) - renderText(posX, posY - offset, textSize, tostring(k) .. " :") - setTextAlignment(RenderText.ALIGN_LEFT) - if vType ~= "table" then - renderText(posX, posY - offset, textSize, " " .. tostring(v)) - end - i = i + 1 - if vType == "table" then - i = renderTableRecursively(posX + textSize * 1.8, v, 1, i) - end - end - end -end - ---- Render a node hierarchy (for debugging purpose) ----@param posX number ----@param posY number ----@param textSize number ----@param inputNode integer ----@param maxDepth integer|nil -function Utility.renderNodeHierarchy(posX, posY, textSize, inputNode, maxDepth) - if inputNode == nil or inputNode == 0 then - return - end - if type(inputNode) == "number" and entityExists(inputNode) then - maxDepth = maxDepth or math.huge - - local function renderNodeHierarchyRecursively(x, node, depth, i) - if depth >= maxDepth then - return i - end - local offset = i * textSize * 1.05 - local _, className = Utility.getObjectClass(node) - renderText(x, posY - offset, textSize, string.format("%s (%s)", getName(node), className)) - i = i + 1 - for ni = 0, getNumOfChildren(node) - 1 do - i = renderNodeHierarchyRecursively(x + textSize * 1.8, getChildAt(node, ni), depth + 1, i) - end - return i - end - - local i = 1 - setTextColor(1, 1, 1, 1) - setTextBold(false) - textSize = getCorrectTextSize(textSize) - local _, className = Utility.getObjectClass(inputNode) - renderText(posX, posY, textSize, string.format("%s (%s)", getName(inputNode), className)) - for ni = 0, getNumOfChildren(inputNode) - 1 do - i = renderNodeHierarchyRecursively(posX + textSize * 1.8, getChildAt(inputNode, ni), 1, i) - end - end -end - ---- Draw a rectangle (for debugging purpose) ----@param node integer ref node ----@param minX number minX ----@param maxX number maxX ----@param minZ number minZ ----@param maxZ number maxZ ----@param yOffset number height offset ----@param alignToGround boolean alignToGround ----@param r number r ----@param g number g ----@param b number b ----@param ar number r color if active ----@param ag number g color if active ----@param ab number b color if active ----@param active boolean active? -function Utility.drawDebugRectangle(node, minX, maxX, minZ, maxZ, yOffset, alignToGround, r, g, b, ar, ag, ab, active) - if active then - r, g, b = ar, ag, ab - end - - local leftFrontX, leftFrontY, leftFrontZ = localToWorld(node, minX, yOffset, maxZ) - local rightFrontX, rightFrontY, rightFrontZ = localToWorld(node, maxX, yOffset, maxZ) - - local leftBackX, leftBackY, leftBackZ = localToWorld(node, minX, yOffset, minZ) - local rightBackX, rightBackY, rightBackZ = localToWorld(node, maxX, yOffset, minZ) - - if alignToGround then - leftFrontY = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, leftFrontX, 0, leftFrontZ) + yOffset + 0.1 - rightFrontY = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, rightFrontX, 0, rightFrontZ) + yOffset + 0.1 - leftBackY = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, leftBackX, 0, leftBackZ) + yOffset + 0.1 - rightBackY = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, rightBackX, 0, rightBackZ) + yOffset + 0.1 - end - - drawDebugLine(leftFrontX, leftFrontY, leftFrontZ, r, g, b, rightFrontX, rightFrontY, rightFrontZ, r, g, b) - drawDebugLine(rightFrontX, rightFrontY, rightFrontZ, r, g, b, rightBackX, rightBackY, rightBackZ, r, g, b) - drawDebugLine(rightBackX, rightBackY, rightBackZ, r, g, b, leftBackX, leftBackY, leftBackZ, r, g, b) - drawDebugLine(leftBackX, leftBackY, leftBackZ, r, g, b, leftFrontX, leftFrontY, leftFrontZ, r, g, b) -end - ---- Draw a cube (for debugging purpose) ----@param node integer|table ref node or ref position ----@param size number size ----@param r number r ----@param g number g ----@param b number b ----@param ar number r color if active ----@param ag number g color if active ----@param ab number b color if active ----@param active boolean active? -function Utility.drawDebugCube(node, size, r, g, b, ar, ag, ab, active) - if active then - r, g, b = ar, ag, ab - end - - local x, y, z = 0, 0, 0 - if type(node) == "table" then - x = node[1] - y = node[2] - z = node[3] - else - x, y, z = getWorldTranslation(node) - end - - local offsets = size / 2 - local corners = {} - corners[1] = {x + offsets, y + offsets, z + offsets} - corners[2] = {x + offsets, y + offsets, z - offsets} - corners[3] = {x + offsets, y - offsets, z - offsets} - corners[4] = {x + offsets, y - offsets, z + offsets} - corners[5] = {x - offsets, y + offsets, z + offsets} - corners[6] = {x - offsets, y + offsets, z - offsets} - corners[7] = {x - offsets, y - offsets, z - offsets} - corners[8] = {x - offsets, y - offsets, z + offsets} - - Utility.drawDebugLine(corners[1], corners[2], 0, 0, 0) - Utility.drawDebugLine(corners[2], corners[3], 0, 0, 0) - Utility.drawDebugLine(corners[3], corners[4], 0, 0, 0) - Utility.drawDebugLine(corners[4], corners[1], 0, 0, 0) - Utility.drawDebugLine(corners[1], corners[5], 0, 0, 0) - Utility.drawDebugLine(corners[2], corners[6], 0, 0, 0) - Utility.drawDebugLine(corners[3], corners[7], 0, 0, 0) - Utility.drawDebugLine(corners[4], corners[8], 0, 0, 0) - Utility.drawDebugLine(corners[5], corners[6], 0, 0, 0) - Utility.drawDebugLine(corners[6], corners[7], 0, 0, 0) - Utility.drawDebugLine(corners[7], corners[8], 0, 0, 0) - Utility.drawDebugLine(corners[8], corners[5], 0, 0, 0) - - Utility.drawDebugTriangle(corners[3], corners[2], corners[1], r, g, b) - Utility.drawDebugTriangle(corners[1], corners[4], corners[3], r, g, b) - Utility.drawDebugTriangle(corners[4], corners[1], corners[5], r, g, b) - Utility.drawDebugTriangle(corners[5], corners[8], corners[4], r, g, b) - Utility.drawDebugTriangle(corners[8], corners[5], corners[6], r, g, b) - Utility.drawDebugTriangle(corners[6], corners[7], corners[8], r, g, b) - Utility.drawDebugTriangle(corners[7], corners[6], corners[2], r, g, b) - Utility.drawDebugTriangle(corners[2], corners[3], corners[7], r, g, b) - Utility.drawDebugTriangle(corners[2], corners[6], corners[5], r, g, b) - Utility.drawDebugTriangle(corners[5], corners[1], corners[2], r, g, b) - Utility.drawDebugTriangle(corners[4], corners[8], corners[7], r, g, b) - Utility.drawDebugTriangle(corners[7], corners[3], corners[4], r, g, b) -end - ---- Draw a triangle (for debugging purpose) ----@param c1 table first corner position {x, y, z} ----@param c2 table second corner position {x, y, z} ----@param c3 table third corner position {x, y, z} ----@param r number r ----@param g number g ----@param b number b -function Utility.drawDebugTriangle(c1, c2, c3, r, g, b) - drawDebugTriangle(c1[1], c1[2], c1[3], c2[1], c2[2], c2[3], c3[1], c3[2], c3[3], r, g, b, 1, false) -end - ---- Draw a triangle (for debugging purpose) ----@param p1 table first point position {x, y, z} ----@param p2 table second point position {x, y, z} ----@param r number r ----@param g number g ----@param b number b -function Utility.drawDebugLine(p1, p2, r, g, b) - drawDebugLine(p1[1], p1[2], p1[3], r, g, b, p2[1], p2[2], p2[3], r, g, b) -end - ---- Render an AnimCurve (for debugging purpose) ----@param x number x position ----@param y number y position ----@param w number width ----@param h number height ----@param curve table AnimCurve object ----@param numPointsToShow? integer number of points to render -function Utility.renderAnimCurve(x, y, w, h, curve, numPointsToShow) - local graph = curve.debugGraph - local numPoints = numPointsToShow or #curve.keyframes - local minTime = 0 - local maxTime = curve.maxTime - if graph == nil then - if numPointsToShow == nil then - graph = Graph:new(numPoints, x, y, w, h, 0, 0.0001, true, "", Graph.STYLE_LINES) - graph:setColor(1, 0, 0, 1) - for i, kf in ipairs(curve.keyframes) do - local v = curve:get(kf.time) - graph:setValue(i, v) - graph:setXPosition(i, (kf.time - minTime) / (maxTime - minTime)) - graph.maxValue = math.max(graph.maxValue, v) - end - else - graph = Graph:new(numPoints + 1, x, y, w, h, 0, 0.0001, true, "", Graph.STYLE_LINES) - graph:setColor(1, 0, 0, 1) - for s = 1, numPoints + 1 do - local i = s - 1 - local v = curve:get(minTime + (maxTime - minTime) * (i / numPoints)) - graph:setValue(s, v) - graph.maxValue = math.max(graph.maxValue, v) - end - end - curve.debugGraph = graph - end - graph:draw() -end - ---- Get the loading speed meter object ----@return LoadingSpeedMeter loadingSpeedMeter -function Utility.getVehicleLoadingSpeedMeter() - if Utility.loadingSpeedMeter == nil then - ---@class LoadingSpeedMeter - Utility.loadingSpeedMeter = {} - Utility.loadingSpeedMeter.vehicles = {} - Utility.loadingSpeedMeter.filters = {} - --- Add a new filter - ---@param filterFunction function | 'function(vehicleData) return true, "meter name" end' - Utility.loadingSpeedMeter.addFilter = function(filterFunction) - table.insert(Utility.loadingSpeedMeter.filters, filterFunction) - end - Utility.overwrittenFunction( - Vehicle, - "load", - function(self, superFunc, vehicleData, asyncCallbackFunction, asyncCallbackObject, asyncCallbackArguments) - local smEnabled = false - local smName = "" - for _, filter in ipairs(Utility.loadingSpeedMeter.filters) do - smEnabled, smName = filter(vehicleData) - if smEnabled then - break - end - end - - if smEnabled then - Utility.loadingSpeedMeter.vehicles[self] = {} - Utility.loadingSpeedMeter.vehicles[self].smName = smName - Utility.loadingSpeedMeter.vehicles[self].totalStartTime = getTimeSec() - end - - local state = superFunc(self, vehicleData, asyncCallbackFunction, asyncCallbackObject, asyncCallbackArguments) - - if smEnabled then - Utility.loadingSpeedMeter.vehicles[self].totalTime = getTimeSec() - Utility.loadingSpeedMeter.vehicles[self].totalStartTime - print(string.format("[%s] Pre time: %.4f ms", Utility.loadingSpeedMeter.vehicles[self].smName, (Utility.loadingSpeedMeter.vehicles[self].preLoadTime or 0) * 1000)) - print(string.format("[%s] Load time: %.4f ms", Utility.loadingSpeedMeter.vehicles[self].smName, (Utility.loadingSpeedMeter.vehicles[self].loadTime or 0) * 1000)) - print(string.format("[%s] Post time: %.4f ms", Utility.loadingSpeedMeter.vehicles[self].smName, (Utility.loadingSpeedMeter.vehicles[self].postLoadTime or 0) * 1000)) - print(string.format("[%s] Total time: %.4f ms", Utility.loadingSpeedMeter.vehicles[self].smName, (Utility.loadingSpeedMeter.vehicles[self].totalTime or 0) * 1000)) - Utility.loadingSpeedMeter.vehicles[self] = nil - end - return state - end - ) - Utility.overwrittenStaticFunction( - SpecializationUtil, - "raiseEvent", - function(superFunc, vehicle, eventName, ...) - if Utility.loadingSpeedMeter.vehicles[vehicle] ~= nil then - if eventName == "onPreLoad" then - Utility.loadingSpeedMeter.vehicles[vehicle].preLoadStartTime = getTimeSec() - superFunc(vehicle, eventName, ...) - Utility.loadingSpeedMeter.vehicles[vehicle].preLoadTime = getTimeSec() - Utility.loadingSpeedMeter.vehicles[vehicle].preLoadStartTime - end - if eventName == "onLoad" then - Utility.loadingSpeedMeter.vehicles[vehicle].loadStartTime = getTimeSec() - superFunc(vehicle, eventName, ...) - Utility.loadingSpeedMeter.vehicles[vehicle].loadTime = getTimeSec() - Utility.loadingSpeedMeter.vehicles[vehicle].loadStartTime - end - if eventName == "onPostLoad" then - Utility.loadingSpeedMeter.vehicles[vehicle].postLoadStartTime = getTimeSec() - superFunc(vehicle, eventName, ...) - Utility.loadingSpeedMeter.vehicles[vehicle].postLoadTime = getTimeSec() - Utility.loadingSpeedMeter.vehicles[vehicle].postLoadStartTime - end - else - superFunc(vehicle, eventName, ...) - end - end - ) - end - return Utility.loadingSpeedMeter -end diff --git a/lib/utility/UtilityEntity.lua b/lib/utility/UtilityEntity.lua deleted file mode 100644 index 58de44f..0000000 --- a/lib/utility/UtilityEntity.lua +++ /dev/null @@ -1,171 +0,0 @@ ---- Royal Utility - ----@author Royal Modding ----@version 1.8.1.0 ----@date 05/01/2021 - ---- Get the class id and name of an onject ----@param objectId integer ----@return integer classId class id ----@return string className class name -function Utility.getObjectClass(objectId) - if objectId == nil then - return nil, nil - end - for name, id in pairs(ClassIds) do - if getHasClassId(objectId, id) then - return id, name - end - end -end - ---- Determines whether a node is a child of a given node ----@param childNode integer ----@param parentNode integer ----@return boolean -function Utility.isChildOf(childNode, parentNode) - if childNode == nil or childNode == 0 or parentNode == nil or parentNode == 0 then - return false - end - local pNode = getParent(childNode) - while pNode ~= 0 do - if pNode == parentNode then - return true - end - pNode = getParent(pNode) - end - return false -end - ---- Get the node index relative to root node ----@param nodeId integer id of node ----@param rootId integer id of root node ----@return string nodeIndex index of node -function Utility.nodeToIndex(nodeId, rootId) - local index = "" - if nodeId ~= nil and entityExists(nodeId) and rootId ~= nil and entityExists(rootId) and Utility.isChildOf(nodeId, rootId) then - index = tostring(getChildIndex(nodeId)) - local pNode = getParent(nodeId) - while pNode ~= rootId and pNode ~= 0 do - index = string.format("%s|%s", getChildIndex(pNode), index) - pNode = getParent(pNode) - end - end - return index -end - ---- Get a node id by an index ----@param nodeIndex string index of node ----@param rootId integer id of root node ----@return integer nodeId id of node -function Utility.indexToNode(nodeIndex, rootId) - if nodeIndex == nil or rootId == nil or not entityExists(rootId) then - return nil - end - local objectId = rootId - local indexes = StringUtility.split(nodeIndex, "|") - for _, index in pairs(indexes) do - index = tonumber(index) - if type(index) == "number" then - if getNumOfChildren(objectId) >= index then - objectId = getChildAt(objectId, index) - else - return nil - end - else - return nil - end - end - return objectId -end - ---- Queries a node hierarchy ----@param inputNode integer ----@param func function | "function(node, name, depth) end" -function Utility.queryNodeHierarchy(inputNode, func) - if not type(inputNode) == "number" or not entityExists(inputNode) or func == nil then - return - end - local function queryNodeHierarchyRecursively(node, depth) - func(node, getName(node), depth) - for i = 0, getNumOfChildren(node) - 1 do - queryNodeHierarchyRecursively(getChildAt(node, i), depth + 1) - end - end - local depth = 1 - func(inputNode, getName(inputNode), depth) - for i = 0, getNumOfChildren(inputNode) - 1 do - queryNodeHierarchyRecursively(getChildAt(inputNode, i), depth + 1) - end -end - ---- Get the hash of a node hierarchy ----@param node integer ----@param parent integer ----@param md5 boolean ----@return string hash hash of the node hierarchy -function Utility.getNodeHierarchyHash(node, parent, md5) - if not type(node) == "number" or not entityExists(node) or not type(parent) == "number" or not entityExists(parent) then - return string.format("Invalid hash node:%s parent:%s", node, parent) - end - local hash = "" - local nodeCount = 0 - - local floatsToString = function(...) - local ret = {} - for i, v in ipairs({...}) do - local tV = string.format("%.1f", v) - if tV == "-0.0" then - tV = "0.0" - end - ret[i] = tV - end - return table.concat(ret, "|") - end - - local isDyna = false - - Utility.queryNodeHierarchy( - node, - function(n, name) - local rbt = getRigidBodyType(n) - if rbt == "Dynamic" then - isDyna = true - end - local pos = "" - local rot = "" - if not isDyna then - pos = floatsToString(getWorldTranslation(n)) - rot = floatsToString(getWorldRotation(n)) - end - local sca = floatsToString(getScale(n)) - local index = Utility.nodeToIndex(node, parent) - local vis = getVisibility(n) - hash = string.format("%s>->%s->%s->%s->%s->%s->%s->%s", hash, name, pos, rot, sca, index, rbt, vis) - nodeCount = nodeCount + 1 - end - ) - if md5 then - return getMD5(string.format("%s%s_dMs5AsHZWy", hash, nodeCount)) - else - return string.format("%s___%s", hash, nodeCount) - end -end - ---- Queries node parents (return false to break the loop) ----@param inputNode integer ----@param func function | "function(node, name, depth) return true end" -function Utility.queryNodeParents(inputNode, func) - if not type(inputNode) == "number" or not entityExists(inputNode) or func == nil then - return - end - local depth = 1 - local pNode = inputNode - while pNode ~= 0 do - if not func(pNode, getName(pNode), depth) then - break - end - pNode = getParent(pNode) - depth = depth + 1 - end -end diff --git a/lib/utility/UtilityGameplay.lua b/lib/utility/UtilityGameplay.lua deleted file mode 100644 index aa70658..0000000 --- a/lib/utility/UtilityGameplay.lua +++ /dev/null @@ -1,79 +0,0 @@ ---- Royal Utility - ----@author Royal Modding ----@version 1.8.1.0 ----@date 05/01/2021 - ---- Get value of a trunk (splitshape) ----@param id integer ----@param splitType table ----@return number, number, number, number -function Utility.getTrunkValue(id, splitType) - if splitType == nil then - splitType = g_splitTypeManager:getSplitTypeByIndex(getSplitType(id)) - end - - if splitType == nil or splitType.pricePerLiter <= 0 then - return 0 - end - - local volume = getVolume(id) - local qualityScale = 1 - local lengthScale = 1 - local defoliageScale = 1 - local sizeX, sizeY, sizeZ, numConvexes, numAttachments = getSplitShapeStats(id) - - if sizeX ~= nil and volume > 0 then - local bvVolume = sizeX * sizeY * sizeZ - local volumeRatio = bvVolume / volume - local volumeQuality = 1 - math.sqrt(MathUtil.clamp((volumeRatio - 3) / 7, 0, 1)) * 0.95 -- ratio <= 3: 100%, ratio >= 10: 5% - local convexityQuality = 1 - MathUtil.clamp((numConvexes - 2) / (6 - 2), 0, 1) * 0.95 - -- 0-2: 100%:, >= 6: 5% - - local maxSize = math.max(sizeX, math.max(sizeY, sizeZ)) - -- 1m: 60%, 6-11m: 120%, 19m: 60% - if maxSize < 11 then - lengthScale = 0.6 + math.min(math.max((maxSize - 1) / 5, 0), 1) * 0.6 - else - lengthScale = 1.2 - math.min(math.max((maxSize - 11) / 8, 0), 1) * 0.6 - end - - local minQuality = math.min(convexityQuality, volumeQuality) - local maxQuality = math.max(convexityQuality, volumeQuality) - qualityScale = minQuality + (maxQuality - minQuality) * 0.3 -- use 70% of min quality - - defoliageScale = 1 - math.min(numAttachments / 15, 1) * 0.8 -- #attachments 0: 100%, >=15: 20% - end - - -- Only take 33% into account of the quality criteria on easy difficulty - qualityScale = MathUtil.lerp(1, qualityScale, g_currentMission.missionInfo.economicDifficulty / 3) - - defoliageScale = MathUtil.lerp(1, defoliageScale, g_currentMission.missionInfo.economicDifficulty / 3) - - return volume * 1000 * splitType.pricePerLiter * qualityScale * defoliageScale * lengthScale, qualityScale, defoliageScale, lengthScale -end - ---- Get the farm color ----@param farmId number ----@return number[] -function Utility.getFarmColor(farmId) - local farm = g_farmManager:getFarmById(farmId) - if farm ~= nil then - local color = Farm.COLORS[farm.color] - if color ~= nil then - return color - end - end - return {1, 1, 1, 1} -end - ---- Get the farm name ----@param farmId number ----@return string -function Utility.getFarmName(farmId) - local farm = g_farmManager:getFarmById(farmId) - if farm ~= nil then - return farm.name - end - return nil -end diff --git a/lib/utility/UtilityInterpolator.lua b/lib/utility/UtilityInterpolator.lua deleted file mode 100644 index 9a15203..0000000 --- a/lib/utility/UtilityInterpolator.lua +++ /dev/null @@ -1,113 +0,0 @@ ---- Royal Utility - ----@author Royal Modding ----@version 1.8.1.0 ----@date 11/01/2021 - ---- Interpolators utilities class ----@class InterpolatorUtility -InterpolatorUtility = InterpolatorUtility or {} - -function InterpolatorUtility.exponential11Interpolator1(first, second, alpha) - alpha = 1 - alpha - alpha = alpha ^ 2 - return (first[1] + alpha * (second[1] - first[1])) -end - -function InterpolatorUtility.exponential11InvertedInterpolator1(first, second, alpha) - alpha = alpha ^ 2 - return (second[1] + alpha * (first[1] - second[1])) -end - -function InterpolatorUtility.exponential12Interpolator1(first, second, alpha) - alpha = 1 - alpha - alpha = alpha ^ 3 - return (first[1] + alpha * (second[1] - first[1])) -end - -function InterpolatorUtility.exponential12InvertedInterpolator1(first, second, alpha) - alpha = alpha ^ 3 - return (second[1] + alpha * (first[1] - second[1])) -end - -function InterpolatorUtility.exponential13Interpolator1(first, second, alpha) - alpha = 1 - alpha - alpha = alpha ^ 4 - return (first[1] + alpha * (second[1] - first[1])) -end - -function InterpolatorUtility.exponential13InvertedInterpolator1(first, second, alpha) - alpha = alpha ^ 4 - return (second[1] + alpha * (first[1] - second[1])) -end - -function InterpolatorUtility.exponential14Interpolator1(first, second, alpha) - alpha = 1 - alpha - alpha = alpha ^ 8 - return (first[1] + alpha * (second[1] - first[1])) -end - -function InterpolatorUtility.exponential14InvertedInterpolator1(first, second, alpha) - alpha = alpha ^ 8 - return (second[1] + alpha * (first[1] - second[1])) -end - -function InterpolatorUtility.exponential21Interpolator1(first, second, alpha) - alpha = 1 - alpha - alpha = (2 ^ alpha) - 1 - return (first[1] + alpha * (second[1] - first[1])) -end - -function InterpolatorUtility.exponential21InvertedInterpolator1(first, second, alpha) - alpha = (2 ^ alpha) - 1 - return (second[1] + alpha * (first[1] - second[1])) -end - -function InterpolatorUtility.exponential22Interpolator1(first, second, alpha) - alpha = 1 - alpha - alpha = ((3 ^ alpha) - 1) / 2 - return (first[1] + alpha * (second[1] - first[1])) -end - -function InterpolatorUtility.exponential22InvertedInterpolator1(first, second, alpha) - alpha = ((3 ^ alpha) - 1) / 2 - return (second[1] + alpha * (first[1] - second[1])) -end - -function InterpolatorUtility.exponential23Interpolator1(first, second, alpha) - alpha = 1 - alpha - alpha = ((4 ^ alpha) - 1) / 3 - return (first[1] + alpha * (second[1] - first[1])) -end - -function InterpolatorUtility.exponential23InvertedInterpolator1(first, second, alpha) - alpha = ((4 ^ alpha) - 1) / 3 - return (second[1] + alpha * (first[1] - second[1])) -end - -function InterpolatorUtility.exponential24Interpolator1(first, second, alpha) - alpha = 1 - alpha - alpha = ((8 ^ alpha) - 1) / 7 - return (first[1] + alpha * (second[1] - first[1])) -end - -function InterpolatorUtility.exponential24InvertedInterpolator1(first, second, alpha) - alpha = ((8 ^ alpha) - 1) / 7 - return (second[1] + alpha * (first[1] - second[1])) -end - -function InterpolatorUtility.catmullRomInterpolator1(first, second, beforeFirst, afterSecond, alpha) - alpha = 1 - alpha - local alpha2 = alpha * alpha - local alpha3 = alpha2 * alpha - - if beforeFirst == nil then - beforeFirst = {2 * first[1] - second[1]} - end - - if afterSecond == nil then - afterSecond = {2 * second[1] - first[1]} - end - - return 0.5 * ((2 * first[1]) + (-beforeFirst[1] + second[1]) * alpha + (2 * beforeFirst[1] - 5 * first[1] + 4 * second[1] - afterSecond[1]) * alpha2 + (-beforeFirst[1] + 3 * first[1] - 3 * second[1] + afterSecond[1]) * alpha3) -end diff --git a/lib/utility/UtilityString.lua b/lib/utility/UtilityString.lua deleted file mode 100644 index 6eb09e1..0000000 --- a/lib/utility/UtilityString.lua +++ /dev/null @@ -1,114 +0,0 @@ ---- Royal Utility - ----@author Royal Modding ----@version 1.8.1.0 ----@date 05/01/2021 - ---- String utilities class ----@class StringUtility -StringUtility = StringUtility or {} - ---- Chars available for randomizing ----@type string[] -StringUtility.randomCharset = { - "0", - "1", - "2", - "3", - "4", - "5", - "6", - "7", - "8", - "9", - "A", - "B", - "C", - "D", - "E", - "F", - "G", - "H", - "I", - "J", - "K", - "L", - "M", - "N", - "O", - "P", - "Q", - "R", - "S", - "T", - "U", - "V", - "W", - "X", - "Y", - "Z", - "a", - "b", - "c", - "d", - "e", - "f", - "g", - "h", - "i", - "j", - "k", - "l", - "m", - "n", - "o", - "p", - "q", - "r", - "s", - "t", - "u", - "v", - "w", - "x", - "y", - "z" -} - ---- Get random string ----@param length number ----@return string -function StringUtility.random(length) - length = length or 1 - if length <= 0 then - return "" - end - return StringUtility.random(length - 1) .. StringUtility.randomCharset[math.random(1, #StringUtility.randomCharset)] -end - ---- Split a string ----@param s string ----@param sep string ----@return string[] -function StringUtility.split(s, sep) - sep = sep or ":" - local fields = {} - local pattern = string.format("([^%s]+)", sep) - s:gsub( - pattern, - function(c) - fields[#fields + 1] = c - end - ) - return fields -end - ---- Get translated text ----@param key string text key prefixed with "$l10n_" ----@return string text translated text -function StringUtility.parseI18NText(key) - if key == nil then - return "" - end - return g_i18n:convertText(key) -end diff --git a/missionVehicles/newTest.xml b/missionVehicles/newTest.xml new file mode 100644 index 0000000..57e4b5a --- /dev/null +++ b/missionVehicles/newTest.xml @@ -0,0 +1,505 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modDesc.xml b/modDesc.xml index 8f427a5..4090c27 100644 --- a/modDesc.xml +++ b/modDesc.xml @@ -1,7 +1,7 @@  Mmtrx - 1.2.4.0 + 1.2.4.1 <en>Better Contracts</en> @@ -24,6 +24,12 @@ Disclaimer: All values shown in details display are ESTIMATES. You should not take them absolutely, but rather as an indication of what contracts to prefer among others. +Changelog v1.2.4.1: +- "lazy NPCs" (leave more work for contracts) can be configured on/ off +- maximum number of active contracts configurable +- indicator for active contracts with borrowed equipment +- clear / new contracts buttons in MP games only work for master user + Changelog v1.2.4.0: - Added (interim) fix for "lazy NPCs": leave more work for contracts - Allow for (future) other contract types @@ -134,8 +140,6 @@ Changelog v1.2.2.0: icon.dds - - diff --git a/scripts/RoyalMod.lua b/scripts/RoyalMod.lua new file mode 100644 index 0000000..81fc94f --- /dev/null +++ b/scripts/RoyalMod.lua @@ -0,0 +1,439 @@ +--- Royal Mod + +---@author Royal Modding +---@version 1.5.0.0 +---@date 03/12/2020 +-- Changelog: +-- v1.0.0.0 03.12.2020 initial by Royal-Modding +-- v1.1.0.0 30.12.2021: (Mmtrx) commented out vehicleTypeManagerFinalizeTypes, different in FS22 +-- v1.1.0.1 27.02.2022: (Mmtrx) delete vehicleTypeManagerFinalizeTypes, different in FS22 +-- remove mod.gameEnv, no longer reachable in FS22 +-- change addOnCreateLoadedObjects -> OnCreateObjectSystem:add() for mpSync +-- delete Mission00.loadOnCreateLoadedObjects, no more in FS22 +-- add BaseMission.onFinishedLoading +-- 13.04.2022 add check on g_vehicleTypeManager when calling mod.initialize +-- 15.05.2022 call mod.onLoad with "mission" param +-- 20.06.2022 check on TypeManager.typeName when calling mod.initialize + +--------- possible functions that mod can specify: ------------------------------------ +--[[ + -- for MP syncing: + ---@field onWriteStream fun(self: RoyalMod, streamId: integer) + ---@field onReadStream fun(self: RoyalMod, streamId: integer) + ---@field onUpdateTick fun(self: RoyalMod, dt: number) + ---@field onWriteUpdateStream fun(self: RoyalMod, streamId: integer, connection: Connection, dirtyMask: integer) + ---@field onReadUpdateStream fun(self: RoyalMod, streamId: integer, timestamp: number, connection: Connection) + + -- standard listeners: + ---@field onLoadMap fun(self: RoyalMod, mapNode: integer, mapFile: string) + ---@field onDeleteMap fun(self: RoyalMod) + ---@field onDraw fun(self: RoyalMod) + ---@field onUpdate fun(self: RoyalMod, dt: number) + ---@field onMouseEvent fun(self: RoyalMod, posX: number, posY: number, isDown: boolean, isUp: boolean, button: integer) + ---@field onKeyEvent fun(self: RoyalMod, unicode: integer, sym: integer, modifier: integer, isDown: boolean) + + -- additional Royal Mod entry points: + ---@field initialize fun(self: RoyalMod) + ---@field onValidateVehicleTypes fun(self: RoyalMod, vtm: VehicleTypeManager, addSpecialization: fun(specName: string), addSpecializationBySpecialization: fun(specName: string, requiredSpecName: string), addSpecializationByVehicleType: fun(specName: string, requiredVehicleTypeName: string), addSpecializationByFunction: fun(specName: string, func: function)) + ---@field onMissionInitialize fun(self: RoyalMod, baseDirectory: string, missionCollaborators: MissionCollaborators) + ---@field onSetMissionInfo fun(self: RoyalMod, missionInfo: MissionInfo, missionDynamicInfo: table) + ---@field onLoad fun(self: RoyalMod) + ---@field onPreLoadMap fun(self: RoyalMod, mapFile: string) + ---@field onCreateStartPoint fun(self: RoyalMod, startPointNode: integer) + ---@field onPostLoadMap fun(self: RoyalMod, mapNode: integer, mapFile: string) + ---@field onLoadSavegame fun(self: RoyalMod, savegameDirectory: string, savegameIndex: integer) + ---@field onPreLoadVehicles fun(self: RoyalMod, xmlFile: integer, resetVehicles: boolean) + ---@field onPreLoadItems fun(self: RoyalMod, xmlFile: integer) + ---@field onLoadFinished fun(self: RoyalMod) + ---@field onStartMission fun(self: RoyalMod) + ---@field onMissionStarted fun(self: RoyalMod) + ---@field onPreDeleteMap fun(self: RoyalMod) + ---@field onPreSaveSavegame fun(self: RoyalMod, savegameDirectory: string, savegameIndex: integer) + ---@field onPostSaveSavegame fun(self: RoyalMod, savegameDirectory: string, savegameIndex: integer) + ---@field onLoadHelpLine fun(self: RoyalMod): string + + + --------- predefined fields of the mod: ------------------------------------ + --@field directory string mod directory + --@field userProfileDirectory string user profile directory + --@field name string mod name + --@field mod table g_modManager mod object + --@field version string mod version + --@field author string mod author + --@field modEnv table mod scripting environment + --@field super table mod super class + --@field debug boolean mod debug state +]] +RoyalMod = {} + +---@param debug boolean defines if debug is enabled +---@param mpSync boolean defines if mp sync is enabled +---@return RoyalMod +function RoyalMod.new(debug, mpSync) + ---@type RoyalMod + local mod = {} + mod.directory = g_currentModDirectory + mod.userProfileDirectory = getUserProfileAppPath() + mod.name = g_currentModName + mod.modManagerMod = g_modManager:getModByName(mod.name) + mod.version = mod.modManagerMod.version + mod.author = mod.modManagerMod.author + mod.modEnv = getfenv() + mod.super = {} + mod.debug = debug + function debugPrint(text, ...) + if mod.debug then + Logging.info(text,...) + end + end + + if mod.debug then + g_showDevelopmentWarnings = true + g_addTestCommands = true + end + + mod.super.oldFunctions = {} + + ---@param error string + mod.super.errorHandle = function(error) + Logging.error("RoyalMod caught error from %s (%s)", mod.name, mod.version) + Logging.error(error) + end + + mod.super.getSavegameDirectory = function() + if g_currentMission ~= nil and g_currentMission.missionInfo ~= nil then + if g_currentMission.missionInfo.savegameDirectory ~= nil then + return string.format("%s/", g_currentMission.missionInfo.savegameDirectory) + end + + if g_currentMission.missionInfo.savegameIndex ~= nil then + return string.format("%ssavegame%d/", mod.userProfileDirectory, g_currentMission.missionInfo.savegameIndex) + end + end + return mod.userProfileDirectory + end + -- ------------- for MP sync handling ---------------------------------------- + if mpSync then + mod.super.sync = Object.new(g_server ~= nil, g_client ~= nil, Class(nil, Object)) + + ---@param self Object + ---@param streamId integer + mod.super.sync.writeStream = function(self, streamId) + self:superClass().writeStream(self, streamId) + if mod.onWriteStream ~= nil then + local time = netGetTime() + local offset = streamGetWriteOffset(streamId) + xpcall(mod.onWriteStream, mod.super.errorHandle, mod, streamId) + offset = streamGetWriteOffset(streamId) - offset + debugPrint("[%s] Written %.0f bits (%.0f bytes) in %s ms", mod.name, offset, offset / 8, netGetTime() - time) + end + end + ---@param self Object + ---@param streamId integer + mod.super.sync.readStream = function(self, streamId) + self:superClass().readStream(self, streamId) + if mod.onReadStream ~= nil then + local time = netGetTime() + local offset = streamGetReadOffset(streamId) + xpcall(mod.onReadStream, mod.super.errorHandle, mod, streamId) + offset = streamGetReadOffset(streamId) - offset + debugPrint("[%s] Read %.0f bits (%.0f bytes) in %s ms", mod.name, offset, offset / 8, netGetTime() - time) + end + end + ---@param self Object + ---@param dt number + mod.super.sync.updateTick = function(self, dt) + self:superClass().updateTick(self, dt) + if mod.onUpdateTick ~= nil then + xpcall(mod.onUpdateTick, mod.super.errorHandle, mod, dt) + end + end + ---@param self Object + ---@param streamId integer + ---@param connection Connection + ---@param dirtyMask integer + mod.super.sync.writeUpdateStream = function(self, streamId, connection, dirtyMask) + self:superClass().writeUpdateStream(self, streamId, connection, dirtyMask) + if mod.onWriteUpdateStream ~= nil then + xpcall(mod.onWriteUpdateStream, mod.super.errorHandle, mod, streamId, connection, dirtyMask) + end + end + ---@param self Object + ---@param streamId integer + ---@param timestamp number + ---@param connection Connection + mod.super.sync.readUpdateStream = function(self, streamId, timestamp, connection) + self:superClass().readUpdateStream(self, streamId, timestamp, connection) + if mod.onReadUpdateStream ~= nil then + xpcall(mod.onReadUpdateStream, mod.super.errorHandle, mod, streamId, timestamp, connection) + end + end + end + + ---@param _ table + ---@param mapFile string + mod.super.loadMap = function(_, mapFile) + if mod.onLoadMap ~= nil then + xpcall(mod.onLoadMap, mod.super.errorHandle, mod, mod.mapNode, mapFile) + end + end + + ---@param _ table + mod.super.deleteMap = function(_) + if mod.onDeleteMap ~= nil then + xpcall(mod.onDeleteMap, mod.super.errorHandle, mod) + end + end + + ---@param _ table + mod.super.draw = function(_) + if mod.onDraw ~= nil then + xpcall(mod.onDraw, mod.super.errorHandle, mod) + end + end + + ---@param _ table + ---@param dt number + mod.super.update = function(_, dt) + if mod.onUpdate ~= nil then + xpcall(mod.onUpdate, mod.super.errorHandle, mod, dt) + end + end + + ---@param _ table + ---@param posX number + ---@param posY number + ---@param isDown boolean + ---@param isUp boolean + ---@param button integer + mod.super.mouseEvent = function(_, posX, posY, isDown, isUp, button) + if mod.onMouseEvent ~= nil then + xpcall(mod.onMouseEvent, mod.super.errorHandle, mod, posX, posY, isDown, isUp, button) + end + end + + ---@param _ table + ---@param unicode integer + ---@param sym integer + ---@param modifier integer + ---@param isDown boolean + mod.super.keyEvent = function(_, unicode, sym, modifier, isDown) + if mod.onKeyEvent ~= nil then + xpcall(mod.onKeyEvent, mod.super.errorHandle, mod, unicode, sym, modifier, isDown) + end + end + + --g_vehicleTypeManager = TypeManager.new("vehicle", "vehicleTypes", "dataS/vehicleTypes.xml", g_specializationManager) + + mod.super.oldFunctions.TypeManagerValidateTypes = TypeManager.validateTypes + TypeManager.validateTypes = function(self, ...) + if mod.initialize ~= nil and self.typeName == "placeable" then + --- validateTypes will be called twice: + --- for vehicles and for placeables + --- g_currentMission is still nil here. All mods are loaded here + xpcall(mod.initialize, mod.super.errorHandle, mod) + end + mod.super.oldFunctions.TypeManagerValidateTypes(self, ...) + end + + mod.super.oldFunctions.Mission00new = Mission00.new + ---@param self Mission00 + ---@param baseDirectory string + ---@param customMt? table + ---@param missionCollaborators MissionCollaborators + ---@return Mission00 + Mission00.new = function(self, baseDirectory, customMt, missionCollaborators, ...) + if mod.onMissionInitialize ~= nil then + --- g_currentMission is still nil here + xpcall(mod.onMissionInitialize, mod.super.errorHandle, mod, baseDirectory, missionCollaborators) + end + return mod.super.oldFunctions.Mission00new(self, baseDirectory, customMt, missionCollaborators, ...) + end + + mod.super.oldFunctions.Mission00setMissionInfo = Mission00.setMissionInfo + ---@param self Mission00 + ---@param missionInfo FSCareerMissionInfo + ---@param missionDynamicInfo table + Mission00.setMissionInfo = function(self, missionInfo, missionDynamicInfo, ...) + g_currentMission:addLoadFinishedListener(mod.super) + g_currentMission:registerObjectToCallOnMissionStart(mod.super) + mod.super.oldFunctions.Mission00setMissionInfo(self, missionInfo, missionDynamicInfo, ...) + if mod.onSetMissionInfo ~= nil then + --- g_currentMission is no more nil here + xpcall(mod.onSetMissionInfo, mod.super.errorHandle, mod, missionInfo, missionDynamicInfo) + end + end + + mod.super.oldFunctions.Mission00load = Mission00.load + ---@param self Mission00 + Mission00.load = function(self, ...) + if mod.onLoad ~= nil then + xpcall(mod.onLoad, mod.super.errorHandle, mod, self) + end + mod.super.oldFunctions.Mission00load(self, ...) + end + + mod.super.oldFunctions.FSBaseMissionloadMap = FSBaseMission.loadMap + ---@param self FSBaseMission + ---@param mapFile string + ---@param addPhysics boolean + ---@param asyncCallbackFunction function + ---@param asyncCallbackObject table + ---@param asyncCallbackArguments table + FSBaseMission.loadMap = function(self, mapFile, addPhysics, asyncCallbackFunction, asyncCallbackObject, asyncCallbackArguments, ...) + if mod.onPreLoadMap ~= nil then + xpcall(mod.onPreLoadMap, mod.super.errorHandle, mod, mapFile) + end + mod.super.oldFunctions.FSBaseMissionloadMap(self, mapFile, addPhysics, asyncCallbackFunction, asyncCallbackObject, asyncCallbackArguments, ...) + end + + -- apparently called when careerStartPoint is created from map + mod.super.oldFunctions.Mission00onCreateStartPoint = Mission00.onCreateStartPoint + ---@param self Mission00 + ---@param startPointNode integer + Mission00.onCreateStartPoint = function(self, startPointNode, ...) + mod.super.oldFunctions.Mission00onCreateStartPoint(self, startPointNode, ...) + if mod.onCreateStartPoint ~= nil then + xpcall(mod.onCreateStartPoint, mod.super.errorHandle, mod, startPointNode) + end + end + + mod.super.oldFunctions.BaseMissionloadMapFinished = BaseMission.loadMapFinished + ---@param self BaseMission + ---@param mapNode integer + ---@param arguments table + ---@param callAsyncCallback boolean + BaseMission.loadMapFinished = function(self, mapNode, failedReason, arguments, callAsyncCallback, ...) + if mod.super.sync ~= nil then + --g_currentMission:addOnCreateLoadedObject(mod.super.sync) + g_currentMission.onCreateObjectSystem:add(mod.super.sync) + mod.super.sync:register(true) + end + mod.mapNode = mapNode + local mapFile, _, _, _ = unpack(arguments) + mod.super.oldFunctions.BaseMissionloadMapFinished(self, mapNode, failedReason, arguments, callAsyncCallback, ...) + if mod.onPostLoadMap ~= nil then + xpcall(mod.onPostLoadMap, mod.super.errorHandle, mod, mapNode, mapFile) + end + end + + mod.super.oldFunctions.Mission00loadMission00Finished = Mission00.loadMission00Finished + ---@param self Mission00 + ---@param mapNode integer + ---@param arguments table + Mission00.loadMission00Finished = function(self, mapNode, arguments, ...) + if mod.onLoadSavegame ~= nil then + xpcall(mod.onLoadSavegame, mod.super.errorHandle, mod, mod.super.getSavegameDirectory(), g_currentMission.missionInfo.savegameIndex) + end + mod.super.oldFunctions.Mission00loadMission00Finished(self, mapNode, arguments, ...) + end + + mod.super.oldFunctions.Mission00loadVehicles = Mission00.loadVehicles + ---@param self Mission00 + ---@param xmlFile integer + ---@param resetVehicles boolean + Mission00.loadVehicles = function(self, xmlFile, resetVehicles, ...) + if mod.onPreLoadVehicles ~= nil then + xpcall(mod.onPreLoadVehicles, mod.super.errorHandle, mod, xmlFile, resetVehicles) + end + mod.super.oldFunctions.Mission00loadVehicles(self, xmlFile, resetVehicles, ...) + end + + mod.super.oldFunctions.Mission00loadItems = Mission00.loadItems + ---@param self Mission00 + ---@param xmlFile integer + Mission00.loadItems = function(self, xmlFile, ...) + if mod.onPreLoadItems ~= nil then + xpcall(mod.onPreLoadItems, mod.super.errorHandle, mod, xmlFile) + end + mod.super.oldFunctions.Mission00loadItems(self, xmlFile, ...) + end + + -- not called on clients in MP games + mod.super.onLoadFinished = function() + if mod.onLoadFinished ~= nil then + xpcall(mod.onLoadFinished, mod.super.errorHandle, mod) + end + end + + mod.super.oldFunctions.BaseMissionOnFinishedLoading = BaseMission.onFinishedLoading + ---@param self BaseMission + BaseMission.onFinishedLoading = function(self, ...) + mod.super.oldFunctions.BaseMissionOnFinishedLoading(self, ...) + if mod.onFinishedLoading ~= nil then + xpcall(mod.onFinishedLoading, mod.super.errorHandle, mod) + end + end + + mod.super.oldFunctions.Mission00onStartMission = Mission00.onStartMission + ---@param self Mission00 + Mission00.onStartMission = function(self, ...) + if mod.onStartMission ~= nil then + xpcall(mod.onStartMission, mod.super.errorHandle, mod) + end + mod.super.oldFunctions.Mission00onStartMission(self, ...) + end + + mod.super.onMissionStarted = function(...) + if mod.onMissionStarted ~= nil then + xpcall(mod.onMissionStarted, mod.super.errorHandle, mod) + end + end + + mod.super.oldFunctions.Mission00delete = Mission00.delete + ---@param self Mission00 + Mission00.delete = function(self, ...) + if mod.onPreDeleteMap ~= nil then + xpcall(mod.onPreDeleteMap, mod.super.errorHandle, mod) + end + mod.super.oldFunctions.Mission00delete(self, ...) + end + + mod.super.oldFunctions.FSBaseMissionsaveSavegame = FSBaseMission.saveSavegame + ---@param self FSBaseMission + FSBaseMission.saveSavegame = function(self, ...) + if mod.onPreSaveSavegame ~= nil then + -- before all vehicles, items and onCreateObjects are saved + xpcall(mod.onPreSaveSavegame, mod.super.errorHandle, mod, mod.super.getSavegameDirectory(), g_currentMission.missionInfo.savegameIndex) + end + mod.super.oldFunctions.FSBaseMissionsaveSavegame(self, ...) + if mod.onPostSaveSavegame ~= nil then + -- after all vhicles, items and onCreateObjects are saved + xpcall(mod.onPostSaveSavegame, mod.super.errorHandle, mod, mod.super.getSavegameDirectory(), g_currentMission.missionInfo.savegameIndex) + end + end + + mod.super.oldFunctions.HelpLineManagerloadMapData = HelpLineManager.loadMapData + ---@param self HelpLineManager + ---@param xmlFile integer + ---@param missionInfo FSCareerMissionInfo + ---@return boolean + HelpLineManager.loadMapData = function(self, xmlFile, missionInfo) + if mod.super.oldFunctions.HelpLineManagerloadMapData(self, xmlFile, missionInfo) then + if mod.onLoadHelpLine ~= nil then + local success, hlFilename = xpcall(mod.onLoadHelpLine, mod.super.errorHandle, mod) + if success and hlFilename ~= nil and type(hlFilename) == "string" and hlFilename ~= "" then + self:loadFromXML(hlFilename) + for ci = 1, #self.categories do + local category = self.categories[ci] + for pi = 1, #category.pages do + local page = category.pages[pi] + for ii = 1, #page.items do + local item = page.items[ii] + if item.type == HelpLineManager.ITEM_TYPE.IMAGE then + if item.value:sub(1, 10) == "$rmModDir/" then + item.value = "$" .. mod.directory .. item.value:sub(11) + end + end + end + end + end + end + end + return true + end + end + + addModEventListener(mod.super) + return mod +end diff --git a/lib/utility/UtilityTable.lua b/scripts/TableUtility.lua similarity index 100% rename from lib/utility/UtilityTable.lua rename to scripts/TableUtility.lua diff --git a/lib/utility/Utility.lua b/scripts/Utility.lua similarity index 100% rename from lib/utility/Utility.lua rename to scripts/Utility.lua diff --git a/scripts/gui.lua b/scripts/gui.lua index f1cf015..3a763ef 100644 --- a/scripts/gui.lua +++ b/scripts/gui.lua @@ -16,6 +16,8 @@ -- v1.2.4.0 26.08.2022 allow for other (future) mission types, -- fix distorted menu page for different screen aspect ratios, -- show fruit type to harvest in contracts list +-- v.1.2.4.1 05.09.2022 indicate leased equipment for active missions +-- allow clear/new contracts button only for master user --======================================================================================================= function BetterContracts:loadGUI(canLoad, guiPath) @@ -314,6 +316,16 @@ function populateCell(frCon, list, sect, index, cell) end end profit:setVisible(showProf) + -- indicate leased equipment for active missions + if cont and cont.miss.status == AbstractMission.STATUS_RUNNING then + local indicator = cell:getAttribute("indicatorActive") + local txt = "" + if cont.miss.spawnedVehicles then + txt = g_i18n:getText("bc_leased") + end + indicator:setText(g_i18n:getText("fieldJob_active")..txt) + indicator:setVisible(true) + end end function sortList(frCon, superfunc) -- sort frCon.contracts according to sort button clicked