├── .gitattributes ├── .gitignore ├── FS25_BetterMinimap.zip ├── FS25_BetterMinimap ├── FS25_BetterMinimap.lua ├── FS25_BetterMinimap_Debug.lua ├── FS25_BetterMinimap_icon.dds ├── lib │ ├── DebugHelper.lua │ ├── DevHelper.lua │ ├── DialogHelper.lua │ ├── LogHelper.lua │ └── ModHelper.lua ├── modDesc.xml ├── translations │ ├── translation_cz.xml │ ├── translation_de.xml │ ├── translation_en.xml │ ├── translation_fr.xml │ ├── translation_pl.xml │ └── translation_ru.xml └── ui │ ├── FS25_BetterMinimap_UI.lua │ └── FS25_BetterMinimap_UI.xml ├── LICENSE ├── README.md ├── setDensityMaskRegion in FS25.docx ├── zip-betterMinimap.bat └── zip-betterMinimap.ps1 /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | trashbin/ 2 | .docx 3 | .bat 4 | .ps1 -------------------------------------------------------------------------------- /FS25_BetterMinimap.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattleHook/FS25_BetterMinimap/a2c4dd87aec367961171e65509322fe982a0ad92/FS25_BetterMinimap.zip -------------------------------------------------------------------------------- /FS25_BetterMinimap/FS25_BetterMinimap.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Mod: FS25_BetterMinimap 3 | -- 4 | -- Author: original FS17:jDanek; FS25: SupremeClicker 5 | -- email: gvdhaak (at) gmail (dot) com 6 | -- @Date: 28.02.2025 7 | -- @Version: 1.0.0.0 8 | --[[ 9 | CHANGELOG 10 | 11 | TODO: - Change the size of the minimap (normal / wide / larger) 12 | TODO: - Remembers the selected size during play 13 | TODO: - Switching transparency 14 | TODO: - Zoom in and out of the minimap 15 | 16 | TODO: Settings Utils 17 | TODO: Sounds when changing modes and refreshing 18 | ]] -- 19 | 20 | local myName = "FS25_BetterMinimap" 21 | 22 | FS25_BetterMinimap = Mod:init() 23 | 24 | --- Initializes settings, keybindings, and UI elements before the map loads. 25 | function FS25_BetterMinimap:beforeLoadMap() 26 | end 27 | 28 | --- Called when the map has finished loading. 29 | function FS25_BetterMinimap:loadMap(filename) 30 | Log:info("--> loaded <--") 31 | if g_inputBinding then 32 | self:registerActionEvents() 33 | else 34 | Log:error("loadMap: g_inputBinding is NIL! Cannot register keybindings!") 35 | end 36 | 37 | Log:info("Initializing settings") 38 | self:initializeSettings() 39 | self.lastUpdateTime = 0 40 | end 41 | 42 | --- Initializes settings by ensuring files exist and loading values from XML. 43 | function FS25_BetterMinimap:initializeSettings() 44 | if ModSettings then -- Ensure ModSettings is available before using it 45 | self.settings = ModSettings:new(self) 46 | self.settings:init("FS25_BetterMinimap", "defaultSettings.xml", "userSettings.xml") 47 | self.settings:load(function(xmlReader) 48 | self.config = { 49 | visible = xmlReader:readBool("settings", "visible", true), 50 | minimapMode = xmlReader:readInt("settings", "minimapMode", 2), -- Default to 2 FruitType Mode 51 | refreshRate = xmlReader:readInt("settings", "refreshRate", 60) -- Default to 60 seconds 52 | } 53 | Log:info("LOADED SETTINGS: minimapMode = " .. tostring(self.config.minimapMode)) 54 | end) 55 | else 56 | Log:warning("ModSettings not available, applying default settings.") 57 | self.config = { 58 | visible = true, 59 | minimapMode = 2, 60 | refreshRate = 60 61 | } 62 | end 63 | end 64 | 65 | --- Called when the mission starts. 66 | function FS25_BetterMinimap:startMission() 67 | FS25_BetterMinimap_Debug:runAllChecks() 68 | self:registerActionEvents() 69 | Log:info("Mission started.") 70 | end 71 | 72 | --- Modifies the minimap to display overlays for different states such as fruit type, growth stage, soil state, fertilizer, plowing, and lime. 73 | function FS25_BetterMinimap:modifyDefaultMinimap() 74 | Log:info("modifyDefaultMinimap called ...") -- NOTE: for development only 75 | Log:trace("Applying minimap mode: " .. self.config.minimapMode) 76 | local overlay = g_currentMission.hud.ingameMap.mapOverlay 77 | if overlay then 78 | local modeToOverlay = { 79 | [2] = MapOverlayGenerator.OVERLAY_FRUIT_TYPES, -- FruitType Mode 80 | [3] = MapOverlayGenerator.OVERLAY_GROWTH, -- GrowthStage Mode 81 | [4] = MapOverlayGenerator.OVERLAY_SOIL, -- SoilState Mode 82 | [5] = MapOverlayGenerator.OVERLAY_FERTILIZER, -- Fertilizer Mode 83 | [6] = MapOverlayGenerator.OVERLAY_PLOWING, -- Plowing Mode 84 | [7] = MapOverlayGenerator.OVERLAY_LIME -- Lime Mode 85 | } 86 | 87 | local overlayType = modeToOverlay[self.config.minimapMode] 88 | if overlayType then 89 | Log:info("Setting overlay type: " .. overlayType) 90 | if overlay.setOverlayType then 91 | overlay:setOverlayType(overlayType) 92 | elseif overlay.applyOverlay then 93 | overlay:applyOverlay(overlayType) 94 | else 95 | Log:error("No valid overlay function found in mapOverlay!") 96 | end 97 | else 98 | Log:error("Invalid minimap mode selected!") 99 | end 100 | else 101 | Log:warning("overlay is nil. Cannot apply overlay.") 102 | end 103 | end 104 | 105 | --- Registers keybindings for minimap controls. 106 | function FS25_BetterMinimap:registerActionEvents() 107 | local actions = { 108 | "FS25_BetterMinimap_SHOW_CONFIG_GUI", 109 | "FS25_BetterMinimap_RELOAD", 110 | "FS25_BetterMinimap_PREV", 111 | "FS25_BetterMinimap_NEXT" 112 | } 113 | for _, action in pairs(actions) do 114 | local _, eventId = g_inputBinding:registerActionEvent(action, self, self.onActionEvent, false, true, false, true) 115 | if eventId then 116 | g_inputBinding:setActionEventTextVisibility(eventId, true) 117 | Log:trace("Registered action event: " .. action) 118 | else 119 | Log:trace("Failed to register action event: " .. action) 120 | end 121 | end 122 | end 123 | 124 | --- Handles keybinding events for minimap actions. 125 | function FS25_BetterMinimap.onActionEvent(actionName, keyStatus) 126 | Log:trace("Action Triggered: " .. actionName) 127 | if actionName == "FS25_BetterMinimap_SHOW_CONFIG_GUI" then 128 | -- Open configuration UI 129 | elseif actionName == "FS25_BetterMinimap_RELOAD" then 130 | Log:info("RELOAD key pressed, refreshing") 131 | FS25_BetterMinimap:modifyDefaultMinimap() 132 | elseif actionName == "FS25_BetterMinimap_PREV" then 133 | Log:info("PREV key pressed, changing mode") 134 | FS25_BetterMinimap:changeMode(-1) 135 | elseif actionName == "FS25_BetterMinimap_NEXT" then 136 | Log:info("NEXT key pressed, changing mode") 137 | FS25_BetterMinimap:changeMode(1) 138 | end 139 | FS25_BetterMinimap:saveSettings() 140 | end 141 | 142 | --- Changes the minimap mode based on user input. 143 | function FS25_BetterMinimap:changeMode(direction) 144 | local numModes = 7 -- Number of available modes 145 | local oldMode = self.config.minimapMode 146 | self.config.minimapMode = math.max(2, math.min(self.config.minimapMode + direction, numModes)) 147 | Log:info("minimapMode Changed: " .. oldMode .. "->" .. self.config.minimapMode) 148 | self:modifyDefaultMinimap() 149 | end 150 | 151 | --- Saves the current settings to userSettings.xml. 152 | function FS25_BetterMinimap:saveSettings() 153 | if self.settings then 154 | self.settings:save(function(xmlWriter) 155 | xmlWriter:saveBool("settings", "visible", self.config.visible) 156 | xmlWriter:saveInt("settings", "minimapMode", self.config.minimapMode) 157 | xmlWriter:saveInt("settings", "refreshRate", self.config.refreshRate) 158 | end) 159 | end 160 | end 161 | -------------------------------------------------------------------------------- /FS25_BetterMinimap/FS25_BetterMinimap_Debug.lua: -------------------------------------------------------------------------------- 1 | -- FS25_BetterMinimap_Debug.lua 2 | -- Debug script to log debug information in XML format 3 | 4 | FS25_BetterMinimap_Debug = {} 5 | 6 | --- Gets the path to the debug log XML file 7 | function FS25_BetterMinimap_Debug:getLogFilePath() 8 | local settingsDir = getUserProfileAppPath() .. "modSettings/FS25_BetterMinimap/" 9 | return settingsDir .. "FS25_BetterMinimap_Debug.xml" 10 | end 11 | 12 | --- Writes a log entry to the XML file 13 | function FS25_BetterMinimap_Debug:writeLog(level, message) 14 | local filePath = self:getLogFilePath() 15 | 16 | -- Load existing XML file or create a new one 17 | local xmlFile = loadXMLFile("debugLog", filePath) 18 | if xmlFile == 0 then 19 | xmlFile = createXMLFile("debugLog", filePath, "FS25_BetterMinimap_Debug") 20 | end 21 | 22 | -- Escape special characters to avoid XML parsing errors 23 | local function escapeXml(str) 24 | str = str:gsub("&", "&") 25 | str = str:gsub("<", "<") 26 | str = str:gsub(">", ">") 27 | return str 28 | end 29 | 30 | -- Retrieve valid date values 31 | local year = g_currentMission.environment.currentYear or getDate("%Y") or 2025 32 | local month = g_currentMission.environment.currentMonth or getDate("%m") or 1 33 | local day = g_currentMission.environment.currentDay or getDate("%d") or 1 34 | 35 | -- Retrieve valid time values 36 | local hour = math.floor((g_currentMission.environment.dayTime or 0) / (60 * 60 * 1000)) % 24 37 | local minute = math.floor((g_currentMission.environment.dayTime or 0) / (60 * 1000)) % 60 38 | 39 | -- If hour or minute is invalid, fallback to system time 40 | if hour == 0 and minute == 0 then 41 | hour = getTime("%H") or 12 42 | minute = getTime("%M") or 0 43 | end 44 | 45 | -- Format timestamp 46 | local timestamp = string.format("%04d-%02d-%02d %02d:%02d", year, month, day, hour, minute) 47 | 48 | -- Find next available log entry index 49 | local logIndex = 0 50 | while hasXMLProperty(xmlFile, string.format("FS25_BetterMinimap_Debug.log(%d)", logIndex)) do 51 | logIndex = logIndex + 1 52 | end 53 | 54 | -- Write new log entry 55 | local logPath = string.format("FS25_BetterMinimap_Debug.log(%d)", logIndex) 56 | setXMLInt(xmlFile, logPath .. "#logid", logIndex) 57 | setXMLString(xmlFile, logPath .. "#time", timestamp) 58 | setXMLString(xmlFile, logPath .. "#level", level) 59 | setXMLString(xmlFile, logPath .. "#message", escapeXml(message)) -- Escaped message 60 | 61 | -- Save and close file 62 | saveXMLFile(xmlFile) 63 | delete(xmlFile) 64 | end 65 | --- Logs an error message 66 | function FS25_BetterMinimap_Debug:logError(message) 67 | self:writeLog("Error", message) 68 | end 69 | 70 | --- Logs a warning message 71 | function FS25_BetterMinimap_Debug:logWarning(message) 72 | self:writeLog("Warning", message) 73 | end 74 | 75 | --- Logs an info message 76 | function FS25_BetterMinimap_Debug:logInfo(message) 77 | self:writeLog("Info", message) 78 | end 79 | 80 | --- Runs all debugging checks 81 | function FS25_BetterMinimap_Debug:runAllChecks() 82 | self:logInfo("==== FS25_BetterMinimap Debugging Start ====") 83 | self:logInfo("Checking available functions in g_fieldManager...") 84 | self:logInfo("Checking available functions in mapOverlay...") 85 | self:logInfo("==== Debugging Complete ====") 86 | end 87 | 88 | return FS25_BetterMinimap_Debug 89 | -------------------------------------------------------------------------------- /FS25_BetterMinimap/FS25_BetterMinimap_icon.dds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BattleHook/FS25_BetterMinimap/a2c4dd87aec367961171e65509322fe982a0ad92/FS25_BetterMinimap/FS25_BetterMinimap_icon.dds -------------------------------------------------------------------------------- /FS25_BetterMinimap/lib/DebugHelper.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | DebugHelper (Weezls Mod Lib for FS22) - A extension "class" that improves the LogHelper by adding some additional features specifically for debugging 4 | 5 | Author: w33zl (github.com/w33zl | facebook.com/w33zl) 6 | Version: 1.2 7 | Modified: 2023-08-10 8 | 9 | Changelog: 10 | v1.2 Added TimedExecution 11 | v1.1 Added saveTable function 12 | v1.0 Initial public release 13 | 14 | License: CC BY-NC-SA 4.0 15 | This license allows reusers to distribute, remix, adapt, and build upon the material in any medium or 16 | format for noncommercial purposes only, and only so long as attribution is given to the creator. 17 | If you remix, adapt, or build upon the material, you must license the modified material under identical terms. 18 | 19 | ]] 20 | 21 | DebugHelper = { 22 | dumpTable = function(self, tableName, tableObject, maxDepth) 23 | maxDepth = maxDepth or 2 24 | if tableObject == nil then 25 | print(tableName .. ":: [nil]") 26 | elseif type(tableObject) ~= "table" then 27 | Log:warning("%s is not a table, cannot print table contents", tableName) 28 | else 29 | DebugUtil.printTableRecursively(tableObject, tableName .. ":: ", 0, maxDepth) 30 | end 31 | end, 32 | 33 | saveTable = function(self, fileName, tableName, tableObject, maxDepth, ignoredTables) 34 | maxDepth = maxDepth or 2 35 | 36 | local function getTableInfo(inputTable, inputIndent, depth, desiredDepth, knownTables, ignoredTables) 37 | inputIndent = inputIndent or " " 38 | depth = depth or 0 39 | desiredDepth = desiredDepth or 2 40 | 41 | if depth > desiredDepth then 42 | return nil 43 | end 44 | 45 | knownTables = knownTables or {} 46 | 47 | -- Always this current table to known tables 48 | knownTables[tostring(inputTable)] = true 49 | 50 | local string1 = "" 51 | 52 | local ignore = { 53 | -- string = true, 54 | -- math = true, 55 | -- table = true 56 | } 57 | ignore = ignoredTables or ignore 58 | 59 | for i, j in pairs(inputTable) do 60 | local indexOrKey = tostring(i) 61 | 62 | if type(i) == "number" then 63 | indexOrKey = string.format("[%d]", i) 64 | elseif type(i) == "table" then 65 | indexOrKey = string.format("[\"%s\"]", i) 66 | elseif type(i) == "string" and not indexOrKey:match("^[%w_]+$") then 67 | indexOrKey = string.format("[\"%s\"]", i) 68 | end 69 | 70 | -- if type(j) == "userdata" then 71 | -- j = string.format("userdata: (%s)", tostring(j)) 72 | -- end 73 | 74 | if type(j) == "table" and knownTables[tostring(j)] == true then 75 | string1 = string1 .. string.format("\n%s%s = {}, -- REFERENCE %s\n", inputIndent, indexOrKey, tostring(j)) 76 | elseif type(j) == "table" and ignore[tostring(i)] == true then 77 | print(string.format("Skipped table '%s'", indexOrKey)) 78 | elseif type(j) == "table" then 79 | local string2 = getTableInfo(j, inputIndent .. "\t", depth + 1, desiredDepth, knownTables, ignore) 80 | 81 | -- string1 = string1 .. string.format("\n%s %s = { -- %s\n", inputIndent, tostring(i), tostring(j)) 82 | 83 | 84 | if string2 ~= nil then 85 | string2 = string.format("%s%s", inputIndent, string2) 86 | string1 = string1 .. string.format("\n%s%s = { -- %s%s\n%s},", inputIndent, indexOrKey, tostring(j), string2, inputIndent) 87 | else 88 | string1 = string1 .. string.format("\n%s%s = {}, -- %s\n", inputIndent, indexOrKey, tostring(j)) 89 | end 90 | 91 | -- knownTables[tostring(j)] = true 92 | 93 | elseif type(j) == "function" and ignore[j] ~= true then 94 | string1 = string1 .. string.format("\n%s%s = function() end, -- %s", inputIndent, indexOrKey, tostring(j)) 95 | 96 | 97 | --TODO: maybe do a pcall and try to figure out return type and even parameters?! 98 | else 99 | 100 | local value = tostring(j) 101 | if type(j) == "string" then 102 | value = value:gsub("\\\"", "\"") 103 | value = value:gsub("\"", "\\\"") 104 | value = value:gsub("\n", "\\\n") 105 | value = value:gsub("\r", "\\\r") 106 | value = value:gsub("\t", "\\\t") 107 | value = string.format("\"%s\"", value) 108 | elseif type(j) == "userdata" then 109 | value = string.format("\"%s\"", value) 110 | end 111 | 112 | string1 = string1 .. string.format("\n%s%s = %s, -- %s", inputIndent, indexOrKey, value, type(j)) 113 | end 114 | end 115 | 116 | return string1 117 | end 118 | 119 | if tableObject == nil then 120 | Log:error("Table '%s' was nil, skipped saving to file '%s'", tableName, fileName) 121 | else 122 | 123 | -- Log:debug("Opening file '%s'", fileName) 124 | 125 | local fileId = createFile(fileName, FileAccess.WRITE) 126 | 127 | if fileId ~= nil then 128 | -- Log:debug("Writing to file handle #%d", fileId) 129 | 130 | local output = getTableInfo(tableObject, "\t", 0, maxDepth - 1, {}, ignoredTables) 131 | 132 | fileWrite(fileId, string.format("%s = {%s\n}\n", tableName, output)) 133 | 134 | delete(fileId) 135 | fileId = nil 136 | else 137 | Log:error("Filed to open file '%s'", fileName) 138 | end 139 | end 140 | end, 141 | 142 | decodeTable = function(self, tableName, tableObject, skipFunctions, unwrapTables) 143 | if Log == nil then return end 144 | if tableObject == nil then 145 | Log:warning("Table '%s' was not found", tableName) 146 | return 147 | end 148 | 149 | skipFunctions = skipFunctions or false 150 | 151 | local function logIt(index, value) 152 | local typeName = type(value) 153 | if typeName == "string" then 154 | value = "\"" .. value .. "\"" 155 | elseif skipFunctions and typeName == "function" then 156 | return -- Skip function 157 | else 158 | value = tostring(value) 159 | end 160 | Log:print("TABLE", "%s%s = %s [%s]", tableName, index, value, typeName) 161 | end 162 | 163 | for index, value in ipairs(tableObject) do 164 | logIt("[" .. tostring(index) .. "]", value) 165 | end 166 | 167 | for key, value in pairs(tableObject) do 168 | logIt("." .. key, value) 169 | end 170 | end, 171 | 172 | traceLog = function(self, message, ...) 173 | if Log == nil then return end 174 | Log.traceIndex = (Log.traceIndex or 999) + 1 175 | Log:print("TRACE-" .. tostring(Log.traceIndex), message, ...) 176 | end, 177 | 178 | inject = function(self, log) 179 | log.table = self.dumpTable 180 | log.tableX = self.decodeTable 181 | log.trace = self.traceLog 182 | log.saveTable = self.saveTable 183 | end, 184 | 185 | ---Intercepts a event/function call on any object and prints the input parameters 186 | ---@param target table The target object where to intercept a function 187 | ---@param functionName string Name of the function to intercept 188 | interceptDecode = function(self, target, functionName) 189 | local prefix = functionName 190 | 191 | local logger 192 | 193 | if Log ~= nil then 194 | logger = Log 195 | else 196 | logger = { 197 | debug = function(self, message, ...) 198 | Logging.info("[DebugHelper] " .. message, ...) 199 | end 200 | } 201 | end 202 | 203 | local function internalDecoder(self, superFunc, ...) 204 | local a = { ... } 205 | print("") 206 | logger:debug("### DECODING: %s [Arg count=%d]", prefix, #a) 207 | for index, value in ipairs(a) do 208 | logger:debug("%s.param[%d]='%s' [%s]", prefix, index, tostring(value), type(value)) 209 | end 210 | 211 | for index, value in ipairs(a) do 212 | if type(value) == "table" then 213 | DebugUtil.printTableRecursively(value, prefix .. ".param[" .. tostring(index) .. "]:: ", 0, 1 ) 214 | end 215 | end 216 | 217 | --TODO: fix - should pack/unpack? 218 | 219 | local returnValue = superFunc(self, ...) 220 | logger:debug("<< RETURN VALUE = %s [%s]", tostring(returnValue), type(returnValue)) 221 | -- if type(returnValue) == "table" then 222 | -- DebugUtil.printTableRecursively(returnValue, "RETURN:: ", 0, 1) 223 | -- else 224 | -- Logging.extInfo("RETURN=%s [%s]", tostring(returnValue), type(returnValue)) 225 | -- end 226 | 227 | return returnValue 228 | end 229 | 230 | 231 | target[functionName] = Utils.overwrittenFunction(target[functionName], internalDecoder) 232 | end 233 | 234 | } 235 | 236 | 237 | --- Executes any function and returns the time it took to execute. 238 | --- The first return value is the execution time (in ms) and the rest of the values are the return values from the callback function. 239 | ---@param callback function Callback function to execute 240 | ---@return number timeElapsed "Execution time (in ms)" 241 | ---@return returnValues "A list of return values from the callback function" 242 | function DebugHelper:timedExecution(callback) 243 | local startTime = getTimeSec() 244 | 245 | local returnValue = { callback() } 246 | local timeElapsed = (getTimeSec() - startTime) 247 | 248 | return timeElapsed, unpack(returnValue) 249 | end 250 | 251 | 252 | --- Starts a execution timer with the given format string. 253 | ---@param formatString string "Format string to print the execution time (you need to add '%f' to the string)" 254 | ---@return table "Execution timer object with the stop function (call ':stop(true)' to supress automatic print of the results)" 255 | ---@remark The results will be printed to the console when the timer is stopped (see ':stop()') unless the 'noPrint' parameter is set to true (e.g. ':stop(true)'). 256 | function DebugHelper:measureStart(formatString) 257 | return { 258 | text = formatString, 259 | startTime = getTimeSec(), 260 | stop = function(self, noPrint) 261 | self.endTime = getTimeSec() 262 | self.diff = self.endTime - self.startTime 263 | self.results = string.format(formatString, self.diff) 264 | if not noPrint then 265 | print(self.results) 266 | end 267 | return self.results 268 | end, 269 | } 270 | end 271 | -------------------------------------------------------------------------------- /FS25_BetterMinimap/lib/DevHelper.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | DevHelper (Weezls Mod Lib for FS22) - A utility "class" to assist mod development 4 | 5 | Author: w33zl (github.com/w33zl | facebook.com/w33zl) 6 | Version: 1.2 7 | Modified: 2023-08-10 8 | 9 | Changelog: 10 | v1.2 Added TimedExecution 11 | v1.1 Added saveTable function 12 | v1.0 Initial public release 13 | 14 | License: CC BY-NC-SA 4.0 15 | This license allows reusers to distribute, remix, adapt, and build upon the material in any medium or 16 | format for noncommercial purposes only, and only so long as attribution is given to the creator. 17 | If you remix, adapt, or build upon the material, you must license the modified material under identical terms. 18 | 19 | ]] 20 | 21 | DevHelper = {} 22 | _G.DevHelper = DevHelper 23 | 24 | --- Executes any function and returns the time it took to execute. 25 | --- The first return value is the execution time (in ms) and the rest of the values are the return values from the callback function. 26 | ---@param callback function Callback function to execute 27 | ---@return number timeElapsed "Execution time (in ms)" 28 | ---@return returnValues "A list of return values from the callback function" 29 | function DevHelper.timedExecution(callback) 30 | local startTime = getTimeSec() 31 | 32 | local returnValue = { callback() } 33 | local timeElapsed = (getTimeSec() - startTime) 34 | 35 | return timeElapsed, unpack(returnValue) 36 | end 37 | 38 | 39 | --- Starts a execution timer with the given format string. 40 | ---@param formatString string "Format string to print the execution time (you need to add '%f' to the string)" 41 | ---@return table "Execution timer object with the stop function (call ':stop(true)' to supress automatic print of the results)" 42 | ---@remark The results will be printed to the console when the timer is stopped (see ':stop()') unless the 'noPrint' parameter is set to true (e.g. ':stop(true)'). 43 | function DevHelper.measureStart(formatString) 44 | return { 45 | text = formatString, 46 | startTime = getTimeSec(), 47 | stop = function(self, noPrint) 48 | self.endTime = getTimeSec() 49 | self.diff = self.endTime - self.startTime 50 | self.results = string.format(formatString, self.diff) 51 | if not noPrint then 52 | print(self.results) 53 | end 54 | return self.results 55 | end, 56 | elapsed = function(self) 57 | return getTimeSec() - self.startTime 58 | end, 59 | } 60 | end 61 | 62 | 63 | ---Prints a table in a human readable format. 64 | ---@param table table The table to print 65 | ---@param maxDepth number The maximum depth to print 66 | ---@param verbose boolean Whether to print verbose information (e.g. type information) 67 | function DevHelper.visualizeTable(table, maxDepth, verbose) 68 | assert(StringWriter, "StringWriter class from WeezlsModLib is not available, please install it to use DevHelper.visualizeTable") 69 | local PREFIX = string.char(195) .. string.char(196) .. " " -- .. string.char(196) 70 | local PREFIX_TABLE = string.char(195) .. string.char(194) .. " " -- .. string.char(196) 71 | -- local PREFIX_TABLE = string.char(192) .. string.char(194) .. string.char(196) 72 | local INDENT_PREFIX = string.char(179) 73 | 74 | local PREFIX = unicodeToUtf8(0x251C) .. " " -- .. string.char(196) 75 | local PREFIX_TABLE = unicodeToUtf8(0x251C) .. unicodeToUtf8(0x252C) .. " " -- .. string.char(196) 76 | local INDENT_PREFIX = unicodeToUtf8(0x2502) 77 | 78 | -- PREFIX = "" 79 | -- PREFIX_TABLE = "-" 80 | 81 | -- PREFIX = unicodeToUtf8(PREFIX) 82 | -- PREFIX_TABLE = unicodeToUtf8(PREFIX_TABLE) 83 | 84 | 85 | local writer = StringWriter.new() 86 | writer:enableTrailingNewLine(false) 87 | 88 | maxDepth = maxDepth or 2 89 | verbose = verbose or false 90 | 91 | local function printTable(t, indent, depth) 92 | local firstValue = true 93 | depth = depth or 1 94 | -- if depth >= maxDepth then return end 95 | if indent == nil then indent = "" end 96 | --TODO: improve indexing using next, we can identify last item and choose different prefix 97 | 98 | for k, v in pairs(t) do 99 | local CURRENT_PREFIX = indent .. PREFIX --.. " " 100 | -- if firstValue and indent ~= "" then 101 | -- CURRENT_PREFIX = PREFIX_TABLE .. "" 102 | -- firstValue = false 103 | -- end 104 | 105 | -- if type(k) == "table" then 106 | -- k = k:gsub("table:", "{") .. " }" 107 | -- end 108 | 109 | -- if type(k) == "string" then 110 | -- k = k:gsub("table:", "{") 111 | -- end 112 | 113 | local indexOrKey = tostring(k) 114 | 115 | if type(k) == "number" then 116 | indexOrKey = string.format("[%d]", k) 117 | elseif type(k) == "boolean" then 118 | indexOrKey = "[\"" .. tostring(k) .. "\"]" 119 | elseif type(k) == "table" then 120 | indexOrKey = string.format("[\"%s\"]", k) 121 | elseif type(k) == "string" and not indexOrKey:match("^[%w_]+$") then 122 | indexOrKey = string.format("[\"%s\"]", k) 123 | end 124 | 125 | if type(v) == "table" then 126 | --TODO: make this better when max depth 127 | if next(v) ~= nil and depth < maxDepth then 128 | writer:appendLine(indent .. PREFIX_TABLE .. indexOrKey .. ": -- " .. tostring(v)) 129 | printTable(v, indent .. INDENT_PREFIX, depth + 1) 130 | else 131 | local tableContents = next(v) ~= nil and " ... " or "" 132 | writer:appendLine(indent .. PREFIX .. indexOrKey .. ":: {" .. tableContents .. "}") 133 | -- print(indent .. string.char(192)) 134 | end 135 | else 136 | local typeName = "" 137 | 138 | if verbose and type(v) ~= "function" then 139 | typeName = " [" .. type(v) .. "]" 140 | end 141 | 142 | if type(v) == "boolean" then 143 | v = tostring(v) 144 | elseif type(v) == "function" then 145 | v = "function()" .. tostring(v) 146 | elseif type(v) == "userdata" then 147 | v = "-- userdata: " .. tostring(v) 148 | end 149 | 150 | -- --HACK: disable type name 151 | -- typeName = "" 152 | 153 | -- if typeName ~= "" then 154 | -- typeName = " [" .. typeName .."]" 155 | -- end 156 | 157 | writer:appendLine(CURRENT_PREFIX .. indexOrKey .. ": " .. v.. typeName) 158 | -- print(CURRENT_PREFIX .. k .. " [" .. typeName .."]" .. ": " .. v) 159 | 160 | firstValue = false 161 | end 162 | end 163 | -- if firstValue then 164 | -- print(indent .. string.char(192)) 165 | -- end 166 | end 167 | 168 | printTable(table) 169 | writer:flush() 170 | end 171 | 172 | 173 | 174 | function DevHelper.saveTableToFile(fileName, tableName, tableObject, maxDepth, ignoredTables, writer, memProfiler, header) 175 | maxDepth = maxDepth or 2 176 | 177 | if tableObject == nil then 178 | Log:error("Table '%s' was nil, skipped saving to file '%s'", tableName, fileName) 179 | return 180 | end 181 | 182 | -- local fileId = createFile(fileName, FileAccess.WRITE) 183 | -- local writer = nil 184 | 185 | 186 | 187 | local function getTableInfo(inputTable, inputIndent, depth, desiredDepth, knownTables, ignoredTables) 188 | inputIndent = inputIndent or "\t" 189 | depth = depth or 0 190 | desiredDepth = desiredDepth or 2 191 | 192 | 193 | if depth > desiredDepth then 194 | return nil 195 | end 196 | 197 | --TODO: improve to keep track of the "path" to known tables and use that with the references? 198 | knownTables = knownTables or {} 199 | 200 | -- Always this current table to known tables 201 | knownTables[tostring(inputTable)] = true 202 | 203 | -- local string1 = "" 204 | local ignore = { 205 | -- string = true, 206 | -- math = true, 207 | -- table = true 208 | } 209 | ignore = ignoredTables or ignore 210 | 211 | for i, j in pairs(inputTable) do 212 | local indexOrKey = tostring(i) 213 | 214 | if type(i) == "number" then 215 | indexOrKey = string.format("[%d]", i) 216 | elseif type(i) == "boolean" then 217 | indexOrKey = "[\"" .. tostring(i) .. "\"]" 218 | elseif type(i) == "table" then 219 | indexOrKey = string.format("[\"%s\"]", i) 220 | elseif type(i) == "string" and not indexOrKey:match("^[%w_]+$") then 221 | indexOrKey = string.format("[\"%s\"]", i) 222 | end 223 | 224 | -- if type(j) == "userdata" then 225 | -- j = string.format("userdata: (%s)", tostring(j)) 226 | -- end 227 | 228 | if type(j) == "table" and knownTables[tostring(j)] == true then 229 | -- string1 = string1 .. string.format("\n%s%s = {}, -- REFERENCE %s\n", inputIndent, indexOrKey, tostring(j)) 230 | --TODO: change to embeeded --[[]] comment instead 231 | writer:appendF("\n%s%s = {}, -- REFERENCE %s", inputIndent, indexOrKey, tostring(j)) 232 | elseif type(j) == "table" and ignore[tostring(i)] == true then 233 | -- print(string.format("Skipped table '%s'", indexOrKey)) 234 | writer:appendF("Skipped table '%s'", indexOrKey) 235 | elseif type(j) == "table" then 236 | -- local string2 = getTableInfo(j, inputIndent .. "\t", depth + 1, desiredDepth, knownTables, ignore) 237 | 238 | -- string1 = string1 .. string.format("\n%s %s = { -- %s\n", inputIndent, tostring(i), tostring(j)) 239 | 240 | 241 | -- if string2 ~= nil then 242 | -- string2 = string.format("%s%s", inputIndent, string2) 243 | 244 | -- -- string1 = string1 .. string.format("\n%s%s = { -- %s%s\n%s},", inputIndent, indexOrKey, tostring(j), string2, inputIndent) 245 | -- writer:appendF("\n%s%s = { -- %s%s\n%s},", inputIndent, indexOrKey, tostring(j), string2, inputIndent) 246 | -- else 247 | -- string1 = string1 .. string.format("\n%s%s = {}, -- %s\n", inputIndent, indexOrKey, tostring(j)) 248 | writer:appendF("\n%s%s = {}, -- %s", inputIndent, indexOrKey, tostring(j)) 249 | -- end 250 | 251 | getTableInfo(j, inputIndent .. "\t", depth + 1, desiredDepth, knownTables, ignore) 252 | 253 | -- knownTables[tostring(j)] = true 254 | 255 | elseif type(j) == "function" and ignore[j] ~= true then 256 | -- string1 = string1 .. string.format("\n%s%s = function() end, -- %s", inputIndent, indexOrKey, tostring(j)) 257 | writer:appendF("\n%s%s = function() end, -- %s", inputIndent, indexOrKey, tostring(j)) 258 | 259 | 260 | --TODO: maybe do a pcall and try to figure out return type and even parameters?! 261 | else 262 | 263 | local value = tostring(j) 264 | if type(j) == "string" then 265 | value = value:gsub("\\\"", "\"") 266 | value = value:gsub("\"", "\\\"") 267 | value = value:gsub("\n", "\\\n") 268 | value = value:gsub("\r", "\\\r") 269 | value = value:gsub("\t", "\\\t") 270 | value = string.format("\"%s\"", value) 271 | elseif type(j) == "userdata" then 272 | value = string.format("\"%s\"", value) 273 | end 274 | 275 | --TODO: remove data type name? 276 | -- string1 = string1 .. string.format("\n%s%s = %s, -- %s", inputIndent, indexOrKey, value, type(j)) 277 | writer:appendF("\n%s%s = %s, -- %s", inputIndent, indexOrKey, value, type(j)) 278 | end 279 | 280 | if memProfiler ~= nil then 281 | memProfiler:update() 282 | end 283 | end 284 | 285 | -- writer:flush() 286 | -- return string1 287 | end 288 | 289 | local file = io.open(fileName, "w") 290 | 291 | if file ~= nil then 292 | if writer == nil then 293 | writer = StringWriter.new(function(...) file:write(...) end) 294 | end 295 | 296 | if writer == nil then 297 | Log:error("No walid writer delegate was found, cannot save table '%s' to file '%s'", tableName, fileName) 298 | return 299 | end 300 | -- Log:debug("Writing to file handle #%d", fileId) 301 | 302 | -- local memProfiler = nil 303 | -- if memProfiler == nil and MemoryProfiler ~= nil then 304 | -- memProfiler = MemoryProfiler.new("Saving table consumed %.2f kb of memory") 305 | -- -- local memUsed, memUsedMax = memProfiler:stop(true) 306 | -- -- Log:debug("Memory used: %.2f (max %.2f) KB", memUsed / 1024, memUsedMax / 1024) 307 | -- end 308 | 309 | writer.memoryProfiler = memProfiler 310 | 311 | local executionTimer = DevHelper.measureStart("") 312 | local lastElapsedValue = 0 313 | 314 | writer.MAX_CHUNKS = 25000 315 | writer.MAX_BUFFER_LENGTH = 1024 * 400 316 | 317 | writer.onFlush = function() 318 | local elapsed = executionTimer:elapsed() 319 | if elapsed - lastElapsedValue >= 10 then 320 | print("Still working on it... Time elapsed " .. elapsed .. " seconds") 321 | lastElapsedValue = elapsed 322 | usleep(1) 323 | if memProfiler ~= nil then 324 | memProfiler:update() 325 | end 326 | collectgarbage() 327 | end 328 | end 329 | 330 | if header ~= nil then 331 | writer:appendLine(header) 332 | end 333 | 334 | writer:appendF("%s = {", tableName) 335 | 336 | getTableInfo(tableObject, "\t", 0, maxDepth - 1, {}, ignoredTables) 337 | 338 | writer:appendLine("\n}") 339 | writer:flush() 340 | 341 | executionTimer = nil 342 | 343 | file:close() 344 | else 345 | Log:error("Filed to open file '%s'", fileName) 346 | return 347 | end 348 | end 349 | -------------------------------------------------------------------------------- /FS25_BetterMinimap/lib/DialogHelper.lua: -------------------------------------------------------------------------------- 1 | 2 | DialogHelper = {} 3 | function DialogHelper.showOptionDialog(parameters) 4 | local optionDialog = OptionDialog.new() 5 | 6 | -- optionDialog.onClose = function() 7 | -- end 8 | 9 | optionDialog.onClickOk = function() 10 | if parameters.callback and (type(parameters.callback)) == "function" then 11 | parameters.callback(parameters.target, optionDialog.optionElement.state, unpack(parameters.args)) 12 | end 13 | optionDialog:close() 14 | end 15 | 16 | optionDialog.onClickBack = function() 17 | if parameters.cancelCallback and (type(parameters.cancelCallback)) == "function" then 18 | parameters.cancelCallback() 19 | end 20 | optionDialog:close() 21 | end 22 | 23 | g_gui:loadGui("dataS/gui/dialogs/OptionDialog.xml", "OptionDialog", optionDialog) 24 | 25 | optionDialog:setTitle(parameters.title or "") 26 | optionDialog:setOptions( parameters.options) 27 | 28 | local defaultOption = parameters.defaultOption or 1 29 | 30 | optionDialog.optionElement:setState( defaultOption) 31 | 32 | optionDialog:show() 33 | 34 | end 35 | 36 | function DialogHelper.showYesNoDialog(parameters) 37 | local optionDialog = OptionDialog.new() 38 | 39 | -- optionDialog.onClose = function() 40 | -- end 41 | 42 | optionDialog.onClickOk = function() 43 | if parameters.callback and (type(parameters.callback)) == "function" then 44 | parameters.callback(parameters.target, optionDialog.optionElement.state, unpack(parameters.args)) 45 | end 46 | optionDialog:close() 47 | end 48 | 49 | optionDialog.onClickBack = function() 50 | if parameters.cancelCallback and (type(parameters.cancelCallback)) == "function" then 51 | parameters.cancelCallback() 52 | end 53 | optionDialog:close() 54 | end 55 | 56 | g_gui:loadGui("dataS/gui/dialogs/OptionDialog.xml", "OptionDialog", optionDialog) 57 | 58 | optionDialog:setTitle(parameters.title or "") 59 | optionDialog:setOptions( parameters.options) 60 | 61 | local defaultOption = parameters.defaultOption or 1 62 | 63 | optionDialog.optionElement:setState( defaultOption) 64 | 65 | optionDialog:show() 66 | 67 | end 68 | 69 | function DialogHelper.showTextInputDialog(parameters) 70 | local optionDialog = OptionDialog.new() 71 | 72 | -- optionDialog.onClose = function() 73 | -- end 74 | 75 | optionDialog.onClickOk = function() 76 | if parameters.callback and (type(parameters.callback)) == "function" then 77 | parameters.callback(parameters.target, optionDialog.optionElement.state, unpack(parameters.args)) 78 | end 79 | optionDialog:close() 80 | end 81 | 82 | optionDialog.onClickBack = function() 83 | if parameters.cancelCallback and (type(parameters.cancelCallback)) == "function" then 84 | parameters.cancelCallback() 85 | end 86 | optionDialog:close() 87 | end 88 | 89 | g_gui:loadGui("dataS/gui/dialogs/OptionDialog.xml", "OptionDialog", optionDialog) 90 | 91 | optionDialog:setTitle(parameters.title or "") 92 | optionDialog:setOptions( parameters.options) 93 | 94 | local defaultOption = parameters.defaultOption or 1 95 | 96 | optionDialog.optionElement:setState( defaultOption) 97 | 98 | optionDialog:show() 99 | 100 | end 101 | -------------------------------------------------------------------------------- /FS25_BetterMinimap/lib/LogHelper.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | LogHelper (Weezls Mod Lib for FS22) - Quality of life log handler for your mod 4 | 5 | The script adds a Log object with some convinient functions to use for loggind and debugging purposes. 6 | 7 | Version: 1.1 8 | Modified: 2023-06-04 9 | Author: w33zl (github.com/w33zl | facebook.com/w33zl) 10 | 11 | Changelog: 12 | v1.0 Initial public release 13 | 14 | License: CC BY-NC-SA 4.0 15 | This license allows reusers to distribute, remix, adapt, and build upon the material in any medium or 16 | format for noncommercial purposes only, and only so long as attribution is given to the creator. 17 | If you remix, adapt, or build upon the material, you must license the modified material under identical terms. 18 | 19 | ]] 20 | local deprecatedMessages = {} 21 | local function dummy() end 22 | local function createLog(modName, modDirectory) 23 | local modDescXML = loadXMLFile("modDesc", modDirectory .. "modDesc.xml"); 24 | local title = getXMLString(modDescXML, "modDesc.title.en"); 25 | delete(modDescXML); 26 | 27 | 28 | 29 | local newLog = { 30 | modName = modName, 31 | title = title or modName, 32 | print = function(self, category, message, ...) 33 | message = (message ~= nil and message:format(...)) or "" 34 | if category ~= nil and category ~= "" then 35 | category = " " .. category .. ":" 36 | else 37 | category = "" 38 | end 39 | print(string.format("[%s]%s %s", self.title, category, tostring(message))) 40 | end, 41 | debug = function(self, message, ...) self:print("DEBUG", message, ...) end, 42 | debugIf = function(self, condition, message, ...) if condition then self:debug(message, ...) end end, 43 | var = function(self, name, variable) 44 | local valType = type(variable) 45 | 46 | if valType == "string" then 47 | variable = "'" .. variable .. "'" 48 | end 49 | 50 | self:print("DEBUG-VAR", "%s=%s [@%s]", name, tostring(variable), valType) 51 | end, 52 | trace = function(self, message, ...)end, 53 | table = function(self, tableName, tableObject, maxDepth) end, 54 | saveTable = function(self, fileName, tableName, tableObject, maxDepth, ignoredTables) end, 55 | tableX = function(self, tableName, tableObject, skipFunctions, unwrapTables)end, 56 | info = function(self, message, ...) self:print("", message, ...) end, 57 | warning = function(self, message, ...) self:print("Warning", message, ...) end, 58 | error = function(self, message, ...) self:print("Error", message, ...) end, 59 | 60 | deprecated = function(self, functionName, replacementMessage, justOnce) 61 | justOnce = justOnce or false 62 | if justOnce and deprecatedMessages[functionName] then 63 | return 64 | end 65 | 66 | self:print("Warning", "[DEPRECATED] %s is deprecated, please use %s instead.", functionName, replacementMessage) 67 | 68 | deprecatedMessages[functionName] = true 69 | end, 70 | 71 | newLog = function(self, name, includeModName) 72 | if name ~= nil then 73 | if includeModName then 74 | name = modName .. "." .. name 75 | end 76 | else 77 | name = title 78 | end 79 | return { 80 | 81 | title = name, 82 | parent = self, 83 | print = self.print, 84 | info = self.info, 85 | warning = self.warning, 86 | error = self.error, 87 | debug = self.debug, 88 | var = self.var, 89 | table = self.table, 90 | tableX = self.tableX, 91 | trace = self.trace, 92 | } 93 | end, 94 | } 95 | 96 | local debugHelperFilename = modDirectory .. "lib/DebugHelper.lua" 97 | 98 | if not fileExists(debugHelperFilename) then 99 | debugHelperFilename = modDirectory .. "scripts/ModLib/DebugHelper.lua" 100 | end 101 | if fileExists(debugHelperFilename) then 102 | newLog:info("Debug mode enabled!") 103 | source(debugHelperFilename) 104 | 105 | if DebugHelper ~= nil then 106 | DebugHelper:inject(newLog) 107 | -- if DebugHelper.dumpTable ~= nil then 108 | -- newLog.table = DebugHelper.dumpTable 109 | -- end 110 | -- if DebugHelper.decodeTable ~= nil then 111 | -- newLog.tableX = DebugHelper.decodeTable 112 | -- end 113 | -- if DebugHelper.traceLog ~= nil then 114 | -- newLog.trace = DebugHelper.traceLog 115 | -- end 116 | end 117 | else 118 | 119 | newLog.debug = dummy 120 | newLog.debugIf = dummy 121 | newLog.var = dummy 122 | newLog.trace = dummy 123 | end 124 | 125 | return newLog 126 | end 127 | 128 | Log = createLog(g_currentModName, g_currentModDirectory) 129 | 130 | 131 | 132 | LogHelper = LogHelper or {} 133 | 134 | --- Starts a execution timer with the given format string. 135 | ---@param formatString string "Format string to print the execution time (you need to add '%f' to the string)" 136 | ---@return table "Execution timer object with the stop function (call ':stop(true)' to supress automatic print of the results)" 137 | ---@remark The results will be printed to the console when the timer is stopped (see ':stop()') unless the 'noPrint' parameter is set to true (e.g. ':stop(true)'). 138 | function LogHelper:measureStart(formatString, ignoreDebugStatus) 139 | if ignoreDebugStatus or DebugHelper ~= nil then 140 | return { -- Enabled 141 | text = formatString, 142 | startTime = getTimeSec(), 143 | stop = function(self, noPrint) 144 | self.endTime = getTimeSec() 145 | self.diff = self.endTime - self.startTime 146 | self.results = string.format(formatString, self.diff) 147 | if not noPrint then 148 | print(self.results) 149 | end 150 | return self.results 151 | end, 152 | } 153 | else -- Disabled 154 | return { 155 | text = "", 156 | startTime = dummy, 157 | stop = dummy, 158 | } 159 | end 160 | end 161 | 162 | 163 | --- Starts a execution timer with the given format string. 164 | ---@param formatString string "Format string to print the execution time (you need to add '%f' to the string)" 165 | ---@return table "Execution timer object with the stop function (call ':stop(true)' to supress automatic print of the results)" 166 | ---@remark The results will be printed to the console when the timer is stopped (see ':stop()') unless the 'noPrint' parameter is set to true (e.g. ':stop(true)'). 167 | function LogHelper:startMemoryProfiling(formatString, ignoreDebugStatus) 168 | if ignoreDebugStatus or DebugHelper ~= nil then 169 | return { -- Enabled 170 | text = formatString, 171 | startMemUsage = gcinfo(), 172 | stop = function(self, noPrint) 173 | self.endMemUsage = gcinfo() 174 | self.diff = (self.endMemUsage - self.startMemUsage) / 1024 175 | self.results = string.format(formatString, self.diff) 176 | if not noPrint then 177 | print(self.results) 178 | end 179 | return self.results 180 | end, 181 | } 182 | else -- Disabled 183 | return { 184 | text = "", 185 | startTime = dummy, 186 | stop = dummy, 187 | } 188 | end 189 | end -------------------------------------------------------------------------------- /FS25_BetterMinimap/lib/ModHelper.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | 3 | ModHelper (Weezls Mod Lib for FS22) - Simplifies the creation of script based mods for FS22 4 | 5 | This utility class acts as a wrapper for Farming Simulator script based mods. It helps with setting up the mod up and 6 | acting as a "bootstrapper" for the main mod class/table. It also add additional utility functions for sourcing additonal files, 7 | manage user settings, assist debugging etc. 8 | 9 | See ModHelper.md (search my GitHub page for it since Giants won't allow "links" in the scripts) for documentation and more details. 10 | 11 | Author: w33zl (https://github.com/w33zl) 12 | Version: 2.2.0 13 | Modified: 2023-08-07 14 | 15 | Changelog: 16 | v2.0 FS22 version 17 | v1.0 Initial public release 18 | 19 | License: CC BY-NC-SA 4.0 20 | This license allows reusers to distribute, remix, adapt, and build upon the material in any medium or 21 | format for noncommercial purposes only, and only so long as attribution is given to the creator. 22 | If you remix, adapt, or build upon the material, you must license the modified material under identical terms. 23 | 24 | ]] 25 | 26 | 27 | 28 | --[[ 29 | 30 | USAGE: 31 | 32 | YourModName = Mod:init() 33 | 34 | -- Logging and debugging (don't forget to add 'scripts/ModLib/LogHelper.lua' to ) 35 | --* debug(), var() and trace() will only print anything in the log if the file 'scripts/ModLib/DebugHelper.lua' is in your mod folder/zip archive 36 | Log:debug("This is a debug message") 37 | Log:var("name", "value") 38 | Log:trace("This is a trace message") 39 | Log:info("This is an info message") 40 | Log:warning("This is a warning message") 41 | Log:error("This is an error message") 42 | 43 | -- Events 44 | function YourModName:beforeLoadMap() end -- Super early event, caution! 45 | function YourModName:loadMap(filename) end -- Executed when the map has finished loading, a good place to begin your mod initialization 46 | function YourModName:beforeStartMission() end -- When user selects "Start" (but as early as possible in that event chain) 47 | function YourModName:startMission() end -- When user selects "Start" 48 | function YourModName:update(dt) end -- Looped as long game is running (CAUTION! Can severely impact performance if not used properly) 49 | 50 | ]] 51 | 52 | local deprecatedMessages = {} 53 | local function deprecated(functionName, replacementMessage, justOnce) 54 | justOnce = justOnce or false 55 | if justOnce and deprecatedMessages[functionName] then 56 | return 57 | end 58 | printWarning(string.format("WARNING: [DEPRECATED] Function '%s' is marked as deprecated, please use %s instead", functionName, replacementMessage)) 59 | deprecatedMessages[functionName] = true 60 | end 61 | 62 | -- This will create the "Mod" base class (and effectively reset any previous references to other mods) 63 | Mod = { 64 | 65 | debugMode = false, 66 | 67 | printInternal = function(self, category, message, ...) 68 | message = (message ~= nil and message:format(...)) or "" 69 | if category ~= nil and category ~= "" then 70 | category = string.format(" %s:", category) 71 | else 72 | category = "" 73 | end 74 | print(string.format("[%s]%s %s", self.title, category, tostring(message))) 75 | end, 76 | 77 | printDebug = function(self, message, ...) 78 | deprecated("Mod:printDebug()", "Log:debug()", true) 79 | printCallstack() 80 | if self.debugMode == true then 81 | self:printInternal("DEBUG", message, ...) 82 | end 83 | end, 84 | 85 | printDebugVar = function(self, name, variable) 86 | deprecated("Mod:printDebugVar()", "Log:var()", true) 87 | if self.debugMode ~= true then 88 | return 89 | end 90 | 91 | -- local tt1 = (val or "") 92 | local valType = type(variable) 93 | 94 | if valType == "string" then 95 | variable = string.format( "'%s'", variable ) 96 | end 97 | 98 | local text = string.format( "%s=%s [@%s]", name, tostring(variable), valType ) 99 | self:printInternal("DBGVAR", text) 100 | end, 101 | 102 | printWarning = function(self, message, ...) 103 | deprecated("Mod:printWarning()", "Log:warning()", true) 104 | self:printInternal("Warning", message, ...) 105 | end, 106 | 107 | printError = function(self, message, ...) 108 | deprecated("Mod:printError()", "Log:error()", true) 109 | self:printInternal("Error", message, ...) 110 | end, 111 | 112 | getIsMultiplayer = function(self) return g_currentMission.missionDynamicInfo.isMultiplayer end, 113 | getIsServer = function(self) return g_currentMission.getIsServer() end, 114 | getIsClient = function(self) return g_currentMission.getIsClient() end, 115 | getIsDedicatedServer = function(self) return not self:getIsClient() and self:getIsServer() end, --g_dedicatedServer 116 | getIsMasterUser = function(self) return g_currentMission.isMasterUser end, 117 | getHasFarmAdminAccess = function(self) return g_currentMission:getHasPlayerPermission("farmManager") end, 118 | getIsValidFarmManager = function(self) return g_currentMission.player ~= nil and self:getHasFarmAdminAccess() and g_currentMission.player.farmId ~= FarmManager.SPECTATOR_FARM_ID end, 119 | } 120 | Mod_MT = { 121 | } 122 | 123 | SubModule = { 124 | printInfo = function(message, ...) Mod:printInfo(message, ...) end, 125 | printDebug = function(message, ...) Mod:printDebug(message) end, 126 | printDebugVar = function(name, variable) Mod:printDebugVar(name, variable) end, 127 | printWarning = function(message, ...) Mod:printWarning(message) end, 128 | printError = function(message, ...) Mod:printError(message) end, 129 | parent = nil, 130 | } 131 | SubModule_MT = { 132 | } 133 | 134 | local function getTrueGlobalG() 135 | return getmetatable(_G).__index 136 | end 137 | 138 | 139 | 140 | -- Set initial values for the global Mod object/"class" 141 | Mod.dir = g_currentModDirectory 142 | Mod.settingsDir = g_currentModSettingsDirectory 143 | Mod.name = g_currentModName 144 | Mod.mod = g_modManager:getModByName(Mod.name) 145 | Mod.env = getfenv() 146 | Mod.__g = getTrueGlobalG() --getfenv(0) --NOTE: WARNING: USE WITH CAUTION!! 147 | Mod.globalEnv = Mod.__g 148 | 149 | -- Wrapper to copy the global (but temporary) g_current* vars into the mod's environment 150 | Mod.env.g_currentModSettingsDirectory = Mod.settingsDir 151 | Mod.env.g_currentModName = Mod.name 152 | Mod.env.g_currentModDirectory = Mod.dir 153 | 154 | local modDescXML = loadXMLFile("modDesc", Mod.dir .. "modDesc.xml"); 155 | Mod.title = getXMLString(modDescXML, "modDesc.title.en"); 156 | Mod.author = getXMLString(modDescXML, "modDesc.author"); 157 | Mod.version = getXMLString(modDescXML, "modDesc.version"); 158 | -- Mod.author = Mod.mod.author 159 | -- Mod.version = Mod.mod.version 160 | delete(modDescXML); 161 | 162 | function Mod:printInfo(message, ...) 163 | deprecated("Mod:printInfo()", "Log:info()", true) 164 | 165 | self:printInternal("", message, ...) 166 | end 167 | 168 | -- Local aliases for convinience 169 | local function printInfo(message) Mod:printInfo(message) end 170 | local function printDebug(message) Mod:printDebug(message) end 171 | local function printDebugVar(name, variable) Mod:printDebugVar(name, variable) end 172 | local function printWarning(message) Mod:printWarning(message) end 173 | local function printError(message) Mod:printError(message) end 174 | 175 | -- Helper functions 176 | local function validateParam(value, typeName, message) 177 | local failed = false 178 | failed = failed or (value == nil) 179 | failed = failed or (typeName ~= nil and type(value) ~= typeName) 180 | failed = failed or (type(value) == string and value == "") 181 | 182 | if failed then print(message) end 183 | 184 | return not failed 185 | end--local function 186 | 187 | local ModSettings = {}; 188 | ModSettings.__index = ModSettings; 189 | 190 | function ModSettings:new(mod) 191 | local newModSettings = {}; 192 | setmetatable(newModSettings, self); 193 | self.__index = self; 194 | newModSettings.__mod = mod; 195 | return newModSettings; 196 | end--function 197 | 198 | function ModSettings:init(name, defaultSettingsFileName, userSettingsFileName) 199 | if not validateParam(name, "string", "Parameter 'name' (#1) is mandatory and must contain a non-empty string") then 200 | return; 201 | end 202 | 203 | if defaultSettingsFileName == nil or type(defaultSettingsFileName) ~= "string" then 204 | self.__mod.printError("Parameter 'defaultSettingsFileName' (#2) is mandatory and must contain a filename"); 205 | return; 206 | end 207 | 208 | --TODO: change to this: g_currentModSettingsDirectory == /Documents/My Games/FarmingSimulator2022/modSettings/MOD_NAME/ 209 | local modSettingsDir = getUserProfileAppPath() .. "modsSettings/" .. g_currentModName 210 | 211 | self._config = { 212 | xmlNodeName = name, 213 | modSettingsDir = modSettingsDir, 214 | defaultSettingsFileName = defaultSettingsFileName, 215 | defaultSettingsPath = self.__mod.dir .. defaultSettingsFileName, 216 | userSettingsFileName = userSettingsFileName, 217 | userSettingsPath = modSettingsDir .. "/" .. userSettingsFileName, 218 | } 219 | 220 | 221 | return self; 222 | end--function 223 | 224 | function ModSettings:load(callback) 225 | if not validateParam(callback, "function", "Parameter 'callback' (#1) is mandatory and must contain a valid callback function") then 226 | return; 227 | end 228 | 229 | local defaultSettingsFile = self._config.defaultSettingsPath; 230 | local userSettingsFile = self._config.userSettingsPath; 231 | local xmlNodeName = self._config.xmlNodeName or "settings" 232 | 233 | if defaultSettingsFile == "" or userSettingsFile == "" then 234 | self.__mod.printError("Cannot load settings, neither a user settings nor a default settings file was supplied. Nothing to read settings from."); 235 | return; 236 | end 237 | 238 | local function executeXmlReader(xmlNodeName, fileName, callback) 239 | local xmlFile = loadXMLFile(xmlNodeName, fileName) 240 | 241 | if xmlFile == nil then 242 | printError("Failed to open/read settings file '" .. fileName .. "'!") 243 | return 244 | end 245 | 246 | local xmlReader = { 247 | xmlFile = xmlFile, 248 | xmlNodeName = xmlNodeName, 249 | 250 | getKey = function(self, categoryName, valueName) 251 | local xmlKey = self.xmlNodeName 252 | 253 | 254 | if categoryName ~= nil and categoryName ~= "" then 255 | xmlKey = xmlKey .. "." .. categoryName 256 | end 257 | 258 | xmlKey = xmlKey .. "." .. valueName 259 | 260 | return xmlKey 261 | end, 262 | 263 | readBool = function(self, categoryName, valueName, defaultValue) 264 | return Utils.getNoNil(getXMLBool(self.xmlFile, self:getKey(categoryName, valueName)), defaultValue or false) 265 | end, 266 | readFloat = function(self, categoryName, valueName, defaultValue) 267 | return Utils.getNoNil(getXMLFloat(self.xmlFile, self:getKey(categoryName, valueName)), defaultValue or 0.0) 268 | end, 269 | readString = function(self, categoryName, valueName, defaultValue) 270 | return Utils.getNoNil(getXMLString(self.xmlFile, self:getKey(categoryName, valueName)), defaultValue or "") 271 | end, 272 | 273 | } 274 | callback(xmlReader); 275 | end 276 | 277 | if fileExists(defaultSettingsFile) then 278 | executeXmlReader(xmlNodeName, defaultSettingsFile, callback); 279 | end 280 | 281 | if fileExists(userSettingsFile) then 282 | executeXmlReader(xmlNodeName, userSettingsFile, callback); 283 | end 284 | 285 | end--function 286 | 287 | function ModSettings:save(callback) 288 | if not validateParam(callback, "function", "Parameter 'callback' (#1) is mandatory and must contain a valid callback function") then 289 | return; 290 | end 291 | 292 | local userSettingsFile = self._config.userSettingsPath; 293 | local xmlNodeName = self._config.xmlNodeName or "settings" 294 | 295 | if userSettingsFile == "" then 296 | printError("Missing filename for user settings, cannot save mod settings."); 297 | return; 298 | end 299 | 300 | if not fileExists(userSettingsFile) then 301 | createFolder(self._config.modSettingsDir) 302 | end 303 | 304 | local function executeXmlWriter(xmlNodeName, fileName, callback) 305 | local xmlFile = createXMLFile(xmlNodeName, fileName, xmlNodeName) 306 | 307 | if xmlFile == nil then 308 | printError("Failed to create/write to settings file '" .. fileName .. "'!") 309 | return 310 | end 311 | 312 | local xmlWriter = { 313 | xmlFile = xmlFile, 314 | xmlNodeName = xmlNodeName, 315 | 316 | getKey = function(self, categoryName, valueName) 317 | local xmlKey = self.xmlNodeName 318 | 319 | 320 | if categoryName ~= nil and categoryName ~= "" then 321 | xmlKey = xmlKey .. "." .. categoryName 322 | end 323 | 324 | xmlKey = xmlKey .. "." .. valueName 325 | 326 | return xmlKey 327 | end, 328 | 329 | saveBool = function(self, categoryName, valueName, value) 330 | return setXMLBool(self.xmlFile, self:getKey(categoryName, valueName), Utils.getNoNil(value, false)) 331 | end, 332 | 333 | saveFloat = function(self, categoryName, valueName, value) 334 | return setXMLFloat(self.xmlFile, self:getKey(categoryName, valueName), Utils.getNoNil(value, 0.0)) 335 | end, 336 | 337 | saveString = function(self, categoryName, valueName, value) 338 | return setXMLString(self.xmlFile, self:getKey(categoryName, valueName), Utils.getNoNil(value, "")) 339 | end, 340 | 341 | } 342 | callback(xmlWriter); 343 | 344 | saveXMLFile(xmlFile) 345 | delete(xmlFile) 346 | end 347 | 348 | executeXmlWriter(xmlNodeName, userSettingsFile, callback); 349 | 350 | return self 351 | end--function 352 | 353 | function Mod:source(file) 354 | source(self.dir .. file); 355 | return self; -- Return self to keep the "chain" (fluent) 356 | end--function 357 | 358 | function Mod:trySource(file, silentFail) 359 | local filename = self.dir .. file 360 | 361 | silentFail = silentFail or false 362 | 363 | if fileExists(filename) then 364 | source(filename); 365 | elseif not silentFail then 366 | self:printWarning("Failed to load sourcefile '" .. filename .. "'") 367 | end 368 | return self; -- Return self to keep the "chain" (fluent) 369 | end--function 370 | 371 | function Mod:init() 372 | local newMod = self:new(); 373 | 374 | addModEventListener(newMod); 375 | 376 | print(string.format("Load mod: %s (v%s) by %s", newMod.title, newMod.version, newMod.author)) 377 | 378 | return newMod; 379 | end--function 380 | 381 | function Mod:enableDebugMode() 382 | deprecated("enableDebugMode()", "Log class") 383 | 384 | self.debugMode = true 385 | 386 | self:printDebug("Debug mode enabled") 387 | 388 | return self; -- Return self to keep the "chain" (fluent) 389 | end--function 390 | 391 | function Mod:loadSound(name, fileName) 392 | local newSound = createSample(name) 393 | loadSample(newSound, self.dir .. fileName, false) 394 | return newSound 395 | end--function 396 | 397 | function Mod:new() 398 | local newMod = {} 399 | 400 | setmetatable(newMod, self) 401 | self.__index = self 402 | 403 | newMod.dir = g_currentModDirectory; 404 | newMod.name = g_currentModName 405 | newMod.settings = ModSettings:new(newMod); 406 | 407 | 408 | local modDescXML = loadXMLFile("modDesc", newMod.dir .. "modDesc.xml"); 409 | newMod.title = getXMLString(modDescXML, "modDesc.title.en"); 410 | newMod.author = getXMLString(modDescXML, "modDesc.author"); 411 | newMod.version = getXMLString(modDescXML, "modDesc.version"); 412 | delete(modDescXML); 413 | 414 | -- newMod.startMission = function() end -- Dummy function/event 415 | 416 | -- FSBaseMission.onStartMission = Utils.appendedFunction(FSBaseMission.onStartMission, function(...) 417 | -- newMod.startMission(newMod, ...) 418 | -- end); 419 | 420 | FSBaseMission.onStartMission = Utils.appendedFunction(FSBaseMission.onStartMission, function(baseMission, ...) 421 | if newMod.startMission ~= nil and type(newMod.startMission) == "function" then 422 | newMod:startMission(baseMission, ...) 423 | end 424 | end) 425 | 426 | FSBaseMission.onStartMission = Utils.prependedFunction(FSBaseMission.onStartMission, function(baseMission, ...) 427 | if newMod.beforeStartMission ~= nil and type(newMod.beforeStartMission) == "function" then 428 | newMod:beforeStartMission(baseMission, ...) 429 | end 430 | end) 431 | 432 | -- Mission00.loadMission00Finished = Utils.appendedFunction(Mission00.loadMission00Finished, function(mission00, ...) 433 | -- if newMod.missionLoaded ~= nil and type(newMod.missionLoaded) == "function" then 434 | -- newMod:missionLoaded(mission00, ...) 435 | -- end 436 | -- end) 437 | 438 | FSBaseMission.load = Utils.appendedFunction(FSBaseMission.load, function(baseMission, ...) 439 | if newMod.load ~= nil and type(newMod.load) == "function" then 440 | newMod:load(baseMission, ...) 441 | end 442 | end) 443 | 444 | 445 | FSBaseMission.initialize = Utils.appendedFunction(FSBaseMission.initialize, function(baseMission, ...) 446 | if newMod.initMission ~= nil and type(newMod.initMission) == "function" then 447 | newMod:initMission(baseMission, ...) 448 | end 449 | end) 450 | 451 | FSBaseMission.loadMap = Utils.prependedFunction(FSBaseMission.loadMap, function(baseMission, ...) 452 | if newMod.beforeLoadMap ~= nil and type(newMod.beforeLoadMap) == "function" then 453 | newMod:beforeLoadMap(baseMission, ...) 454 | end 455 | end) 456 | 457 | FSBaseMission.loadMap = Utils.appendedFunction(FSBaseMission.loadMap, function(baseMission, ...) 458 | if newMod.afterLoadMap ~= nil and type(newMod.afterLoadMap) == "function" then 459 | newMod:afterLoadMap(baseMission, ...) 460 | end 461 | end) 462 | 463 | 464 | FSBaseMission.loadMapFinished = Utils.prependedFunction(FSBaseMission.loadMapFinished, function(baseMission, ...) 465 | if newMod.loadMapFinished ~= nil and type(newMod.loadMapFinished) == "function" then 466 | newMod:loadMapFinished(baseMission, ...) 467 | end 468 | end) 469 | 470 | FSBaseMission.loadMapFinished = Utils.appendedFunction(FSBaseMission.loadMapFinished, function(baseMission, ...) 471 | if newMod.afterLoadMapFinished ~= nil and type(newMod.afterLoadMapFinished) == "function" then 472 | newMod:afterLoadMapFinished(baseMission, ...) 473 | end 474 | end) 475 | 476 | FSBaseMission.onMinuteChanged = Utils.appendedFunction(FSBaseMission.onMinuteChanged, function(baseMission, ...) 477 | if newMod.onMinuteChanged ~= nil and type(newMod.onMinuteChanged) == "function" then 478 | newMod:onMinuteChanged(baseMission, ...) 479 | end 480 | end) 481 | 482 | FSBaseMission.onHourChanged = Utils.appendedFunction(FSBaseMission.onHourChanged, function(baseMission, ...) 483 | if newMod.onHourChanged ~= nil and type(newMod.onHourChanged) == "function" then 484 | newMod:onHourChanged(baseMission, ...) 485 | end 486 | end) 487 | 488 | FSBaseMission.onDayChanged = Utils.appendedFunction(FSBaseMission.onDayChanged, function(baseMission, ...) 489 | if newMod.onDayChanged ~= nil and type(newMod.onDayChanged) == "function" then 490 | newMod:onDayChanged(baseMission, ...) 491 | end 492 | end) 493 | 494 | return newMod; 495 | end--function 496 | 497 | function SubModule:new(parent, table) 498 | local newSubModule = table or {} 499 | 500 | setmetatable(newSubModule, self) 501 | self.__index = self 502 | newSubModule.parent = parent 503 | return newSubModule 504 | end--function 505 | 506 | function Mod:newSubModule(table) 507 | return SubModule:new(self, table) 508 | end--function 509 | 510 | --- Check if the third party mod is loaded 511 | ---@param modName string The name of the mod/zip-file 512 | ---@param envName string (Optional)The environment name to check for 513 | function Mod:getIsModActive(modName, envName) 514 | 515 | if modName == nil and envName == nil then 516 | return false 517 | end 518 | 519 | local testMod = g_modManager:getModByName(modName) 520 | if testMod == nil then 521 | return false 522 | end 523 | 524 | local modNameCheck = false 525 | local envCheck = false 526 | 527 | 528 | modNameCheck = (modName == nil) or (g_modIsLoaded[modName] ~= nil) 529 | envCheck = (envName == nil) or (getfenv(0)[envName] ~= nil) 530 | 531 | return modNameCheck and envCheck 532 | 533 | end--function 534 | 535 | -- function Mod:getIsSeasonsActive() 536 | -- --TODO: fix check basegame option 537 | -- return false -- Mod:getIsModActive(nil, "g_seasons") 538 | -- end 539 | 540 | ModHelper = {} 541 | 542 | function ModHelper.isModInstalled(name) 543 | return g_modManager:getModByName(name) ~= nil 544 | end 545 | 546 | function ModHelper.isModActive(name) 547 | return g_modIsLoaded[name] == true 548 | end--function 549 | 550 | function ModHelper.getMod(name) 551 | return g_modManager:getModByName(name) 552 | end--function 553 | 554 | function ModHelper.getModEnvironment(name) 555 | return getTrueGlobalG()[name] 556 | end--function 557 | 558 | function ModHelper.getIsMaizePlusActive() 559 | return ModHelper.isModActive("FS22_MaizePlus") 560 | end--function 561 | 562 | function ModHelper.isMaizePlusForageActive() 563 | return ModHelper.isModActive("FS22_MaizePlus_forageExtension") 564 | end--function 565 | 566 | function ModHelper.isMaizePlusAnimalFoodAdditionsActive() 567 | return ModHelper.isModActive("FS22_maizePlus_animalFoodAdditions") 568 | end--function 569 | 570 | ---comment 571 | ---@param scope integer|string 572 | ---@param trueG boolean 573 | ---@return table 574 | function ModHelper.getfenv(scope, trueG) 575 | if not trueG or scope == nil then 576 | return getfenv(scope) -- Use default (nerfed) getfenv 577 | else 578 | local __g = getTrueGlobalG() 579 | local tempObject = __g ~= nil and __g[scope] 580 | if tempObject == nil and type(scope) == "string" then 581 | tempObject = __g ~= nil and __g["FS22_" .. scope] 582 | end 583 | return tempObject 584 | end 585 | end--function 586 | -------------------------------------------------------------------------------- /FS25_BetterMinimap/modDesc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 0.9.1.32 4 | FS25:SupremeClicker (original FS17:jDanek); 5 | 6 | <en>Better Minimap</en> 7 | <de>Bessere Minimap</de> 8 | <fr>Meilleure Minicarte</fr> 9 | <cz>Lepší Minimapa</cz> 10 | <pl>Ulepszona minimap</pl> 11 | 12 | 13 | 25 | 38 | 51 | 64 | 77 | 78 | 79 | FS25_BetterMinimap_icon.dds 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 94 | 95 | 97 | 98 | 99 | 101 | 102 | 103 | 105 | 106 | 107 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /FS25_BetterMinimap/translations/translation_cz.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | jDanek 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /FS25_BetterMinimap/translations/translation_de.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | jDanek 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /FS25_BetterMinimap/translations/translation_en.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | jDanek 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /FS25_BetterMinimap/translations/translation_fr.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | jDanek 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /FS25_BetterMinimap/translations/translation_pl.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Ziuta 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /FS25_BetterMinimap/translations/translation_ru.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Gonimy_Vetrom 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /FS25_BetterMinimap/ui/FS25_BetterMinimap_UI.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- Mod: FS25_BetterMinimap 3 | -- 4 | -- Author: original FS17:jDanek; FS25:SupremeClicker 5 | -- email: gvdhaak (at) gmail (dot) com 6 | -- @Date: 28.02.2025 7 | -- @Version: 1.0.0.0 8 | 9 | -- TODO: NOTE:Total lua script has to be debugged togetheer with UI xml. 10 | 11 | local myName = "FS25_BetterMinimap_UI" 12 | 13 | -- FS25_BetterMinimap_UI: Handles UI interactions for FS25 Better Minimap 14 | 15 | source(g_currentModDirectory .. "lib/DialogHelper.lua") 16 | 17 | FS25_BetterMinimap_UI = {} 18 | local FS25_BetterMinimap_UI_mt = Class(FS25_BetterMinimap_UI, ScreenElement) 19 | 20 | --- Constructor for the UI screen. 21 | function FS25_BetterMinimap_UI:new(target, custom_mt) 22 | if custom_mt == nil then 23 | custom_mt = FS25_BetterMinimap_UI_mt 24 | end 25 | local self = ScreenElement:new(target, custom_mt) 26 | self.returnScreenName = "" 27 | return self 28 | end 29 | 30 | --- Called when the UI screen opens. 31 | function FS25_BetterMinimap_UI:onOpen() 32 | FS25_BetterMinimap_UI:superClass().onOpen(self) 33 | 34 | -- Populate dropdown with refresh rate options 35 | self.refreshRateDropdown:setTexts({"15s", "30s", "45s", "60s"}) 36 | 37 | -- Set currently selected refresh rate 38 | local currentRate = tostring(FS25_BetterMinimap.config.refreshRate) .. "s" 39 | for i, option in ipairs({"15s", "30s", "45s", "60s"}) do 40 | if option == currentRate then 41 | self.refreshRateDropdown:setState(i) 42 | break 43 | end 44 | end 45 | end 46 | 47 | --- Called when the user selects a new refresh rate. 48 | function FS25_BetterMinimap_UI:onRefreshRateChange(element) 49 | local selectedIndex = element:getState() 50 | local refreshOptions = {15, 30, 45, 60} 51 | FS25_BetterMinimap.config.refreshRate = refreshOptions[selectedIndex] 52 | end 53 | 54 | --- Saves the new settings and closes the UI. 55 | function FS25_BetterMinimap_UI:onSaveSettings() 56 | DialogHelper:showConfirmationDialog("Save Changes", "Do you want to save the new minimap settings?", 57 | function() 58 | FS25_BetterMinimap:saveSettings() 59 | g_gui:closeGui() 60 | end 61 | ) 62 | end 63 | 64 | --- Closes the UI without saving changes. 65 | function FS25_BetterMinimap_UI:onClose() 66 | g_gui:closeGui() 67 | end 68 | -------------------------------------------------------------------------------- /FS25_BetterMinimap/ui/FS25_BetterMinimap_UI.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |