From 5bcb740070fcc47845d002ed7742bf50e3ebb521 Mon Sep 17 00:00:00 2001 From: Mmtrx Date: Sat, 3 Dec 2022 18:14:31 +0100 Subject: [PATCH] v1.2.6.0 - UI page for settings - show farmland owner on menu map page - show # of jobs under NPC farmer image - repackage source files --- betterContracts.lua | 593 ++++++++++++++++---------- gui/BCsettingsPage.lua | 105 +++++ gui/settingsPage.xml | 41 ++ gui/ui_2.dds | Bin 0 -> 32896 bytes l10n/l10n_br.xml | 98 +++++ l10n/l10n_cz.xml | 46 ++ l10n/l10n_de.xml | 48 ++- l10n/l10n_en.xml | 57 ++- l10n/l10n_fr.xml | 46 ++ l10n/l10n_it.xml | 46 ++ l10n/l10n_jp.xml | 46 ++ l10n/l10n_pl.xml | 46 ++ l10n/l10n_ru.xml | 48 ++- modDesc.xml | 60 ++- scripts/RoyalMod.lua | 5 - scripts/betterContractsClearEvent.lua | 67 --- scripts/betterContractsNewEvent.lua | 40 -- scripts/events.lua | 135 ++++++ scripts/gui.lua | 222 +++++++--- scripts/missionManagerFix.lua | 18 - scripts/options.lua | 345 ++++++++++----- scripts/settings.lua | 308 +++++++++++++ scripts/userint.lua | 9 +- 23 files changed, 1884 insertions(+), 545 deletions(-) create mode 100644 gui/BCsettingsPage.lua create mode 100644 gui/settingsPage.xml create mode 100644 gui/ui_2.dds create mode 100644 l10n/l10n_br.xml delete mode 100644 scripts/betterContractsClearEvent.lua delete mode 100644 scripts/betterContractsNewEvent.lua create mode 100644 scripts/events.lua delete mode 100644 scripts/missionManagerFix.lua create mode 100644 scripts/settings.lua diff --git a/betterContracts.lua b/betterContracts.lua index 2a251ff..b19a16d 100644 --- a/betterContracts.lua +++ b/betterContracts.lua @@ -31,6 +31,7 @@ -- v1.2.5.0 31.10.2022 hard mode: active miss time out at midnght. Penalty for missn cancel -- discount mode: get discounted field price, based on # of missions -- mission vehicle warnings: only if no vehicles or debug="true" +-- v1.2.6.0 30.11.2022 UI for all settings --======================================================================================================= SC = { FERTILIZER = 1, -- prices index @@ -46,12 +47,13 @@ SC = { TRANSP = 5, SUPPLY = 6, OTHER = 7, - -- hard mode: - PENALTY = 0.1, - - -- discount mode: - MAXJOBS = 5, - DISCOUNT = 0.1, + -- refresh MP: + ADMIN = 1, + FARMMANAGER = 2, + PLAYER = 3, + -- hardMode expire: + DAY = 1, + MONTH = 2, -- Gui controls: CONTROLS = { @@ -82,7 +84,7 @@ SC = { } } function debugPrint(text, ...) - if BetterContracts.debug then + if BetterContracts.config and BetterContracts.config.debug then Logging.info(text,...) end end @@ -91,20 +93,7 @@ source(Utils.getFilename("Utility.lua", g_currentModDirectory.."scripts/")) -- ---@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 - self.modSettings= getUserProfileAppPath().."modSettings/" - g_missionManager.missionMapNumChannels = 6 - self.missionUpdTimeout = 15000 - self.missionUpdTimer = 0 -- will also update on frame open of contracts page - self.turnTime = 5.0 -- estimated seconds per turn at end of each lane - self.events = {} - self.initialized = false - -- Amazon ZA-TS3200, Hardi Mega, TerraC6F, Lemken Az9, mission,grain potat Titan18 - -- default:spreader, sprayer, sower, planter, empty, harv, harv, plow, mow,lime - self.SPEEDLIMS = {15, 12, 15, 15, 0, 10, 10, 12, 20, 18} - self.WORKWIDTH = {42, 24, 6, 6, 0, 9, 3.3, 4.9, 9, 18} +function catMissionTypes(self) --[[ contract types: 1 mow_bale 2 plow @@ -116,9 +105,9 @@ function BetterContracts:initialize() 8 fertilize 9 transport 10 supplyTransport (Mod) + mission.type to BC category: harvest, spread, simple, mow, transp, supply + self.typeToCat = {4, 3, 3, 2, 1, 3, 2, 2, 5, 6} ]] - -- 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) @@ -138,6 +127,7 @@ function BetterContracts:initialize() addMapping("supplyTransport", SC.SUPPLY) -- mod by GtX addMapping("deadwood", SC.OTHER) -- Platinum DLC mission by Giants addMapping("treeTransport", SC.OTHER) -- Platinum DLC mission by Giants + addMapping("destructibleRocks", SC.OTHER) -- Platinum DLC mission by Giants addMapping("roll", SC.SIMPLE) -- roller mission by tn4799 addMapping("lime", SC.SPREAD) -- lime mission by Mmtrx for _, missionType in pairs(g_missionManager.missionTypes) do @@ -145,111 +135,10 @@ function BetterContracts:initialize() addMapping(missionType.name, SC.OTHER) -- default category for not registered mission types end end - - self.harvest = {} -- harvest missions 1 - self.spread = {} -- sow, spray, fertilize, lime 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.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" - self.catSpread = "fertilizerspreaders seeders planters sprayers sprayervehicles slurrytanks manurespreaders" - self.catSimple = "CULTIVATORS DISCHARROWS PLOWS POWERHARROWS SUBSOILERS WEEDERS ROLLERS" - self.isOn = false - self.numCont = 0 -- # of contracts in our tables - self.numHidden = 0 -- # of hidden (filtered) contracts - self.my = {} -- will hold my gui element adresses - self.sort = 0 -- sorted status: 1 cat, 2 prof, 3 permin - self.lastSort = 0 -- last sorted status - self.buttons = { - {"sortcat", g_i18n:getText("SC_sortCat")}, -- {button id, help text} - {"sortprof", g_i18n:getText("SC_sortProf")}, - {"sortpmin", g_i18n:getText("SC_sortpMin")} - } - self.npcProb = { - harvest = 1.0, - plowCultivate = 0.5, - sow = 0.5, - fertilize = 0.9, - weed = 0.9, - lime = 0.9 - } - self.npcType = {} - self.lazyNPC = false -- adjust NPC field work activity - self.hardMode = false -- penalty for canceled missions - self.discountMode = false -- get field price discount for successfull missions - self.maxActive = 3 -- max active contracts - - if g_server ~= nil then - readconfig(self) - local txt = string.format("%s read config: maxActive %d",self.name, self.maxActive) - if self.lazyNPC then txt = txt..", lazyNPC" end - if self.hardMode then txt = txt..", hardMode" end - if self.discountMode then txt = txt..", discountMode" end - debugPrint(txt) - -- 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 - -- adjust NPC activity for missions: - if self.lazyNPC then -- always false on an MP client - Utility.overwrittenFunction(FieldManager, "updateNPCField", NPCHarvest) - end - if self.hardMode then - Utility.overwrittenFunction(HarvestMission,"calculateStealingCost",harvestCalcStealing) - Utility.overwrittenFunction(AbstractMission,"calculateStealingCost",calcStealing) - Utility.overwrittenFunction(InGameMenuContractsFrame, "onButtonCancel", onButtonCancel) - Utility.appendedFunction(InGameMenuContractsFrame, "updateDetailContents", updateDetails) - Utility.appendedFunction(AbstractMission, "dismiss", dismiss) - g_messageCenter:subscribe(MessageType.DAY_CHANGED, self.onDayChanged, self) - g_messageCenter:subscribe(MessageType.HOUR_CHANGED, self.onHourChanged, self) - end - if self.discountMode then - Utility.appendedFunction(AbstractFieldMission,"finish",finish) - Utility.appendedFunction(InGameMenuMapFrame, "onClickMap", onClickFarmland) - Utility.overwrittenFunction(InGameMenuMapFrame, "onClickBuyFarmland", onClickBuyFarmland) - Utility.appendedFunction(FarmStats,"saveToXMLFile",saveToXML) - Utility.appendedFunction(FarmStats,"loadFromXMLFile",loadFromXML) - g_farmlandManager:addStateChangeListener(self) - end - end - checkOtherMods(self) - - -- to load own mission vehicles: - Utility.overwrittenFunction(MissionManager, "loadMissionVehicles", BetterContracts.loadMissionVehicles) - -- fix AbstractMission: - Utility.overwrittenFunction(AbstractMission, "new", abstractMissionNew) - - -- get addtnl mission values from server: - Utility.appendedFunction(HarvestMission, "writeStream", BetterContracts.writeStream) - Utility.appendedFunction(HarvestMission, "readStream", BetterContracts.readStream) - Utility.appendedFunction(BaleMission, "writeStream", BetterContracts.writeStream) - Utility.appendedFunction(BaleMission, "readStream", BetterContracts.readStream) - Utility.appendedFunction(TransportMission, "writeStream", BetterContracts.writeTransport) - Utility.appendedFunction(TransportMission, "readStream", BetterContracts.readTransport) - Utility.appendedFunction(AbstractMission, "writeUpdateStream", BetterContracts.writeUpdateStream) - Utility.appendedFunction(AbstractMission, "readUpdateStream", BetterContracts.readUpdateStream) - -- functions for ingame menu contracts frame: - 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("bcprint", "Print detail stats for all available missions.", "consoleCommandPrint", self) - addConsoleCommand("bcFieldGenerateMission", "Force generating a new mission for given field", "consoleGenerateFieldMission", g_missionManager) - addConsoleCommand("gsMissionLoadAllVehicles", "Loading and unloading all field mission vehicles", "consoleLoadAllFieldMissionVehicles", g_missionManager) - addConsoleCommand("gsMissionHarvestField", "Harvest a field and print the liters", "consoleHarvestField", g_missionManager) - addConsoleCommand("gsMissionTestHarvests", "Run an expansive tests for harvest missions", "consoleHarvestTests", g_missionManager) + -- check mission types + for i = #self.typeToCat+1, g_missionManager.nextMissionTypeId -1 do + Logging.warning("[%s] ignoring new mission type %s (id %s)", self.name, + g_missionManager.missionTypes[i].name, i) end end function checkOtherMods(self) @@ -267,46 +156,79 @@ function checkOtherMods(self) end end end +function registerXML(self) + self.baseXmlKey = "BetterContracts" + self.xmlSchema = XMLSchema.new(self.baseXmlKey) + self.xmlSchema:register(XMLValueType.BOOL, self.baseXmlKey.."#debug") + self.xmlSchema:register(XMLValueType.BOOL, self.baseXmlKey.."#lazyNPC") + self.xmlSchema:register(XMLValueType.BOOL, self.baseXmlKey.."#discount") + self.xmlSchema:register(XMLValueType.BOOL, self.baseXmlKey.."#hard") + + self.xmlSchema:register(XMLValueType.INT, self.baseXmlKey.."#maxActive") + self.xmlSchema:register(XMLValueType.INT, self.baseXmlKey.."#refreshMP") + self.xmlSchema:register(XMLValueType.FLOAT, self.baseXmlKey.."#reward") + self.xmlSchema:register(XMLValueType.FLOAT, self.baseXmlKey.."#lease") + + local key = self.baseXmlKey..".lazyNPC" + self.xmlSchema:register(XMLValueType.BOOL, key.."#harvest") + self.xmlSchema:register(XMLValueType.BOOL, key.."#plowCultivate") + self.xmlSchema:register(XMLValueType.BOOL, key.."#sow") + self.xmlSchema:register(XMLValueType.BOOL, key.."#weed") + self.xmlSchema:register(XMLValueType.BOOL, key.."#fertilize") + + local key = self.baseXmlKey..".discount" + self.xmlSchema:register(XMLValueType.FLOAT, key.."#perJob") + self.xmlSchema:register(XMLValueType.INT, key.."#maxJobs") + + local key = self.baseXmlKey..".hard" + self.xmlSchema:register(XMLValueType.FLOAT, key.."#penalty") + self.xmlSchema:register(XMLValueType.INT, key.."#leaseJobs") + self.xmlSchema:register(XMLValueType.INT, key.."#expire") +end function readconfig(self) - -- check for config file in modSettings/ - self.configFile = self.modSettings .. self.name..'.xml' - if not fileExists(self.configFile) then - -- create initial config file in /modSettings - local config = { - '', - '', - '', - ' ', - ' ', - '', - } - local f = createFile(self.configFile, FileAccess.WRITE) - for _, line in ipairs(config) do - fileWrite(f, line.."\n") + if g_currentMission.missionInfo.savegameDirectory == nil then return end + -- check for config file in current savegame dir + self.savegameDir = g_currentMission.missionInfo.savegameDirectory .."/" + self.configFile = self.savegameDir .. self.name..'.xml' + local xmlFile = XMLFile.loadIfExists("BCconf", self.configFile, self.xmlSchema) + if xmlFile then + -- read config parms: + local key = self.baseXmlKey + + self.config.debug = xmlFile:getValue(key.."#debug", false) + self.config.maxActive = xmlFile:getValue(key.."#maxActive", 3) + self.config.multReward= xmlFile:getValue(key.."#reward", 1.) + self.config.multLease = xmlFile:getValue(key.."#lease", 1.) + self.config.refreshMP = xmlFile:getValue(key.."#refreshMP", 2) + self.config.lazyNPC = xmlFile:getValue(key.."#lazyNPC", false) + self.config.hardMode = xmlFile:getValue(key.."#hard", false) + self.config.discountMode = xmlFile:getValue(key.."#discount", false) + if self.config.lazyNPC then + key = self.baseXmlKey..".lazyNPC" + self.config.npcHarvest = xmlFile:getValue(key.."#harvest", false) + self.config.npcPlowCultivate =xmlFile:getValue(key.."#plowCultivate", false) + self.config.npcSow = xmlFile:getValue(key.."#sow", false) + self.config.npcFertilize = xmlFile:getValue(key.."#fertilize", false) + self.config.npcWeed = xmlFile:getValue(key.."#weed", false) end - delete(f) - 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) - self.hardMode = Utils.getNoNil(getXMLBool(xmlFile, key.."#hard"), false) - self.discountMode = Utils.getNoNil(getXMLBool(xmlFile, key.."#discount"), 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) - self.npcType.weed = Utils.getNoNil(getXMLBool(xmlFile, key.."#weed"), false) + if self.config.discountMode then + key = self.baseXmlKey..".discount" + self.config.discPerJob = MathUtil.round(xmlFile:getValue(key.."#perJob", 0.05),2) + self.config.discMaxJobs = xmlFile:getValue(key.."#maxJobs", 5) + end + if self.config.hardMode then + key = self.baseXmlKey..".hard" + self.config.hardPenalty = MathUtil.round(xmlFile:getValue(key.."#penalty", 0.1),2) + self.config.hardLease = xmlFile:getValue(key.."#leaseJobs", 2) + self.config.hardExpire = xmlFile:getValue(key.."#expire", SC.MONTH) + end + xmlFile:delete() + for _,setting in ipairs(self.settings) do + setting:setValue(self.config[setting.name]) + end + else + debugPrint("[%s] config file %s not found, using default settings",self.name,self.configFile) end - delete(xmlFile) end function loadPrices(self) local prices = {} @@ -374,9 +296,209 @@ function setupMissionFilter(self) button.elements[1]:setText(self.fieldjobs[i][3]) end end +function initGui(self) + if not self:loadGUI(self.directory .. "gui/") then + Logging.warning("'%s.Gui' failed to load! Supporting files are missing.", self.name) + else + debugPrint("-------- gui loaded -----------") + end + self:fixInGameMenuPage(self.settingsPage, "pageBCSettings", "gui/ui_2.dds", + {0, 0, 128, 128}, {256,256}, nil, function () + if g_currentMission.missionDynamicInfo.isMultiplayer then + return g_currentMission.isMasterUser + end + return true + end) + + ------------------- setup my display elements ------------------------------------- + -- move farmer picture to right + local fbox = self.frCon.farmerBox + for _,v in ipairs(fbox:getDescendants()) do + if v.id ~= nil and + v.id:sub(1,6) == "farmer" then + v:move(115/1920, 0) + end + end + -- add field "profit" to all listItems + 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 *g_aspectScaleX, -12/1080 *g_aspectScaleY) -- + profit.textBold = false + profit:setVisible(false) + + -- add field "owner" to InGameMenuMapFrame farmland view: + local box = self.frMap.farmlandValueBox + local labelFarmland = box:getFirstDescendant( + function(e) return e.sourceText and + e.sourceText == g_i18n:getText("ui_farmlandScreen"):upper()..":" + end ) + local label = labelFarmland:clone(box) + label:setText(g_i18n:getText("bc_owner")) + label:setVisible(false) + + local ownerText = self.frMap.farmlandIdText:clone(box) + ownerText.textUpperCase = false + ownerText:setVisible(false) + + self.my.ownerLabel = label + self.my.ownerText = ownerText + self.frMap.farmlandValueBox:setSize(unpack(GuiUtils.getNormalizedValues( + "1000px", {g_referenceScreenWidth,g_referenceScreenHeight}))) + + -- set controls for npcbox, sortbox and their elements: + for _, name in pairs(SC.CONTROLS) do + self.my[name] = self.frCon.farmerBox:getDescendantById(name) + end + -- set callbacks for our 3 sort buttons + for _, name in ipairs({"sortcat", "sortprof", "sortpmin"}) do + self.my[name].onClickCallback = onClickSortButton + self.my[name].onHighlightCallback = onHighSortButton + self.my[name].onHighlightRemoveCallback = onRemoveSortButton + self.my[name].onFocusCallback = onHighSortButton + self.my[name].onLeaveCallback = onRemoveSortButton + end + setupMissionFilter(self) + + self.my.filterlayout:setVisible(true) + self.my.hidden:setVisible(false) + self.my.npcbox:setVisible(false) + self.my.sortbox:setVisible(false) +end + +function BetterContracts:initialize() + debugPrint("[%s] initialize(): %s", self.name,self.initialized) + if self.initialized ~= nil then return end -- run only once + self.initialized = false + self.config = { + debug = false, -- debug mode + maxActive = 3, -- max active contracts + multReward = 1., -- general reward multiplier + multLease = 1., -- general lease cost multiplier + refreshMP = SC.ADMIN, -- necessary permission to refresh contract list (MP) + lazyNPC = false, -- adjust NPC field work activity + hardMode = false, -- penalty for canceled missions + discountMode = false, -- get field price discount for successfull missions + npcHarvest = false, + npcPlowCultivate = false, + npcSow = false, + npcFertilize = false, + npcWeed = false, + discPerJob = 0.05, + discMaxJobs = 5, + hardPenalty = 0.1, -- % of total reward for missin cancel + hardLease = 2, -- # of jobs to allow borrowing equipment + hardExpire = SC.MONTH, -- or "day" + } + self.settingsByName = {} -- will hold setting objects, init by BCsetting.init() + self.settings = BCsetting.init(self) -- settings list + + g_missionManager.missionMapNumChannels = 6 + self.missionUpdTimeout = 15000 + self.missionUpdTimer = 0 -- will also update on frame open of contracts page + self.turnTime = 5.0 -- estimated seconds per turn at end of each lane + self.events = {} + -- Amazon ZA-TS3200, Hardi Mega, TerraC6F, Lemken Az9, mission,grain potat Titan18 + -- default:spreader, sprayer, sower, planter, empty, harv, harv, plow, mow,lime + self.SPEEDLIMS = {15, 12, 15, 15, 0, 10, 10, 12, 20, 18} + self.WORKWIDTH = {42, 24, 6, 6, 0, 9, 3.3, 4.9, 9, 18} + self.harvest = {} -- harvest missions 1 + self.spread = {} -- sow, spray, fertilize, lime 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.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" + self.catSpread = "fertilizerspreaders seeders planters sprayers sprayervehicles slurrytanks manurespreaders" + self.catSimple = "CULTIVATORS DISCHARROWS PLOWS POWERHARROWS SUBSOILERS WEEDERS ROLLERS" + self.isOn = false + self.numCont = 0 -- # of contracts in our tables + self.numHidden = 0 -- # of hidden (filtered) contracts + self.my = {} -- will hold my gui element adresses + self.sort = 0 -- sorted status: 1 cat, 2 prof, 3 permin + self.lastSort = 0 -- last sorted status + self.buttons = { + {"sortcat", g_i18n:getText("SC_sortCat")}, -- {button id, help text} + {"sortprof", g_i18n:getText("SC_sortProf")}, + {"sortpmin", g_i18n:getText("SC_sortpMin")} + } + self.npcProb = { + harvest = 1.0, + plowCultivate = 0.5, + sow = 0.5, + fertilize = 0.9, + weed = 0.9, + lime = 0.9 + } + catMissionTypes(self) -- init self.typeToCat[] + checkOtherMods(self) + registerXML(self) -- register xml: self.xmlSchema + + -- to show our ingame menu settings page when admin logs in: + Utility.appendedFunction(InGameMenuMultiplayerUsersFrame,"onAdminLoginSuccess",adminMP) + + -- to count and save/load # of jobs per farm per NPC + Utility.appendedFunction(AbstractFieldMission,"finish",finish) + Utility.appendedFunction(FarmStats,"saveToXMLFile",saveToXML) + Utility.appendedFunction(FarmStats,"loadFromXMLFile",loadFromXML) + Utility.appendedFunction(Farm,"writeStream",farmWrite) + Utility.appendedFunction(Farm,"readStream",farmRead) + + -- to adjust contracts reward / vehicle lease values: + Utility.overwrittenFunction(AbstractFieldMission,"calculateReward",calcReward) + Utility.overwrittenFunction(AbstractFieldMission,"calculateVehicleUseCost",calcLeaseCost) + + -- adjust NPC activity for missions: + Utility.overwrittenFunction(FieldManager, "updateNPCField", NPCHarvest) + + -- hard mode: + Utility.overwrittenFunction(HarvestMission,"calculateStealingCost",harvestCalcStealing) + Utility.overwrittenFunction(InGameMenuContractsFrame, "onButtonCancel", onButtonCancel) + Utility.appendedFunction(InGameMenuContractsFrame, "updateDetailContents", updateDetails) + Utility.appendedFunction(AbstractMission, "dismiss", dismiss) + g_messageCenter:subscribe(MessageType.DAY_CHANGED, self.onDayChanged, self) + g_messageCenter:subscribe(MessageType.HOUR_CHANGED, self.onHourChanged, self) + g_messageCenter:subscribe(MessageType.PERIOD_CHANGED, self.onPeriodChanged, self) + + -- discount mode: + -- to display discount if farmland selected / on buy dialog + Utility.appendedFunction(InGameMenuMapFrame, "onClickMap", onClickFarmland) + Utility.overwrittenFunction(InGameMenuMapFrame, "onClickBuyFarmland", onClickBuyFarmland) + -- to handle disct price on farmland buy + g_farmlandManager:addStateChangeListener(self) + + -- to load own mission vehicles: + Utility.overwrittenFunction(MissionManager, "loadMissionVehicles", BetterContracts.loadMissionVehicles) + -- flexible mission limit: + Utility.overwrittenFunction(MissionManager, "hasFarmReachedMissionLimit", hasFarmReachedMissionLimit) + -- fix AbstractMission: + Utility.overwrittenFunction(AbstractMission, "new", abstractMissionNew) + + -- get addtnl mission values from server: + Utility.appendedFunction(HarvestMission, "writeStream", BetterContracts.writeStream) + Utility.appendedFunction(HarvestMission, "readStream", BetterContracts.readStream) + Utility.appendedFunction(BaleMission, "writeStream", BetterContracts.writeStream) + Utility.appendedFunction(BaleMission, "readStream", BetterContracts.readStream) + Utility.appendedFunction(TransportMission, "writeStream", BetterContracts.writeTransport) + Utility.appendedFunction(TransportMission, "readStream", BetterContracts.readTransport) + Utility.appendedFunction(AbstractMission, "writeUpdateStream", BetterContracts.writeUpdateStream) + Utility.appendedFunction(AbstractMission, "readUpdateStream", BetterContracts.readUpdateStream) + + -- functions for ingame menu contracts frame: + 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.overwrittenFunction(InGameMenuContractsFrame, "startContract", startContract) + Utility.appendedFunction(InGameMenu, "updateButtonsPanel", updateButtonsPanel) +end function BetterContracts:onMissionInitialize(baseDirectory, missionCollaborators) - MissionManager.AI_PRICE_MULTIPLIER = 1.5 MissionManager.MISSION_GENERATION_INTERVAL = 3600000 -- every 1 game hour end @@ -391,12 +513,26 @@ function BetterContracts:onStartMission() end function BetterContracts:onPostLoadMap(mapNode, mapFile) + -- handle our config and optional settings + if g_server ~= nil then + readconfig(self) + local txt = string.format("%s read config: maxActive %d",self.name, self.config.maxActive) + if self.config.lazyNPC then txt = txt..", lazyNPC" end + if self.config.hardMode then txt = txt..", hardMode" end + if self.config.discountMode then txt = txt..", discountMode" end + debugPrint(txt) + end + if self.config.debug then + addConsoleCommand("bcprint", "Print detail stats for all available missions.", "consoleCommandPrint", self) + addConsoleCommand("bcFieldGenerateMission", "Force generating a new mission for given field", "consoleGenerateFieldMission", g_missionManager) + addConsoleCommand("gsMissionLoadAllVehicles", "Loading and unloading all field mission vehicles", "consoleLoadAllFieldMissionVehicles", g_missionManager) + addConsoleCommand("gsMissionHarvestField", "Harvest a field and print the liters", "consoleHarvestField", g_missionManager) + addConsoleCommand("gsMissionTestHarvests", "Run an expansive tests for harvest missions", "consoleHarvestTests", g_missionManager) + end -- adjust max missions - local fieldsAmount = TableUtility.count(g_fieldManager.fields) + local fieldsAmount = table.size(g_fieldManager.fields) local adjustedFieldsAmount = math.max(fieldsAmount, 45) MissionManager.MAX_MISSIONS = math.min(80, math.ceil(adjustedFieldsAmount * 0.60)) -- max missions = 60% of fields amount (minimum 45 fields) max 120 - --MissionManager.MAX_TRANSPORT_MISSIONS = math.max(math.ceil(MissionManager.MAX_MISSIONS / 15), 2) -- max transport missions is 1/15 of maximum missions but not less then 2 - --MissionManager.MAX_MISSIONS = MissionManager.MAX_MISSIONS + MissionManager.MAX_TRANSPORT_MISSIONS -- add max transport missions to max missions MissionManager.MAX_MISSIONS_PER_GENERATION = math.min(MissionManager.MAX_MISSIONS / 5, 30) -- max missions per generation = max mission / 5 but not more then 30 MissionManager.MAX_TRIES_PER_GENERATION = math.ceil(MissionManager.MAX_MISSIONS_PER_GENERATION * 1.5) -- max tries per generation 50% more then max missions per generation debugPrint("[%s] Fields amount %s (%s)", self.name, fieldsAmount, adjustedFieldsAmount) @@ -428,78 +564,59 @@ function BetterContracts:onPostLoadMap(mapNode, mapFile) self.frMap = self.gameMenu.pageMapOverview self.frMap.ingameMap.onClickMapCallback = self.frMap.onClickMap - -- check mission types - for i = #self.typeToCat+1, g_missionManager.nextMissionTypeId -1 do - Logging.warning("[%s] ignoring new mission type %s (id %s)", self.name, - g_missionManager.missionTypes[i].name, i) - end + initGui(self) -- setup my gui additions + self.initialized = true +end - -- load my gui xmls - if not self:loadGUI(true, self.directory .. "gui/") then - Logging.warning("'%s.Gui' failed to load! Supporting files are missing.", self.name) - else - debugPrint("-------- gui loaded -----------") - end +function BetterContracts:onPostSaveSavegame(saveDir, savegameIndex) + -- save our settings + debugPrint("** saving settings to %s (%d)", saveDir, savegameIndex) + self.configFile = saveDir.."/".. self.name..'.xml' + local xmlFile = XMLFile.create("BCconf", self.configFile, self.baseXmlKey, self.xmlSchema) + if xmlFile == nil then return end - ------------------- setup my display elements ------------------------------------- - -- move farmer picture to right - local fbox = self.frCon.farmerBox - for _,v in ipairs(fbox:getDescendants()) do - if v.id ~= nil and - v.id:sub(1,6) == "farmer" then - v:move(115/1920, 0) - end + local conf = self.config + local key = self.baseXmlKey + xmlFile:setBool ( key.."#debug", conf.debug) + xmlFile:setInt ( key.."#maxActive",conf.maxActive) + xmlFile:setFloat( key.."#reward", conf.multReward) + xmlFile:setFloat( key.."#lease", conf.multLease) + xmlFile:setInt ( key.."#refreshMP",conf.refreshMP) + xmlFile:setBool ( key.."#lazyNPC", conf.lazyNPC) + xmlFile:setBool ( key.."#discount", conf.discountMode) + xmlFile:setBool ( key.."#hard", conf.hardMode) + if conf.lazyNPC then + key = self.baseXmlKey .. ".lazyNPC" + xmlFile:setBool (key.."#harvest", conf.npcHarvest) + xmlFile:setBool (key.."#plowCultivate",conf.npcPlowCultivate) + xmlFile:setBool (key.."#sow", conf.npcSow) + xmlFile:setBool (key.."#weed", conf.npcWeed) + xmlFile:setBool (key.."#fertilize", conf.npcFertilize) end - -- add field "profit" to all listItems - 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 *g_aspectScaleX, -12/1080 *g_aspectScaleY) -- - profit.textBold = false - profit:setVisible(false) - - -- set controls for npcbox, sortbox and their elements: - for _, name in pairs(SC.CONTROLS) do - self.my[name] = self.frCon.farmerBox:getDescendantById(name) + if conf.discountMode then + key = self.baseXmlKey .. ".discount" + xmlFile:setFloat(key.."#perJob", conf.discPerJob) + xmlFile:setInt (key.."#maxJobs", conf.discMaxJobs) end - -- set callbacks for our 3 sort buttons - for _, name in ipairs({"sortcat", "sortprof", "sortpmin"}) do - self.my[name].onClickCallback = onClickSortButton - self.my[name].onHighlightCallback = onHighSortButton - self.my[name].onHighlightRemoveCallback = onRemoveSortButton - self.my[name].onFocusCallback = onHighSortButton - self.my[name].onLeaveCallback = onRemoveSortButton + if conf.hardMode then + key = self.baseXmlKey .. ".hard" + xmlFile:setFloat(key.."#penalty", conf.hardPenalty) + xmlFile:setInt (key.."#leaseJobs", conf.hardLease) + xmlFile:setInt (key.."#expire", conf.hardExpire) end - setupMissionFilter(self) - - -- hard mode: change text in tallybox - if self.hardMode then - updateTallyText(self.frCon.tallyBox) - end - self.my.filterlayout:setVisible(true) - self.my.hidden:setVisible(false) - self.my.npcbox:setVisible(false) - self.my.sortbox:setVisible(false) - self.initialized = true + xmlFile:save() + xmlFile:delete() end - function BetterContracts:onWriteStream(streamId) - -- write to a client when it joins - debugPrint("** writing maxActive %d ", self.maxActive) - streamWriteUInt8(streamId, self.maxActive) - streamWriteBool(streamId, self.debug) + -- write settings to a client when it joins + for _, setting in ipairs(self.settings) do + setting:writeStream(streamId) + end 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) + -- client reads our config settings when it joins + for _, setting in ipairs(self.settings) do + setting:readStream(streamId) end end function BetterContracts:onUpdate(dt) @@ -722,9 +839,21 @@ function BetterContracts.readUpdateStream(self, streamId, timestamp, connection) self.depositedLiters = streamReadFloat32(streamId) end end +function hasFarmReachedMissionLimit(self,superf,farmId) + -- overwritten from MissionManager + local maxActive = BetterContracts.config.maxActive + if maxActive == 0 then return false end + + MissionManager.ACTIVE_CONTRACT_LIMIT = maxActive + return superf(self, farmId) +end function abstractMissionNew(isServer, superf, isClient, customMt ) local self = superf(isServer, isClient, customMt) self.mission = g_currentMission -- Fix for error in AbstractMission 'self.mission' still missing in Version 1.6 return self end +function adminMP(self) + -- appended to InGameMenuMultiplayerUsersFrame:onAdminLoginSuccess() + BetterContracts.gameMenu:updatePages() +end \ No newline at end of file diff --git a/gui/BCsettingsPage.lua b/gui/BCsettingsPage.lua new file mode 100644 index 0000000..6ddc8e6 --- /dev/null +++ b/gui/BCsettingsPage.lua @@ -0,0 +1,105 @@ +--======================================================================================================= +-- BetterContracts SCRIPT +-- +-- Purpose: Enhance ingame contracts menu. +-- Author: Mmtrx +-- Changelog: +-- v1.2.6.0 30.11.2022 UI for all settings +--======================================================================================================= + +BCSettingsPage = { + CONTROLS = { + "header", + "subTitlePrefab", + "multiTextOptionPrefab", + "settingsContainer", + "boxLayout" + }, +} +local BCSettingsPage_mt = Class(BCSettingsPage, TabbedMenuFrameElement) + +function BCSettingsPage.new(target, custom_mt) + local self = TabbedMenuFrameElement.new(target, custom_mt or BCSettingsPage_mt) + self:registerControls(BCSettingsPage.CONTROLS) + self.settings = BetterContracts.settings + self.settingsBySubtitle = BCSettingsBySubtitle + return self +end +function generateGuiElements(settingsBySubTitle, parentGuiElement, genericSettingElement, genericSubTitleElement) + for _, data in ipairs(settingsBySubTitle) do + local clonedSubTitleElement = genericSubTitleElement:clone(parentGuiElement) + clonedSubTitleElement:setText(g_i18n:getText(data.title)) + FocusManager:loadElementFromCustomValues(clonedSubTitleElement) + for _, setting in ipairs(data.elements) do + local cloned = genericSettingElement:clone(parentGuiElement) + if cloned.labelElement and cloned.labelElement.setText then + cloned:setLabel(g_i18n:getText(setting.title)) + end + local toolTipElement = cloned.elements[6] + if toolTipElement then + toolTipElement:setText(g_i18n:getText(setting.tooltip)) + end + FocusManager:loadElementFromCustomValues(cloned) + end + end + parentGuiElement:invalidateLayout() +end +function linkGuiElementsAndSettings(settings, layout) + local i = 1 -- index for settings + for ix, element in ipairs(layout.elements) do + if element:isa(MultiTextOptionElement) then + settings[i]:setGuiElement(element) + i = i + 1 + end + end +end +function BCSettingsPage:onGuiSetupFinished() + BCSettingsPage:superClass().onGuiSetupFinished(self) + + self.subTitlePrefab:unlinkElement() + FocusManager:removeElement(self.subTitlePrefab) + self.multiTextOptionPrefab:unlinkElement() + FocusManager:removeElement(self.multiTextOptionPrefab) + + self.header:setText(g_i18n:getText("bc_settingsPage_title")) + generateGuiElements(self.settingsBySubtitle, + self.boxLayout,self.multiTextOptionPrefab, self.subTitlePrefab) + self.boxLayout:invalidateLayout() +end +function BCSettingsPage:onFrameOpen() + BCSettingsPage:superClass().onFrameOpen(self) + + linkGuiElementsAndSettings(self.settings, self.boxLayout) + + FocusManager:loadElementFromCustomValues(self.boxLayout) + self.boxLayout:invalidateLayout() + self:setSoundSuppressed(true) + FocusManager:setFocus(self.boxLayout) + self:setSoundSuppressed(false) +end +function BCSettingsPage:onFrameClose() + BCSettingsPage:superClass().onFrameClose(self) + --unlinkGuiElementsAndSettings(self.settings,self.boxLayout) + self.boxLayout:invalidateLayout() +end +function BCSettingsPage:updateDisabled(layout) + if g_gui:getIsGuiVisible() and g_currentMission.inGameMenu.currentPage == self then + for _, element in ipairs(layout.elements) do + if element:isa(MultiTextOptionElement) then + local isDisabledFunc = element.bc_setting.isDisabledFunc + if isDisabledFunc then + element:setDisabled(isDisabledFunc()) + end + end + end + end +end +function BCSettingsPage:onClick(state, element) + local setting = element.bc_setting + if setting == nil then return end + setting:setIx(state) + if TableUtility.contains({"lazyNPC","discountMode","hardMode"}, setting.name) then + self:updateDisabled(element.parent) + end + SettingsEvent.sendEvent(setting) +end diff --git a/gui/settingsPage.xml b/gui/settingsPage.xml new file mode 100644 index 0000000..f60dd04 --- /dev/null +++ b/gui/settingsPage.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gui/ui_2.dds b/gui/ui_2.dds new file mode 100644 index 0000000000000000000000000000000000000000..e3e9f74aa7816d13fdebcda610f756348c8d2025 GIT binary patch literal 32896 zcmeHQJ+9<75^gUH7;xkx_zIhEFu~klxA8XcLZ{eZ02}lrHd(-X03YHVVY1%vy>P;b zgbs{`{C!od7D;^ zRs8u8BfrJJ_y7FIUwbG0(AV3@=Wqz<)A1(ewoQ{_$_?<&zJ z=f|d2|Bl)_Rqpcfi~>f<)-;dTPoJ8G{(C+*%_YLsxvEO_BA*jJ2|taK=H_uBasj*G z3nF@f!lz9Gdexup%cZUpy=9!*$m4Q6r}9^sI!mhs^!9ozrd5kySFeA4R@`43`p@)` zaToPxg%j3ql;=+vul$sD=TDA<%IoL(HbvAqch!&c>-u8zYPOxD)|y{zBW&&b49fgy zE?&>o;>Pqdx+Z_XUk&lCo}RAfZQNQ^?6{QW(-S^x%1!IjaRvUg2jxTJN_FM2%)X&< zdQMTd{13*@h*QpPPUJX@|EuK3;qb~jne{*Upsf0O>O5`hs_Gi+f2uE&@*~7wHPnDDTf?wMo&wD;QbQ}&Z=i^gbUtXK%^E-TGA90EI5%GMsQhZ`OF8K86 zi+b;LE;9x7-qxXsw6k6%`lt*Z^<#N?OZ)|oZv0>4a_BtZmvPj-&yU*wa(0!j-zW1Q zc1SYbRo8kT?Ygty6Tad4sytPiqC?sGZLDsl`9FX^#R%V}l$ye9sguf!$F9G$Pwluh z{(btWhr4?Ee&=a!-4NW2Z^^nyekW|Ka&pZ-Im&rsek;Y_^e&)JBYw#FC+Sc3mvwS} z`F=3;nXU;I^|%R;4~u+=#@_Xcz$}0 z$Fleh*MI6WjDORF`K|gNWgWmWa>bYZSRR6Wem?WAk&OSh+1=g`5V;Gl|IM$Zc8BBi zJGD^1>bElgg?#$vh52v%bCmPzsQz+fR}byW>U-4t^1L4^`?dkf-B`R|9mAiu$0-?j z3%;!CY;&0i`^WC*I6p7F@1nNx*tJ_N*8Vq>|4#MI?;p4y<`~WBG5>QcKYjV1_D^lo zza{cR98?C6YUgB%1QC7+D505sWy<%h;GgQ9wx`x#&gZ8OfBFw@#JNe-89&auHU43J zqT!N}{MWYI_#x5g->m-uSS}9~%Wd%w z^-AT-0Z5{#)C>CjN798IZ5* zKT^Q+U7N~2?+xI;w%x|>M86LB|I1we4~KWOdtd+8-v8O(m8MbL{^1fQ>jrTN_9YU3 z+#axhZ3_H_@1y7c+IAa1_*v{ONLMF`xn&z z3M`NVYs6*D$M7fJ%KnqLKj(k7jmP`ozqMTBKNo+U|F=(y&s_S)3+MB9S>ODnjiV5M zl_P^zskbS=2gG03`|a-gMErrjK)^hv-|fCY>ZdK+%DaPqq$kE~(gyyK{kAS_X>S|jxOMwc@V~X)CXe>t9)De5?7qakDIOcxq|U03dF)?L`SUgU|K9eW@Pnu@KXdx- z{rx!Ze%bx9=MS@BkApo9_Bi;8@?h@uNj;J$l|Mg~{}^AF@O`WMo`UB_<-Vi&qsO0e zK7L+iLjBel*X!r>`91!1mws+({P%{j>+SUZ44(J&&o3?G8@?w<;=n%__15y0550(g zM`-QOSNO>XzG4nPcpm~T_&DF5qtJ6aD&G>SW5{p(`Z2uT=lH4k=bp2f$`_9bM)y7N zxRCgv#9r$CDC4^C2}$!p+kE_ydZ&Im0f43Us4w0I{;!_*L|}$+-8%pA{-|r-o8Crg z58nGRsK1WovBgin)1Q1V-Ex1LUrT8C9E#~-G_3F1*L}Yd>S2Q9bAD zsntKcs6_pBiTpprC+FXx{xSU1`pWTrbzzBKq5WSw{wKc<^L<^!&DvO4Ca)_)xEo$$ z_=j=bH2?iSUmYxZS)77Vi5!#HN42kv@4eT5(|?QnH$UVke;6B9!u#k{vSTJ z*LBmbyQk=7_Ljue&G7g6m;3!-h4Z1_VV+FQ2jf#pr}7^f--kcP!@Q{8#D0H1ud(kp zz07X5AEEx6KBbg;FTtPlL;sG+RllvXFUa8^h=%eW4gU}q z@iPK$ZTrbDlwKo#k%A;O(KjL~l0)W=G-{g|-j=y?$DBsOr zV^sVpbpd~$>wTZ8ybmp^>({@(`S)V;{eC8g*Zuf?*I+#9SRUiLJ=E57YVqChM*@oH zt&V!?KtuiNJPR!mu`8XG?H=&<6 zehiPL`~Ru(X81wsY$(V4u{Nc@P|r`EcItbnJkHm#^C{l9>6C|Z>x*xmvELn-ywFcP z|Ha_1uG}Qg=_kV-k0%K3=Fgb^^r=FJj5msXAij!Ty#I{--=ckv^5W6U!3)>*JEfW& z)}j7==B@Epop@Azx2AAMr7~K7%x#zBP6@ZMFO`Gsh10&)UVh8HPyb8DpH-gS*LfUOulgRPT8IY2%Fc;BVI}y%g(zbp4jwr-yu17PX;o#wkbS z_?q5RsT0|(+fV#jqzA9(pzb^Blgl6F#p8W(Pv!GEL-Zc`J=s<`fv@G=EDHQ(UTW7` zZN6>Z|Nk;%iSKP(%I`7o10?p$eIL2<6xN&Ck(1+aLM + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/l10n/l10n_cz.xml b/l10n/l10n_cz.xml index 811dea0..9642d37 100644 --- a/l10n/l10n_cz.xml +++ b/l10n/l10n_cz.xml @@ -49,5 +49,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/l10n/l10n_de.xml b/l10n/l10n_de.xml index 3561f86..d8d0b15 100644 --- a/l10n/l10n_de.xml +++ b/l10n/l10n_de.xml @@ -15,15 +15,18 @@ + - + + + @@ -50,5 +53,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/l10n/l10n_en.xml b/l10n/l10n_en.xml index 4bd0696..c5afab5 100644 --- a/l10n/l10n_en.xml +++ b/l10n/l10n_en.xml @@ -11,21 +11,25 @@ + + + + + + + + - - - - - + @@ -49,5 +53,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/l10n/l10n_fr.xml b/l10n/l10n_fr.xml index f46546d..ee4efc3 100644 --- a/l10n/l10n_fr.xml +++ b/l10n/l10n_fr.xml @@ -49,5 +49,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/l10n/l10n_it.xml b/l10n/l10n_it.xml index b97abcc..bd367c6 100644 --- a/l10n/l10n_it.xml +++ b/l10n/l10n_it.xml @@ -49,5 +49,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/l10n/l10n_jp.xml b/l10n/l10n_jp.xml index d11cb59..394bd15 100644 --- a/l10n/l10n_jp.xml +++ b/l10n/l10n_jp.xml @@ -49,5 +49,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/l10n/l10n_pl.xml b/l10n/l10n_pl.xml index 72c7bb2..6cc5db9 100644 --- a/l10n/l10n_pl.xml +++ b/l10n/l10n_pl.xml @@ -49,5 +49,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/l10n/l10n_ru.xml b/l10n/l10n_ru.xml index 77bc655..a80df95 100644 --- a/l10n/l10n_ru.xml +++ b/l10n/l10n_ru.xml @@ -39,7 +39,7 @@ - + @@ -48,5 +48,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modDesc.xml b/modDesc.xml index b740271..71a9824 100644 --- a/modDesc.xml +++ b/modDesc.xml @@ -1,7 +1,7 @@  Mmtrx - 1.2.5.0 + 1.2.6.0 <en>Better Contracts</en> @@ -152,18 +152,68 @@ Changelog v1.2.2.0: - Ajout de support pour FS22_SupplyTransportContracts, FS22_TransportMissions ]]> +
+ +
icon.dds - - - + - + + + diff --git a/scripts/RoyalMod.lua b/scripts/RoyalMod.lua index 81fc94f..6848bee 100644 --- a/scripts/RoyalMod.lua +++ b/scripts/RoyalMod.lua @@ -81,11 +81,6 @@ function RoyalMod.new(debug, mpSync) 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 diff --git a/scripts/betterContractsClearEvent.lua b/scripts/betterContractsClearEvent.lua deleted file mode 100644 index 8514282..0000000 --- a/scripts/betterContractsClearEvent.lua +++ /dev/null @@ -1,67 +0,0 @@ ----${title} - ----@author ${author} ----@version r_version_r ----@date @date 25/02/2021 - -BetterContractsClearEvent = {} -BetterContractsClearEvent_mt = Class(BetterContractsClearEvent, Event) - -InitEventClass(BetterContractsClearEvent, "BetterContractsClearEvent") - -function BetterContractsClearEvent.emptyNew() - local o = Event.new(BetterContractsClearEvent_mt) - o.className = "BetterContractsClearEvent" - return o -end - -function BetterContractsClearEvent.new() - local o = BetterContractsClearEvent.emptyNew() - return o -end - -function BetterContractsClearEvent:writeStream(_, _) -end - -function BetterContractsClearEvent:readStream(_, connection) - self:run(connection) -end - ----@param connection any -function BetterContractsClearEvent:run(connection) - if g_server ~= nil and connection:getIsServer() == false then - -- if the event is coming from a client, server have only to broadcast - BetterContractsClearEvent.sendEvent() - else - -- if the event is coming from the server, both clients and server have to delete old contracts - -- remove only inactive (status == 0) missions - - ---@type table - local missionsToDelete = {} - - for i, mission in ipairs(g_missionManager.missions) do - if mission.status == AbstractMission.STATUS_STOPPED then - missionsToDelete[i] = mission - end - end - - for _, mission in pairs(missionsToDelete) do - mission:delete() - end - - if g_currentMission.inGameMenu.isOpen and g_currentMission.inGameMenu.pageContracts.visible then - g_currentMission.inGameMenu.pageContracts:setButtonsForState() - end - end -end - -function BetterContractsClearEvent.sendEvent() - local event = BetterContractsClearEvent.new() - if g_server ~= nil then - -- server have to broadcast to all clients and himself - g_server:broadcastEvent(event, true) - else - -- clients have to send to server - g_client:getServerConnection():sendEvent(event) - end -end diff --git a/scripts/betterContractsNewEvent.lua b/scripts/betterContractsNewEvent.lua deleted file mode 100644 index a7bd312..0000000 --- a/scripts/betterContractsNewEvent.lua +++ /dev/null @@ -1,40 +0,0 @@ ----${title} - ----@author ${author} ----@version r_version_r ----@date @date 19/10/2020 - -BetterContractsNewEvent = {} -BetterContractsNewEvent_mt = Class(BetterContractsNewEvent, Event) - -InitEventClass(BetterContractsNewEvent, "BetterContractsNewEvent") - -function BetterContractsNewEvent.emptyNew() - local o = Event.new(BetterContractsNewEvent_mt) - o.className = "BetterContractsNewEvent" - return o -end - -function BetterContractsNewEvent.new() - local o = BetterContractsNewEvent.emptyNew() - return o -end - -function BetterContractsNewEvent:writeStream(_, _) -end - -function BetterContractsNewEvent:readStream(_, connection) - self:run(connection) -end - -function BetterContractsNewEvent:run(_) - if g_server ~= nil then - g_missionManager:generateMissions() - -- reset generation timer - g_missionManager.generationTimer = MissionManager.MISSION_GENERATION_INTERVAL - end -end - -function BetterContractsNewEvent.sendEvent() - g_client:getServerConnection():sendEvent(BetterContractsNewEvent.new()) -end diff --git a/scripts/events.lua b/scripts/events.lua new file mode 100644 index 0000000..8148668 --- /dev/null +++ b/scripts/events.lua @@ -0,0 +1,135 @@ +--======================================================================================================= +-- BetterContracts EVENTS +-- +-- Purpose: Enhance ingame contracts menu. +-- Functions: options for lazyNPC, hardMode, fieldDiscount +-- Author: Royal-Modding / Mmtrx +-- Changelog: +-- v1.2.5.0 31.10.2022 hard mode: active miss time out at midnght. Penalty for missn cancel +-- v1.2.6.0 30.11.2022 UI for all settings +--======================================================================================================= + +--------------------- ClearEvent ------------------------------------------------------------------------ +BetterContractsClearEvent = {} +BetterContractsClearEvent_mt = Class(BetterContractsClearEvent, Event) +InitEventClass(BetterContractsClearEvent, "BetterContractsClearEvent") + +function BetterContractsClearEvent.emptyNew() + local o = Event.new(BetterContractsClearEvent_mt) + o.className = "BetterContractsClearEvent" + return o +end +function BetterContractsClearEvent.new() + local o = BetterContractsClearEvent.emptyNew() + return o +end +function BetterContractsClearEvent:writeStream(_, _) +end +function BetterContractsClearEvent:readStream(_, connection) + self:run(connection) +end +function BetterContractsClearEvent:run(connection) + if g_server ~= nil and connection:getIsServer() == false then + -- if the event is coming from a client, server have only to broadcast + BetterContractsClearEvent.sendEvent() + else + -- if the event is coming from the server, both clients and server have to delete old contracts + -- remove only inactive (status == 0) missions + + ---@type table + local missionsToDelete = {} + + for i, mission in ipairs(g_missionManager.missions) do + if mission.status == AbstractMission.STATUS_STOPPED then + missionsToDelete[i] = mission + end + end + + for _, mission in pairs(missionsToDelete) do + mission:delete() + end + + if g_currentMission.inGameMenu.isOpen and g_currentMission.inGameMenu.pageContracts.visible then + g_currentMission.inGameMenu.pageContracts:setButtonsForState() + end + end +end +function BetterContractsClearEvent.sendEvent() + local event = BetterContractsClearEvent.new() + if g_server ~= nil then + -- server have to broadcast to all clients and himself + g_server:broadcastEvent(event, true) + else + -- clients have to send to server + g_client:getServerConnection():sendEvent(event) + end +end + +--------------------- NewEvent ------------------------------------------------------------------------- +BetterContractsNewEvent = {} +BetterContractsNewEvent_mt = Class(BetterContractsNewEvent, Event) +InitEventClass(BetterContractsNewEvent, "BetterContractsNewEvent") + +function BetterContractsNewEvent.emptyNew() + local o = Event.new(BetterContractsNewEvent_mt) + o.className = "BetterContractsNewEvent" + return o +end +function BetterContractsNewEvent.new() + local o = BetterContractsNewEvent.emptyNew() + return o +end +function BetterContractsNewEvent:writeStream(_, _) +end +function BetterContractsNewEvent:readStream(_, connection) + self:run(connection) +end +function BetterContractsNewEvent:run(_) + if g_server ~= nil then + g_missionManager:generateMissions() + -- reset generation timer + g_missionManager.generationTimer = MissionManager.MISSION_GENERATION_INTERVAL + end +end +function BetterContractsNewEvent.sendEvent() + g_client:getServerConnection():sendEvent(BetterContractsNewEvent.new()) +end + +--------------------- SettingsEvent -------------------------------------------------------------------- +SettingsEvent = {} +SettingsEvent_mt = Class(SettingsEvent, Event) +InitEventClass(SettingsEvent, "SettingsEvent") + +function SettingsEvent.emptyNew() + return Event.new(SettingsEvent_mt) +end +function SettingsEvent.new(setting) + local o = SettingsEvent.emptyNew() + o.setting = setting + return o +end +function SettingsEvent:writeStream(streamId, connection) + streamWriteString(streamId,self.setting.name) + self.setting:writeStream(streamId, connection) +end +function SettingsEvent:readStream(streamId, connection) + local name = streamReadString(streamId) + local setting = BetterContracts.settingsByName[name] + setting:readStream(streamId, connection) + self:run(connection, setting) +end +function SettingsEvent:run(connection, setting) + --- If we received this from a client, forward to other clients + if not connection:getIsServer() then + -- ignore self (connection) + g_server:broadcastEvent(SettingsEvent.new(setting), nil, connection, nil) + end +end +function SettingsEvent.sendEvent(setting) + if g_server ~= nil then + -- send to all clients + g_server:broadcastEvent(SettingsEvent.new(setting)) + else -- send to Server + g_client:getServerConnection():sendEvent(SettingsEvent.new(setting)) + end +end diff --git a/scripts/gui.lua b/scripts/gui.lua index 85db26c..46bf752 100644 --- a/scripts/gui.lua +++ b/scripts/gui.lua @@ -1,4 +1,3 @@ ----@diagnostic disable: lowercase-global --======================================================================================================= -- BetterContracts SCRIPT -- @@ -19,56 +18,139 @@ -- v1.2.4.1 05.09.2022 indicate leased equipment for active missions -- allow clear/new contracts button only for master user -- v1.2.4.3 10.10.2022 recognize FS22_LimeMission +-- v1.2.6.0 30.11.2022 UI for all settings --======================================================================================================= -function BetterContracts:loadGUI(canLoad, guiPath) - if canLoad then - local fname - -- load my gui profiles - fname = guiPath .. "guiProfiles.xml" - if fileExists(fname) then - g_gui:loadProfiles(fname) - else - canLoad = false +--- Adds a new page to the in game menu. +function BetterContracts:fixInGameMenuPage(frame, pageName, iconFile, uvs, sizeFile, + position, predicateFunc) + local inGameMenu = g_gui.screenControllers[InGameMenu] + + -- remove all to avoid warnings + for k, v in pairs({pageName}) do + inGameMenu.controlIDs[v] = nil + end + inGameMenu:registerControls({pageName}) + inGameMenu[pageName] = frame + inGameMenu.pagingElement:addElement(inGameMenu[pageName]) + inGameMenu:exposeControlsAsFields(pageName) + + if position == nil then -- should insert before contractsPage + for i = 1, #inGameMenu.pagingElement.elements do + local child = inGameMenu.pagingElement.elements[i] + if child == inGameMenu.pageContracts then + position = i + break + end end - local xmlFile, layout - -- load "SCGui.xml" - fname = guiPath .. "SCGui.xml" - if canLoad and fileExists(fname) then - xmlFile = loadXMLFile("Temp", fname) - local fbox = self.frCon.farmerBox - -- load our "npcbox" as child of farmerBox: - g_gui:loadGuiRec(xmlFile, "GUI", fbox, self.frCon) - local npcbox = fbox:getDescendantById("npcbox") - npcbox:applyScreenAlignment() - npcbox:updateAbsolutePosition() - layout = fbox:getDescendantById("layout") - layout:invalidateLayout(true) -- adjust sort buttons - delete(xmlFile) - else - canLoad = false - Logging.error("[GuiLoader %s] Required file '%s' could not be found!", self.name, fname) + end + for i = 1, #inGameMenu.pagingElement.elements do + local child = inGameMenu.pagingElement.elements[i] + if child == inGameMenu[pageName] then + table.remove(inGameMenu.pagingElement.elements, i) + table.insert(inGameMenu.pagingElement.elements, position, child) + break + end + end + for i = 1, #inGameMenu.pagingElement.pages do + local child = inGameMenu.pagingElement.pages[i] + if child.element == inGameMenu[pageName] then + table.remove(inGameMenu.pagingElement.pages, i) + table.insert(inGameMenu.pagingElement.pages, position, child) + break end - -- load "filterGui.xml" - fname = guiPath .. "filterGui.xml" - if canLoad and fileExists(fname) then - xmlFile = loadXMLFile("Temp", fname) - local cont = self.frCon.contractsContainer - g_gui:loadGuiRec(xmlFile, "GUI", cont, self.frCon) - layout = cont:getDescendantById("filterlayout") - layout:applyScreenAlignment() - layout:updateAbsolutePosition() - layout:invalidateLayout(true) -- adjust filter buttons - local hidden = cont:getDescendantById("hidden") - hidden:applyScreenAlignment() - hidden:updateAbsolutePosition() - delete(xmlFile) + end + + inGameMenu.pagingElement:updateAbsolutePosition() + inGameMenu.pagingElement:updatePageMapping() + + inGameMenu:registerPage(inGameMenu[pageName], position, predicateFunc) + local iconFileName = Utils.getFilename(iconFile, self.directory) + inGameMenu:addPageTab(inGameMenu[pageName],iconFileName,GuiUtils.getUVs(uvs, sizeFile)) + inGameMenu[pageName]:applyScreenAlignment() + inGameMenu[pageName]:updateAbsolutePosition() + + for i = 1, #inGameMenu.pageFrames do + local child = inGameMenu.pageFrames[i] + if child == inGameMenu[pageName] then + table.remove(inGameMenu.pageFrames, i) + table.insert(inGameMenu.pageFrames, position, child) + break + end + end + inGameMenu:rebuildTabList() +end +function BetterContracts:loadGUI(guiPath) + local canLoad = true + local fname + -- load my gui profiles + fname = guiPath .. "guiProfiles.xml" + if fileExists(fname) then + g_gui:loadProfiles(fname) + else + Logging.error("[GuiLoader %s] Required file '%s' could not be found!", self.name, fname) + return false + end + local xmlFile, layout + -- load "SCGui.xml" + fname = guiPath .. "SCGui.xml" + if fileExists(fname) then + xmlFile = loadXMLFile("Temp", fname) + local fbox = self.frCon.farmerBox + -- load our "npcbox" as child of farmerBox: + g_gui:loadGuiRec(xmlFile, "GUI", fbox, self.frCon) + local npcbox = fbox:getDescendantById("npcbox") + npcbox:applyScreenAlignment() + npcbox:updateAbsolutePosition() + layout = fbox:getDescendantById("layout") + layout:invalidateLayout(true) -- adjust sort buttons + delete(xmlFile) + else + Logging.error("[GuiLoader %s] Required file '%s' could not be found!", self.name, fname) + return false + end + -- load "filterGui.xml" + fname = guiPath .. "filterGui.xml" + if fileExists(fname) then + xmlFile = loadXMLFile("Temp", fname) + local cont = self.frCon.contractsContainer + g_gui:loadGuiRec(xmlFile, "GUI", cont, self.frCon) + layout = cont:getDescendantById("filterlayout") + layout:applyScreenAlignment() + layout:updateAbsolutePosition() + layout:invalidateLayout(true) -- adjust filter buttons + local hidden = cont:getDescendantById("hidden") + hidden:applyScreenAlignment() + hidden:updateAbsolutePosition() + delete(xmlFile) + else + Logging.error("[GuiLoader %s] Required file '%s' could not be found!", self.name, fname) + return false + end + + -- load "BCsettingsPage.lua" + if g_gui ~= nil and g_gui.guis.BCSettingsFrame == nil then + local luaPath = guiPath .. "BCsettingsPage.lua" + if fileExists(luaPath) then + source(luaPath) else - canLoad = false - Logging.error("[GuiLoader %s] Required file '%s' could not be found!", self.name, fname) + Logging.error("[GuiLoader %s] Required file '%s' could not be found!", self.name, luaPath) + return false end end - return canLoad + -- load "settingsPage.xml" + fname = guiPath .. "settingsPage.xml" + if fileExists(fname) then + self.settingsPage = BCSettingsPage:new() + if g_gui:loadGui(fname, "BCSettingsFrame", self.settingsPage, true) == nil then + Logging.error("[GuiLoader %s] Error loading SettingsPage", self.name) + return false + end + else + Logging.error("[GuiLoader %s] Required file '%s' could not be found!", self.name, fname) + return false + end + return true end function onFrameOpen(superself, superFunc, ...) local self = BetterContracts @@ -397,10 +479,9 @@ end function updateFarmersBox(frCon, field, npc) -- set the text values in our npcbox local self = BetterContracts - if not self.isOn then - return - end - + if not self.isOn then return end + + -- find the current contract local section, ix = frCon.contractsList:getSelectedPath() local cont, m, con = nil, nil, nil local secCons = frCon.sectionContracts[section] @@ -414,13 +495,27 @@ function updateFarmersBox(frCon, field, npc) con = self.IdToCont[m.id] end if con == nil then - print("**Error BetterContracts:updateFarmersBox() - no contract found for mission id " .. tostring(m.id)) + Logging.error("**BetterContracts:updateFarmersBox() - no contract found for mission id " .. tostring(m.id)) return end local cat = con[1] local c = con[2] self.my.npcbox:setVisible(true) + -- show # of completed jobs + if field ~= nil and npc ~= nil then + local farm = g_farmManager:getFarmById(g_currentMission.player.farmId) + if farm.stats.npcJobs == nil then + farm.stats.npcJobs = {} + end + local jobs = farm.stats.npcJobs + if jobs[npc.index] == nil then + jobs[npc.index] = 0 + end + local txt = string.format(g_i18n:getText("bc_jobsCompleted"), jobs[npc.index]) + frCon.farmerText:setText(txt) + end + -- handle non-field missions self.my.field:setText(g_i18n:getText("SC_fillType")) -- line 1 self.my.filltype:setText(c.ftype) @@ -562,25 +657,16 @@ 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 + if page.id ~= "pageContracts" or inGameMenu.newButton == nil + or not g_currentMission.missionDynamicInfo.isMultiplayer then + return end + -- disable buttons accorcing to setting refreshMP + local refresh = BetterContracts.config.refreshMP + local enable = g_currentMission.isMasterUser or refresh == SC.PLAYER + or refresh == SC.FARMMANAGER and g_currentMission:getHasPlayerPermission("farmManager") + + inGameMenu.newButton:setDisabled(not enable) + inGameMenu.clearButton:setDisabled(not enable) end function BetterContracts:radioButton(st) -- implement radiobutton behaviour: max. one sort button can be active diff --git a/scripts/missionManagerFix.lua b/scripts/missionManagerFix.lua deleted file mode 100644 index d6472b2..0000000 --- a/scripts/missionManagerFix.lua +++ /dev/null @@ -1,18 +0,0 @@ ----${title} - ----@author ${author} ----@version r_version_r ----@date 25/02/2021 - -function MissionManager:update(dt) - if g_currentMission:getIsServer() then - self.generationTimer = self.generationTimer - g_currentMission.missionInfo.timeScale * dt - self:updateMissions(dt) - if self.generationTimer <= 0 then - if #self.missions < MissionManager.MAX_MISSIONS then - self:generateMissions(dt) - end - self.generationTimer = MissionManager.MISSION_GENERATION_INTERVAL - end - end -end diff --git a/scripts/options.lua b/scripts/options.lua index 06f37d6..7b14a57 100644 --- a/scripts/options.lua +++ b/scripts/options.lua @@ -6,16 +6,18 @@ -- Author: Royal-Modding / Mmtrx -- Changelog: -- v1.2.5.0 31.10.2022 hard mode: active miss time out at midnght. Penalty for missn cancel +-- v1.2.6.0 30.11.2022 UI for all settings --======================================================================================================= --------------------- lazyNPC --------------------------------------------------------------------------- function NPCHarvest(self, superf, field, allowUpdates) - if not allowUpdates or BetterContracts.fieldToMission[field.fieldId] == nil then + if not BetterContracts.config.lazyNPC or not allowUpdates + or BetterContracts.fieldToMission[field.fieldId] == nil then superf(self, field, allowUpdates) return end - -- there is a mission offered for this field, and - local npc = BetterContracts.npcType + -- there is a mission offered for this field, lazyNPC active, and field upates allowed + local conf = BetterContracts.config local prob = BetterContracts.npcProb local limeMiss = BetterContracts.limeMission local fruitDesc, harvestReadyState, maxHarvestState, area, total, withered @@ -29,7 +31,7 @@ function NPCHarvest(self, superf, field, allowUpdates) area, total = FieldUtil.getFruitArea(x - 1, z - 1, x + 1, z - 1, x - 1, z + 1, FieldUtil.FILTER_EMPTY, FieldUtil.FILTER_EMPTY, field.fruitType, witheredState, witheredState, 0, 0, 0, false) withered = area > 0.5 * total end - if npc.harvest then + if conf.npcHarvest then -- don't let NPCs harvest harvestReadyState = fruitDesc.maxHarvestingGrowthState if fruitDesc.maxPreparingGrowthState > -1 then @@ -38,13 +40,13 @@ function NPCHarvest(self, superf, field, allowUpdates) maxHarvestState = FieldUtil.getMaxHarvestState(field, field.fruitType) if maxHarvestState == harvestReadyState then return end end - if npc.weed and not withered then + if conf.npcWeed and not withered then -- leave field with weeds for weeding/ spraying local maxWeedState = FieldUtil.getMaxWeedState(field) if maxWeedState >= 3 and math.random() < prob.weed then return end end - if npc.plowCultivate then + if conf.npcPlowCultivate then -- leave a cut field for plow/ grubber/ lime 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 @@ -55,12 +57,12 @@ function NPCHarvest(self, superf, field, allowUpdates) end end end - if npc.fertilize and not withered then + if conf.npcFertilize and not withered then local sprayFactor = FieldUtil.getSprayFactor(field) if sprayFactor < 1 and math.random() < prob.fertilize then return end end - elseif npc.sow then + elseif conf.npcSow then -- leave empty (plowed/grubbered) field for sow/ lime mission local limeFactor = FieldUtil.getLimeFactor(field) if limeMiss and limeFactor == 0 and math.random() < prob.lime then return @@ -71,41 +73,131 @@ function NPCHarvest(self, superf, field, allowUpdates) superf(self, field, allowUpdates) end ---------------------- hard mode ------------------------------------------------------------------------- -function updateTallyText(parent) - debugPrint("*** updating tallyBox ***") - local function findId(e) - return e.sourceText and - e.sourceText == g_i18n:getText("fieldJob_tally_stealing") +--------------------- reward / lease cost --------------------------------------------------------------- +function calcReward(self,superf) + return BetterContracts.config.multReward * superf(self) +end +function calcLeaseCost(self,superf) + return BetterContracts.config.multLease * superf(self) +end + +--------------------- manage npc jobs per farm ---------------------------------------------------------- +function farmWrite(self, streamId) + -- appended to Farm:writeStream() + -- write stats.npcJobs when MP syncing a farm + if self.isSpectator then return end + + local count = 0 + if self.stats.npcJobs == nil then + self.stats.npcJobs = {} + else + count = table.size(self.stats.npcJobs) -- returns 0 if table is empty end - local element = parent:getFirstDescendant(findId) - if element then - element:setText(g_i18n:getText("bc_penalty")) + streamWriteUInt8(streamId, count) -- # of job infos to follow + debugPrint("* writing %d stats.npcJobs for farm %d", count, self.farmId) + if count > 0 then + for k,v in pairs(self.stats.npcJobs) do + streamWriteUInt8(streamId, k) -- npcIndex + streamWriteUInt8(streamId, v) -- jobs[npcIndex] + end end end -function harvestCalcStealing(self,superf) - -- body - local penal = HarvestMission:superClass().calculateStealingCost(self) - local steal = superf(self) - debugPrint("BC: harvest steal/ penalty is %.1f / %.1f", steal, penal) - return steal + penal +function farmRead(self, streamId) + -- appended to Farm:readStream() + if self.isSpectator then return end + + -- read npcJobs[npcIndex] for a farm + if self.stats.npcJobs == nil then + self.stats.npcJobs = {} + end + local jobs = self.stats.npcJobs + local npcIndex + for j = 1, streamReadUInt8(streamId) do + npcIndex = streamReadUInt8(streamId) + jobs[npcIndex] = streamReadUInt8(streamId) + debugPrint(" jobs[%d] = %d (farm %d)", npcIndex, jobs[npcIndex],self.farmId) + end end -function calcStealing(self,superf) - -- calc penalty for canceled mission - if not self.success and self.isServer then - local penalty = 0 - local difficulty = 0.7 + 0.3 * g_currentMission.missionInfo.economicDifficulty - if self.reward then - penalty = self.reward * difficulty * SC.PENALTY - debugPrint("* calcStealing: diff %.1f, penalty %d", - difficulty, penalty) +function finish(self, success ) + -- appended to AbstractFieldMission:finish(success) + debugPrint("** finish() %s %s on field %s",success,self.type.name, self.field.fieldId) + local farm = g_farmManager:getFarmById(self.farmId) + if farm.stats.npcJobs == nil then + farm.stats.npcJobs = {} + end + local jobs = farm.stats.npcJobs + local npcIndex = self.field.farmland.npcIndex + + if success then + -- (always) count as valid job for this npc: + if jobs[npcIndex] == nil then + jobs[npcIndex] = 1 + else + jobs[npcIndex] = jobs[npcIndex] +1 + end + -- show notifications, if discount mode + if BetterContracts.config.discountMode and g_client + and g_currentMission:getFarmId() == self.farmId then + local discPerJob = BetterContracts.config.discPerJob + local disMax = math.min(BetterContracts.config.discMaxJobs,math.floor(0.5 / discPerJob)) + local disJobs = math.min(jobs[npcIndex], disMax) + local disct = disJobs * 100 * discPerJob + local npc = self:getNPC() + g_currentMission:addIngameNotification(FSBaseMission.INGAME_NOTIFICATION_OK, + string.format(g_i18n:getText("bc_discValue"), npc.title, disct)) + if jobs[npcIndex] >= disMax then + g_currentMission:addIngameNotification(FSBaseMission.INGAME_NOTIFICATION_OK, + string.format(g_i18n:getText("bc_maxJobs"), npc.title)) + end + end + elseif BetterContracts.config.hardMode then + -- reduce # valid jobs for this npc: + if jobs[npcIndex] == nil then + jobs[npcIndex] = 0 + else + jobs[npcIndex] = math.max(0, jobs[npcIndex] -1) end - return penalty end - return superf(self) +end +function saveToXML(self, xmlFile, key) + -- appended to FarmStats:saveToXMLFile(), self is farm.stats + local jobs = self.npcJobs + if jobs ~= nil then + xmlFile:setTable(key .. ".npcJobs.npc", jobs, + function (npcKey, npc, npcIndex) + xmlFile:setInt(npcKey .. "#index", npcIndex) + xmlFile:setInt(npcKey .. "#count", npc or 0) + end) + end +end +function loadFromXML(self, xmlFile, key) + -- appended to FarmStats:loadFromXMLFile() + self.npcJobs = {} + xmlFile:iterate(key .. ".npcJobs.npc", function (_, npcKey) + local ix = xmlFile:getInt(npcKey.."#index") + self.npcJobs[ix] = xmlFile:getInt(npcKey.."#count", 0) + end) +end + +--------------------- hard mode ------------------------------------------------------------------------- +function AbstractFieldMission:calculateStealingCost() + -- calc penalty for canceled field mission + if BetterContracts.config.hardMode and not self.success and self.reward then + return self:getReward() * BetterContracts.config.hardPenalty + end + return 0 +end +function harvestCalcStealing(self,superf) + local steal = superf(self) + local penal = 0 + if BetterContracts.config.hardMode then + penal = HarvestMission:superClass().calculateStealingCost(self) + debugPrint("BC: harvest steal/ penalty is %.1f / %.1f", steal, penal) + end + return steal + penal end function updateDetails(self, section, index) - -- hard Mode: vehicle lease cost also for canceled mission + if not BetterContracts.config.hardMode then return end local contract = nil local sectionContracts = self.sectionContracts[section] if sectionContracts ~= nil then @@ -115,11 +207,14 @@ function updateDetails(self, section, index) local mission = contract.mission local lease, penal = 0, 0 if contract.finished and not mission.success then + -- hard Mode: vehicle lease cost also for canceled mission if mission:hasLeasableVehicles() and mission.spawnedVehicles then - lease = -mission.vehicleUseCost + lease = - MathUtil.round(mission.vehicleUseCost) end + -- stealing contains our penalty value if mission.stealingCost ~= nil then - penal = -mission.stealingCost + penal = - MathUtil.round(mission.stealingCost) + self.tallyBox:getDescendantByName("stealingText"):setText(g_i18n:getText("bc_penalty")) end local total = lease + penal self.tallyBox:getDescendantByName("leaseCost"):setText(g_i18n:formatMoney(lease, 0, true, true)) @@ -128,42 +223,99 @@ function updateDetails(self, section, index) end end function dismiss(self) + -- appended to AbstractMission:dismiss() + if not BetterContracts.config.hardMode or not self.isServer then return end + -- deduct lease cost for a canceled mission - if self.isServer and not self.success and self:hasLeasableVehicles() - and self.spawnedVehicles then - self.mission:addMoney(-self.vehicleUseCost,self.farmId, - MoneyType.MISSIONS, true, true) + if self:hasLeasableVehicles() and self.spawnedVehicles then + self.mission:addMoney(-self.vehicleUseCost,self.farmId, MoneyType.MISSIONS, true, true) + end +end +function startContract(frCon, superf, wantsLease) + self = BetterContracts + local farmId = g_currentMission:getFarmId() + + -- overwrite dialog info box + if g_missionManager:hasFarmReachedMissionLimit(farmId) + and BetterContracts.config.maxActive ~= 3 then + g_gui:showInfoDialog({ + visible = true, + text = g_i18n:getText("bc_enoughMissions"), + dialogType = DialogElement.TYPE_INFO + }) + return + end + -- (hardMode) check if enough jobs complete to allow lease + if wantsLease and self.config.hardMode then + local farm = g_farmManager:getFarmById(farmId) + local contract = frCon:getSelectedContract() + local npc = contract.mission:getNPC() + local jobs = 0 + if farm.stats.npcJobs ~= nil and farm.stats.npcJobs[npc.index] ~= nil then + jobs = farm.stats.npcJobs[npc.index] + end + if jobs < self.config.hardLease then + local txt = string.format(g_i18n:getText("bc_leaseNotEnough"), + self.config.hardLease - jobs, npc.title) + g_gui:showInfoDialog({ + visible = true, + text = txt, + dialogType = DialogElement.TYPE_INFO + }) + return + end + end + superf(frCon, wantsLease) +end +function BetterContracts:onPeriodChanged() + -- hard mode: cancel any active field missions + if g_server ~= nil and self.config.hardMode and self.config.hardExpire == SC.MONTH then + for _, m in ipairs(g_missionManager:getActiveMissions()) do + if m:hasField() then + g_missionManager:cancelMission(m) + end + end end end function BetterContracts:onDayChanged() - -- hard mode: cancel any active missions + -- hard mode: cancel any active field missions + if g_server == nil or not self.config.hardMode + or self.config.hardExpire == SC.MONTH then return end for _, m in ipairs(g_missionManager:getActiveMissions()) do - g_missionManager:cancelMission(m) + if m:hasField() then + g_missionManager:cancelMission(m) + end end end function BetterContracts:onHourChanged() - -- hard mode: issue warning for active missions - if g_currentMission.environment.currentHour ~= 23 then return end + -- hard mode: issue warnings 6,3,1 h before active missions cancel + if not self.config.hardMode or g_client == nil then return end + local env = g_currentMission.environment + if self.config.hardExpire == SC.MONTH and + env.currentDayInPeriod ~= env.daysPerPeriod then return end + if not TableUtility.contains({18,21,23}, env.currentHour) then return end local farmId = g_currentMission:getFarmId() local count = 0 for _, m in ipairs(g_missionManager:getActiveMissions()) do - if m.farmId == farmId then + if m:hasField() and m.farmId == farmId then count = count +1 end end if count > 0 then - g_currentMission:addIngameNotification(FSBaseMission.INGAME_NOTIFICATION_CRITICAL, string.format(g_i18n:getText("bc_warnTimeout"), count)) + g_currentMission:addIngameNotification(FSBaseMission.INGAME_NOTIFICATION_CRITICAL, + string.format(g_i18n:getText("bc_warnTimeout"), count)) end end -function onButtonCancel(self) +function onButtonCancel(self, superf) + if not BetterContracts.config.hardMode then return superf(self) end local contract = self:getSelectedContract() local m = contract.mission - local difficulty = 0.7 + 0.3 * g_currentMission.missionInfo.economicDifficulty + --local difficulty = 0.7 + 0.3 * g_currentMission.missionInfo.economicDifficulty local text = g_i18n:getText("fieldJob_endContract") local reward = m:getReward() if reward then - local penalty = reward * difficulty * SC.PENALTY + local penalty = MathUtil.round(reward * BetterContracts.config.hardPenalty) text = text.. g_i18n:getText("bc_warnCancel") .. g_i18n:formatMoney(penalty, 0, true, true) end @@ -175,45 +327,34 @@ function onButtonCancel(self) end --------------------- discount mode --------------------------------------------------------------------- -function finish(self, success ) - -- appended to AbstractFieldMission:finish(success) - if g_currentMission:getIsServer() and success then - local farm = g_farmManager:getFarmById(self.farmId) +function AbstractFieldMission:getNPC() local npcIndex = self.field.farmland.npcIndex - local npc = g_npcManager:getNPCByIndex(npcIndex) - if farm.stats.npcJobs == nil then - farm.stats.npcJobs = {} - end - local jobs = farm.stats.npcJobs - if jobs[npcIndex] == nil then - jobs[npcIndex] = 1 - else - jobs[npcIndex] = math.min(jobs[npcIndex] +1, SC.MAXJOBS) - end - local disct = jobs[npcIndex] * 100*SC.DISCOUNT - g_currentMission:addIngameNotification(FSBaseMission.INGAME_NOTIFICATION_OK, - string.format(g_i18n:getText("bc_discValue"), npc.title, disct)) - if jobs[npcIndex] == SC.MAXJOBS then - g_currentMission:addIngameNotification(FSBaseMission.INGAME_NOTIFICATION_OK, - string.format(g_i18n:getText("bc_maxJobs"), npc.title)) - end - end + return g_npcManager:getNPCByIndex(npcIndex) end function getDiscountPrice(farmland) + local discPerJob = BetterContracts.config.discPerJob local price = farmland.price local disct = "" local farm = g_farmManager:getFarmById(g_currentMission.player.farmId) local jobs = farm.stats.npcJobs local count = jobs[farmland.npcIndex] or 0 - if count > 0 then - price = price * (1 - count * SC.DISCOUNT) - disct = string.format(" (- %d%%)", 100*count *SC.DISCOUNT) + local disJobs = math.min(count, BetterContracts.config.discMaxJobs, + math.floor(0.5 / discPerJob)) + + if disJobs > 0 then + price = price * (1 - disJobs * discPerJob) + disct = string.format(" (- %d%%)", 100 *disJobs *discPerJob) end return price, disct end function onClickFarmland(self, elem, X, Z) -- appended to InGameMenuMapFrame:onClickMap() - if self.mode ~= InGameMenuMapFrame.MODE_FARMLANDS then return end + local bc = BetterContracts + bc.my.ownerText:setVisible(false) + bc.my.ownerLabel:setVisible(false) + + if not bc.config.discountMode or + self.mode ~= InGameMenuMapFrame.MODE_FARMLANDS then return end local farmland = self.selectedFarmland if farmland == nil or not farmland.showOnFarmlandsScreen @@ -221,12 +362,21 @@ function onClickFarmland(self, elem, X, Z) then return end local price, disct = getDiscountPrice(farmland) - --self.farmlandValueText.textUpperCase = disct == "" + if price <= self.playerFarm:getBalance() then + self.farmlandValueText:applyProfile(InGameMenuMapFrame.PROFILE.MONEY_VALUE_NEUTRAL) + end self.farmlandValueText:setText(g_i18n:formatMoney(price, 0, true, true)..disct) + -- show npc owner: + local npc = g_npcManager:getNPCByIndex(farmland.npcIndex) + bc.my.ownerText:setText(npc.title) + bc.my.ownerText:setVisible(true) + bc.my.ownerLabel:setVisible(true) + self.farmlandValueBox:invalidateLayout() end function onClickBuyFarmland(self, superf) -- adjust price if player buys farmland + if not BetterContracts.config.discountMode then return superf(self) end if self.selectedFarmland == nil then return end local price, disct = getDiscountPrice(self.selectedFarmland) @@ -248,43 +398,34 @@ function onClickBuyFarmland(self, superf) end end function BetterContracts:onYesNoBuyFarmland(yes, args) - -- body if yes then + -- remove owner info: + local bc = BetterContracts + bc.my.ownerText:setVisible(false) + bc.my.ownerLabel:setVisible(false) g_client:getServerConnection():sendEvent(FarmlandStateEvent.new(unpack(args))) end end function BetterContracts:onFarmlandStateChanged(landId, farmId) - if g_server == nil or farmId == FarmlandManager.NO_OWNER_FARM_ID + -- if client buys/sells farmland, FarmlandStateEvent is sent to server, then broadcast to all clients + -- so we only change npcJobs on server and on the client who bought the farmland + if farmId == FarmlandManager.NO_OWNER_FARM_ID + or not self.config.discountMode + then return end + if not (g_server or g_currentMission:getFarmId() == farmId) then return end - -- reset npcJobs to 0 for npc seller of farmland + -- decrease npcJobs to 0, or by discMaxJobs for npc seller of farmland local farm = g_farmManager:getFarmById(farmId) local npcIndex = g_farmlandManager:getFarmlandById(landId).npcIndex if farm == nil or npcIndex == nil then return end if farm.stats.npcJobs == nil then farm.stats.npcJobs = {} + elseif farm.stats.npcJobs[npcIndex] ~= nil then + farm.stats.npcJobs[npcIndex] = + math.max(farm.stats.npcJobs[npcIndex] - self.config.discMaxJobs, 0) + else + farm.stats.npcJobs[npcIndex] = 0 end - farm.stats.npcJobs[npcIndex] = 0 -end -function saveToXML(self, xmlFile, key) - -- appended to FarmStats:saveToXMLFile() - -- self is farm.stats - local jobs = self.npcJobs - if jobs ~= nil then - xmlFile:setTable(key .. ".npcJobs.npc", jobs, - function (npcKey, npc, npcIndex) - xmlFile:setInt(npcKey .. "#index", npcIndex) - xmlFile:setInt(npcKey .. "#count", npc or 0) - end) - end -end -function loadFromXML(self, xmlFile, key) - -- appended to FarmStats:loadFromXMLFile() - -- self: farm.stats - self.npcJobs = {} - xmlFile:iterate(key .. ".npcJobs.npc", function (_, npcKey) - local ix = xmlFile:getInt(npcKey.."#index") - self.npcJobs[ix] = xmlFile:getInt(npcKey.."#count", 0) - end) end diff --git a/scripts/settings.lua b/scripts/settings.lua new file mode 100644 index 0000000..ad3a3e9 --- /dev/null +++ b/scripts/settings.lua @@ -0,0 +1,308 @@ +--======================================================================================================= +-- BetterContracts SCRIPT +-- +-- Purpose: Enhance ingame contracts menu. +-- Author: Mmtrx +-- Changelog: +-- v1.2.6.0 30.11.2022 UI for all settings +--======================================================================================================= +local function lazyNPCDisabled() + return not BetterContracts.config.lazyNPC +end +local function hardDisabled() + return not BetterContracts.config.hardMode +end +local function discountDisabled() + return not BetterContracts.config.discountMode +end +BCSettingsBySubtitle = { + { + title = "bc_baseSettings", + elements = { + {name = "multReward", + values = {.8,.9,1,1.1,1.2,1.3,1.4}, + texts = {"-20 %","-10 %","standard","+10 %","+20 %","+30 %","+40 %"}, + default = 3, + title = "bc_rewardMultiplier", + tooltip = "bc_rewardMultiplier_tooltip", + noTranslate = true + }, + {name = "multLease", + min = .8, max = 1.5, increment = .1, + values = {.8,.9,1,1.1,1.2,1.3,1.4,1.5}, + texts = {"-20 %","-10 %","standard","+10 %","+20 %","+30 %","+40 %","+50 %"}, + default = 3, + title = "bc_leaseMultiplier", + tooltip = "bc_leaseMultiplier_tooltip", + noTranslate = true + }, + {name = "maxActive", + values = {0,1,2, 3, 4, 5, 6, 7, 8, 9, 10}, + texts = {"unlimited", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"}, + default = 4, + title = "bc_maxActive", + tooltip = "bc_maxActive_tooltip", + noTranslate = true + }, + {name = "refreshMP", + values = {SC.ADMIN, SC.FARMMANAGER, SC.PLAYER}, + texts = {"ui_admin","ui_farmManager","ui_players"}, + default = 1, + title = "bc_refreshMP", + tooltip = "bc_refreshMP_tooltip", + isDisabledFunc = function() + return not g_currentMission.missionDynamicInfo.isMultiplayer end, + }, + {name = "debug", + values = {false, true}, + texts = {"ui_off", "ui_on"}, + default = 1, + title = "bc_debug", + tooltip = "bc_debug_tooltip", + }, + }, + }, + { + title = "bc_lazyNPC", + elements = { + {name = "lazyNPC", + values = {false, true}, + texts = {"ui_no", "ui_yes"}, + default = 1, + title = "bc_mainSwitch", + tooltip = "bc_lazyNPC_tooltip", + }, + {name = "npcHarvest", + values = {false, true}, + texts = {"ui_off", "bc_active"}, + default = 1, + title = "fieldJob_jobType_harvesting", + tooltip = "bc_lazyNPCHarvest_tooltip", + isDisabledFunc = lazyNPCDisabled, + }, + {name = "npcSow", + values = {false, true}, + texts = {"ui_off", "bc_active"}, + default = 1, + title = "fieldJob_jobType_sowing", + tooltip = "bc_lazyNPCSow_tooltip", + isDisabledFunc = lazyNPCDisabled, + }, + {name = "npcPlowCultivate", + values = {false, true}, + texts = {"ui_off", "bc_active"}, + default = 1, + title = "bc_lazyNPCPlow", + tooltip = "bc_lazyNPCPlow_tooltip", + isDisabledFunc = lazyNPCDisabled, + }, + {name = "npcFertilize", + values = {false, true}, + texts = {"ui_off", "bc_active"}, + default = 1, + title = "fieldJob_jobType_fertilizing", + isDisabledFunc = lazyNPCDisabled, + tooltip = "bc_lazyNPCFertilize_tooltip", + }, + {name = "npcWeed", + values = {false, true}, + texts = {"ui_off", "bc_active"}, + default = 1, + title = "fieldJob_jobType_weeding", + tooltip = "bc_lazyNPCWeed_tooltip", + isDisabledFunc = lazyNPCDisabled, + } + }, + }, + { + title = "bc_hardMode", + elements = { + {name = "hardMode", + values = {false, true}, + texts = {"ui_no", "ui_yes"}, + default = 1, + title = "bc_mainSwitch", + tooltip = "bc_hardMode_tooltip", + }, + {name = "hardPenalty", + values = {.1,.2,.3,.4,.5,.6,.7}, + texts = {"10 %","20 %","30 %","40 %","50 %","60 %","70 %"}, + default = 1, + title = "bc_hardPenalty", + tooltip = "bc_hardPenalty_tooltip", + isDisabledFunc = hardDisabled, + noTranslate = true + }, + {name = "hardLease", + values = {1,2,3,4,5,6,7}, + texts = {"1", "2", "3", "4", "5", "6", "7",}, + default = 2, + title = "bc_hardLease", + tooltip = "bc_hardLease_tooltip", + isDisabledFunc = hardDisabled, + noTranslate = true + }, + {name = "hardExpire", + values = {SC.DAY, SC.MONTH}, + texts = {"ui_day", "ui_month"}, + default = 2, + title = "bc_hardExpire", + tooltip = "bc_hardExpire_tooltip", + isDisabledFunc = hardDisabled, + }, + }, + }, + { + title = "bc_discountMode", + elements = { + {name = "discountMode", + values = {false, true}, + texts = {"ui_no", "ui_yes"}, + default = 1, + title = "bc_mainSwitch", + tooltip = "bc_discountMode_tooltip", + }, + {name = "discPerJob", + values = {.05,.08,.11,.14}, + texts = {"5 %","8 %","11 %","14 %", }, + default = 1, + title = "bc_discPerJob", + tooltip = "bc_discPerJob_tooltip", + isDisabledFunc = discountDisabled, + noTranslate = true + }, + {name = "discMaxJobs", + values = {1,2,3,4,5,6,7}, + texts = {"1", "2", "3", "4", "5", "6", "7",}, + default = 5, + title = "bc_discMaxJobs", + tooltip = "bc_discMaxJobs_tooltip", + isDisabledFunc = discountDisabled, + noTranslate = true + }, + }, + }, +} +-- settings class +BCsetting = {} +local BCsetting_mt = Class(BCsetting, AIParameter) + +function BCsetting.new(data, customMt) + local self = AIParameter.new(customMt or BCsetting_mt) + self.type = AIParameterType.SELECTOR + self.name = data.name + self.data = data + if next(data.values) ~=nil then + self.values = table.copy(data.values) + self.texts = table.copy(data.texts) + elseif data.min ~= nil and data.max ~=nil then + -- maybe future use -- + self.data.values = {} + self.data.texts = {} + BCsetting.generateValues(self, self.data.values, self.data.texts, data.min, data.max, data.increment, data.unit) + self.values = table.copy(self.data.values) + if self.data.texts ~= nil then + self.texts = table.copy(self.data.texts) + end + end + self.title = data.title + self.tooltip = data.tooltip + + -- index of the current value/text + self.default = data.default + self.current = data.default or 1 + -- index of the previous value/text + self.previous = 1 + self.isDisabledFunc = data.isDisabledFunc + self.guiElement = nil + return self +end +function BCsetting.init(bc) + -- initialize setting objects from constants + local settings = {} + for _, subtitle in ipairs(BCSettingsBySubtitle) do + for _, data in ipairs(subtitle.elements) do + local setting = BCsetting.new(data) + setting:setValue(bc.config[setting.name]) + table.insert(settings, setting) + bc.settingsByName[setting.name] = setting -- do we still need this? + end + end + return settings +end +function BCsetting:generateValues(values, texts, min, max, inc, percent) + inc = inc or 1 + for i=min, max, inc do + table.insert(values, i) + local value = MathUtil.round(i, 2) + local text = percent and string.format("%+d %%",value*100) or tostring(value) + table.insert(texts, text) + end +end +function BCsetting:setValue(value) + -- set the settings current corresponding to input value. Return false, if value nil or not found + local function func(...) return false end + if value ~= nil then + if type(value) == "number" then + func = function(a, b) + local epsilon = self.data.incremental or 0.01 + if a == nil or b == nil then return false end + return a > b - epsilon/2 and a <= b + epsilon/2 + end + else + func = function(a, b) return a == b end + end + else + Logging.warning("[BetterContracts] %s:setValue() called with nil value",self.name) + return false + end + -- find the value requested, set current correspondingly + for i = 1, #self.values do + if func(self.values[i], value) then + self.previous = self.current + self.current = i + return true + end + end + return false +end +function BCsetting:setIx(ix) + -- set it to values[ix] + local conf = BetterContracts.config + if self.current ~= ix then + self.previous = self.current + self.current = ix + conf[self.name] = self.values[ix] + debugPrint("** %s set to %s **", self.name,self.values[ix]) + end +end +function BCsetting:setGuiElement(element) + local labels = {} + for i = 1, #self.texts do + if self.data.noTranslate == true then + labels[i] = self.texts[i] + else + labels[i] = g_i18n:getText(self.texts[i]) + end + end + element:setTexts(labels) + + -- init value from BetterContracts.config: + self:setValue(BetterContracts.config[self.name]) + + element:setState(self.current) + self.guiElement = element + element.bc_setting = self + + local isDisabledFunc = self.isDisabledFunc + if isDisabledFunc then + element:setDisabled(isDisabledFunc()) + end +end +function BCsetting:writeStream(streamId, connection) + streamWriteInt32(streamId, self.current) +end +function BCsetting:readStream(streamId, connection) + local ix = streamReadInt32(streamId) + self:setIx(ix) +end diff --git a/scripts/userint.lua b/scripts/userint.lua index 8bffbfe..2a677ed 100644 --- a/scripts/userint.lua +++ b/scripts/userint.lua @@ -11,6 +11,7 @@ -- v1.2.0.0 18.01.2022 (Mmtrx) adapt for FS22 -- v1.2.4.3 10.10.2022 recognize FS22_LimeMission -- v1.2.5.0 31.10.2022 fewer mission vehicle warnings +-- v1.2.5.1 02.11.2022 check estWorktime() parms if nil --======================================================================================================= -------------------- mission analysis functions --------------------------------------------------- @@ -208,7 +209,7 @@ function BetterContracts:spreadMission(m, wid, hei, vWorkwidth, vSpeed) price[1] = self.prices[SC.LIME] usage[1] = self.sprUse[SC.LIME] maxj = 1 - typ = 9 -- default lime sprayer (Bredal K165) + typ = 10 -- default lime sprayer (Bredal K165) price[2], usage[2], ftext[2] = price[1], usage[1], ftext[1] end -- set specs from mission vehicle @@ -280,6 +281,12 @@ end function BetterContracts:estWorktime(wid, hei, wwid, speed) -- estimate time to work a rectangle field (wid x hei) with a tool -- of working width wwid at given speed + if wwid == nil or wwid == 0 or speed == nil or speed == 0 then + Logging.warning("[%s] estWorktime invalid parms: %.1f, %.1f, %s, %s", + self.name, wid, hei, wwid, speed) + printCallstack() + return nil, 36000 + end local nlanes = math.ceil(wid / wwid) -- how many passes local workL = nlanes * hei + wid -- length of working path local netT = workL / speed * 3.6 -- net working time in sec