├── .gitignore ├── README.md ├── Resources ├── Client │ ├── floodBeamMP.zip │ └── floodBeamMP │ │ ├── lua │ │ └── ge │ │ │ └── extensions │ │ │ └── floodBeamMP.lua │ │ └── scripts │ │ └── flood │ │ └── modScript.lua └── Server │ └── Flood │ ├── flood.lua │ ├── main.lua │ └── multiplayer.lua └── automod.ps1 /.gitignore: -------------------------------------------------------------------------------- 1 | .sentry-native 2 | *.log 3 | *.exe 4 | *.toml 5 | 6 | # Can't delete it right now so just ignore it.. 7 | /Server/ 8 | /Releases/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BeamMP-FloodMod 2 | 3 | This is a resource for BeamMP that adds a flood to all sorts of maps (as long as they have an ocean). 4 | 5 | ## Installation 6 | 1. Download the latest release from the [releases page](https://github.com/vulcan-dev/BeamMP-FloodMod/releases) 7 | 2. Copy `floodBeamMP.zip` into your `BeamMP-Server/Resources/Client` folder 8 | 3. Copy `Flood` into your `BeamMP-Server/Resources/Server` folder 9 | 10 | ## Commands 11 | ### v1.1.0 Preview 12 | - `/flood_start` - Starts the flood 13 | - `/flood_stop` - Stops the flood 14 | - `/flood_reset` - Resets the height and disables the flood 15 | - `/flood_resetAt` - TODO 16 | - `/flood_level ` - Sets the flood level/height 17 | - `/flood_speed ` - Sets the flood speed (0.001 is default) 18 | - `/flood_decrease ` - Makes the water level decrease instead of increase 19 | - `/flood_limit ` - Sets the flood height limit 20 | - `/flood_printSettings` - Prints the current flood settings 21 | 22 | ### v1.0.1 23 | - `/flood_start` - Starts the flood 24 | - `/flood_stop` - Stops the flood 25 | - `/flood_reset` - Resets the height and disables the flood 26 | - `/flood_setLevel ` - Sets the flood level/height 27 | - `/flood_setSpeed ` - Sets the flood speed (0.001 is default) 28 | - `/flood_setDecrease ` - Makes the water level decrease instead of increase 29 | - `/flood_setLimit ` - Sets the flood height limit 30 | - `/flood_setLimitEnabled ` - Enables or disables the flood height limit 31 | - `/flood_printSettings` - Prints the current flood settings 32 | 33 | ## Credits 34 | - [Dudekahedron](https://github.com/StanleyDudek) - Testing and providing a test server 35 | -------------------------------------------------------------------------------- /Resources/Client/floodBeamMP.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vulcan-dev/BeamMP-FloodMod/e463c56a211ec25aab7624615359bab8a68bf4bc/Resources/Client/floodBeamMP.zip -------------------------------------------------------------------------------- /Resources/Client/floodBeamMP/lua/ge/extensions/floodBeamMP.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | local allWater = {} 4 | local ocean = nil 5 | local calledOnInit = false -- For calling "E_OnInitialize" only once when BeamMP's experimental "Disable lua reloading when bla bla bla" is enabled 6 | 7 | local function findObject(objectName, className) 8 | local obj = scenetree.findObject(objectName) 9 | if obj then return obj end 10 | if not className then return nil end 11 | 12 | local objects = scenetree.findClassObjects(className) 13 | for _, name in pairs(objects) do 14 | local object = scenetree.findObject(name) 15 | if string.find(name, objectName) then return object end 16 | end 17 | 18 | return 19 | end 20 | 21 | local function tableToMatrix(tbl) 22 | local mat = MatrixF(true) 23 | mat:setColumn(0, tbl.c0) 24 | mat:setColumn(1, tbl.c1) 25 | mat:setColumn(2, tbl.c2) 26 | mat:setColumn(3, tbl.c3) 27 | return mat 28 | end 29 | 30 | local hiddenWater = {} 31 | 32 | local function getWaterLevel() 33 | if not ocean then return nil end 34 | return ocean.position:getColumn(3).z 35 | end 36 | 37 | local function getAllWater() 38 | local water = {} 39 | local toSearch = { 40 | "River", 41 | "WaterBlock" 42 | } 43 | 44 | for _, name in pairs(toSearch) do 45 | local objects = scenetree.findClassObjects(name) 46 | for _, id in pairs(objects) do 47 | if not tonumber(id) then 48 | local source = scenetree.findObject(id) 49 | if source then 50 | table.insert(water, source) 51 | end 52 | else 53 | local source = scenetree.findObjectById(tonumber(id)) 54 | if source then 55 | table.insert(water, source) 56 | end 57 | end 58 | end 59 | end 60 | 61 | return water 62 | end 63 | 64 | local function handleWaterSources() 65 | local height = getWaterLevel() 66 | 67 | for id, water in pairs(allWater) do 68 | local waterHeight = water.position:getColumn(3).z 69 | if M.hideCoveredWater and not hiddenWater[id] and waterHeight < height then 70 | water.isRenderEnabled = false 71 | hiddenWater[id] = true 72 | elseif waterHeight > height and hiddenWater[id] then 73 | water.isRenderEnabled = true 74 | hiddenWater[id] = false 75 | elseif not M.hideCoveredWater and hiddenWater[id] then 76 | water.isRenderEnabled = true 77 | hiddenWater[id] = false 78 | end 79 | end 80 | end 81 | 82 | AddEventHandler("E_OnPlayerLoaded", function() 83 | allWater = getAllWater() 84 | ocean = findObject("Ocean", "WaterPlane") 85 | 86 | if calledOnInit then return end 87 | TriggerServerEvent("E_OnInitiliaze", tostring(getWaterLevel())) 88 | calledOnInit = true 89 | end) 90 | 91 | AddEventHandler("E_SetWaterLevel", function(level) 92 | level = tonumber(level) or nil 93 | if not level then log("W", "setWaterLevel", "level is nil") return end 94 | if not ocean then log("W", "setWaterLevel", "ocean is nil") return end 95 | local c3 = ocean.position:getColumn(3) 96 | ocean.position = tableToMatrix({ 97 | c0 = ocean.position:getColumn(0), 98 | c1 = ocean.position:getColumn(1), 99 | c2 = ocean.position:getColumn(2), 100 | c3 = vec3(c3.x, c3.y, level) 101 | }) 102 | 103 | handleWaterSources() -- Hides/Shows water sources depending on the ocean level 104 | end) 105 | 106 | AddEventHandler("E_SetRainVolume", function(volume) 107 | local volume = tonumber(volume) or 0 108 | if not volume then 109 | log("W", "E_SetRainVolume", "Invalid data: " .. tostring(data)) 110 | return 111 | end 112 | 113 | local rainObj = findObject("rain_coverage", "Precipitation") 114 | if not rainObj then 115 | log("W", "E_SetRainVolume", "rain_coverage not found") 116 | return 117 | end 118 | 119 | local soundObj = findObject("rain_sound") 120 | if soundObj then 121 | soundObj:delete() 122 | end 123 | 124 | if volume == -1 then -- Automatic 125 | volume = rainObj.numDrops / 100 126 | end 127 | 128 | soundObj = createObject("SFXEmitter") 129 | soundObj.scale = Point3F(100, 100, 100) 130 | soundObj.fileName = String('/art/sound/environment/amb_rain_medium.ogg') 131 | soundObj.playOnAdd = true 132 | soundObj.isLooping = true 133 | soundObj.volume = volume 134 | soundObj.isStreaming = true 135 | soundObj.is3D = false 136 | soundObj:registerObject('rain_sound') 137 | end) 138 | 139 | AddEventHandler("E_SetRainAmount", function(amount) 140 | amount = tonumber(amount) or 0 141 | local rainObj = findObject("rain_coverage", "Precipitation") 142 | if not rainObj then -- Create the rain object 143 | rainObj = createObject("Precipitation") 144 | rainObj.dataBlock = scenetree.findObject("rain_medium") 145 | rainObj.splashSize = 0 146 | rainObj.splashMS = 0 147 | rainObj.animateSplashes = 0 148 | rainObj.boxWidth = 16.0 149 | rainObj.boxHeight = 10.0 150 | rainObj.dropSize = 1.0 151 | rainObj.doCollision = true 152 | rainObj.hitVehicles = true 153 | rainObj.rotateWithCamVel = true 154 | rainObj.followCam = true 155 | rainObj.useWind = true 156 | rainObj.minSpeed = 0.4 157 | rainObj.maxSpeed = 0.5 158 | rainObj.minMass = 4 159 | rainObj.masMass = 5 160 | rainObj:registerObject('rain_coverage') 161 | end 162 | 163 | rainObj.numDrops = amount 164 | end) 165 | 166 | M.hideCoveredWater = hideCoveredWater 167 | 168 | return M -------------------------------------------------------------------------------- /Resources/Client/floodBeamMP/scripts/flood/modScript.lua: -------------------------------------------------------------------------------- 1 | load("floodBeamMP") 2 | registerCoreModule("floodBeamMP") -------------------------------------------------------------------------------- /Resources/Server/Flood/flood.lua: -------------------------------------------------------------------------------- 1 | require("multiplayer") 2 | 3 | local M = {} 4 | 5 | M.options = { 6 | oceanLevel = 0.0, 7 | floodSpeed = 0.001, 8 | limit = 0.0, 9 | limitEnabled = false, 10 | enabled = false, 11 | decrease = false, 12 | resetAt = 0.0, -- Doesn't reset everything, just the ocean level. Will be used for automatic flooding 13 | rainAmount = 0.0, 14 | rainVolume = -1.0, -- -1.0 = automatic, 0.0 = off, 1.0 = max 15 | floodWithRain = true 16 | } 17 | 18 | M.isOceanValid = false 19 | M.initialLevel = 0.0 20 | M.commands = {} 21 | 22 | local invalidCount = 0 23 | 24 | function onPlayerJoin(pid) 25 | local success = MP.TriggerClientEvent(pid, "E_OnPlayerLoaded", "") 26 | if not success then 27 | print("Failed to send \"E_OnPlayerLoaded\" to " .. pid) 28 | end 29 | 30 | -- Sync rain & volume 31 | if M.options.rainAmount > 0.0 then 32 | MP.TriggerClientEvent(pid, "E_SetRainAmount", tostring(M.options.rainAmount)) 33 | end 34 | 35 | if M.options.rainVolume == -1.0 or M.options.rainVolume > 0.0 then 36 | MP.TriggerClientEvent(pid, "E_SetRainVolume", tostring(M.options.rainVolume)) 37 | end 38 | end 39 | 40 | function onInit() 41 | MP.CancelEventTimer("ET_Update") 42 | 43 | for pid, player in pairs(MP.GetPlayers()) do 44 | onPlayerJoin(pid) 45 | end 46 | end 47 | 48 | local function setWaterLevel(level) 49 | if not M.isOceanValid then 50 | print("setWaterLevel: ocean is nil") 51 | return 52 | end 53 | 54 | MP.TriggerClientEvent(-1, "E_SetWaterLevel", tostring(level)) 55 | end 56 | 57 | function T_Update() 58 | if not M.isOceanValid or not M.options.enabled then return end 59 | 60 | local level = M.options.oceanLevel 61 | local changeAmount = M.options.floodSpeed 62 | local limit = M.options.limit 63 | local limitEnabled = M.options.limitEnabled 64 | local decrease = M.options.decrease 65 | 66 | -- If we have rain, add the rain amount to the change amount. The flood speed will now act as a multiplier. 67 | if M.floodWithRain then 68 | local rainAmount = M.options.rainAmount 69 | if rainAmount > 0.0 then 70 | changeAmount = changeAmount + rainAmount * 0.0001 71 | end 72 | end 73 | 74 | -- Increase or decrease the level 75 | if decrease then 76 | level = level - changeAmount 77 | else 78 | level = level + changeAmount 79 | end 80 | 81 | -- Reset at (0 = disabled) 82 | if M.options.resetAt > 0.0 and level >= M.options.resetAt then 83 | level = M.initialLevel 84 | elseif M.options.resetAt < 0.0 and level <= M.options.resetAt then 85 | level = M.initialLevel 86 | end 87 | 88 | -- Limit the level 89 | if limitEnabled then 90 | if decrease then 91 | if level < limit then 92 | level = limit 93 | end 94 | else 95 | if level > limit then 96 | level = limit 97 | end 98 | end 99 | end 100 | 101 | M.options.oceanLevel = level 102 | setWaterLevel(level) 103 | end 104 | 105 | function E_OnInitialize(pid, waterLevel) 106 | waterLevel = tonumber(waterLevel) or nil 107 | 108 | -- Make sure the level has an ocean, we use "invalidCount" to make sure it's not just 1 player that doesn't have an ocean 109 | if not waterLevel and invalidCount < 2 then 110 | print("E_OnInitialize: waterLevel for player " .. GetPlayerName(pid) .. " is nil") 111 | invalidCount = invalidCount + 1 112 | return 113 | elseif not waterLevel and invalidCount >= 2 then 114 | print("This map doesn't have an ocean, disabling flood") 115 | M.isOceanValid = false 116 | return 117 | end 118 | 119 | M.isOceanValid = true 120 | if M.initialLevel == 0.0 then 121 | print("Setting initial water level to " .. waterLevel) 122 | M.initialLevel = waterLevel -- We sadly have to rely on the client 😅🔫 123 | end 124 | end 125 | 126 | M.commands["start"] = function(pid) 127 | if not M.isOceanValid then 128 | MP.hSendChatMessage(pid, "This map doesn't have an ocean, unable to flood") 129 | return 130 | end 131 | 132 | if M.options.enabled then 133 | MP.hSendChatMessage(pid, "Flood has already started") 134 | return 135 | end 136 | 137 | M.options.enabled = true 138 | if M.options.oceanLevel == 0.0 then 139 | M.options.oceanLevel = M.initialLevel 140 | end 141 | 142 | MP.CreateEventTimer("ET_Update", 25) 143 | 144 | MP.hSendChatMessage(-1, "A flood has started!") 145 | end 146 | 147 | M.commands["stop"] = function(pid) 148 | if not M.options.enabled then 149 | MP.hSendChatMessage(pid, "Flood is already stopped") 150 | return 151 | end 152 | 153 | MP.CancelEventTimer("ET_Update") 154 | M.options.enabled = false 155 | MP.hSendChatMessage(-1, "The flood has stopped!") 156 | end 157 | 158 | M.commands["reset"] = function(pid) 159 | if not M.isOceanValid then 160 | MP.hSendChatMessage(pid, "This map doesn't have an ocean, unable to flood") 161 | return 162 | end 163 | 164 | MP.CancelEventTimer("ET_Update") 165 | M.options.enabled = false 166 | M.options.oceanLevel = M.initialLevel 167 | setWaterLevel(M.initialLevel) 168 | end 169 | 170 | M.commands["level"] = function(pid, level) 171 | level = tonumber(level) or nil 172 | if not level then 173 | MP.hSendChatMessage(pid, "Invalid level") 174 | return 175 | end 176 | 177 | if not M.isOceanValid then 178 | MP.hSendChatMessage(pid, "This map doesn't have an ocean, unable to flood") 179 | return 180 | end 181 | 182 | M.options.oceanLevel = level 183 | setWaterLevel(level) 184 | MP.hSendChatMessage(pid, "Set water level to " .. level) 185 | end 186 | 187 | M.commands["speed"] = function(pid, speed) 188 | speed = tonumber(speed) or nil 189 | if not speed then 190 | MP.hSendChatMessage(pid, "Invalid speed") 191 | return 192 | end 193 | 194 | if speed < 0.0 then 195 | MP.hSendChatMessage(pid, "Speed can't be negative, setting to 0.0") 196 | speed = 0.0 197 | end 198 | 199 | -- Do I limit the max? Hmmm, not sure 🤔 200 | 201 | M.options.floodSpeed = speed 202 | MP.hSendChatMessage(pid, "Set flood speed to " .. speed) 203 | end 204 | 205 | M.commands["limit"] = function(pid, limit) 206 | limit = tonumber(limit) or nil 207 | if not limit then 208 | MP.hSendChatMessage(pid, "Invalid limit") 209 | return 210 | end 211 | 212 | M.options.limit = limit 213 | MP.hSendChatMessage(pid, "Set flood limit to " .. limit) 214 | end 215 | 216 | M.commands["limitEnabled"] = function(pid, enabled) 217 | if not enabled then 218 | MP.hSendChatMessage(pid, "Invalid value") 219 | return 220 | end 221 | 222 | if string.lower(enabled) == "true" or enabled == "1" then 223 | enabled = true 224 | elseif string.lower(enabled) == "false" or enabled == "0" then 225 | enabled = false 226 | else 227 | MP.hSendChatMessage(pid, "Please use true/false or 1/0") 228 | return 229 | end 230 | 231 | M.options.limitEnabled = enabled 232 | MP.hSendChatMessage(pid, tostring(enabled and "Enabled" or "Disabled") .. " flood limit") 233 | end 234 | 235 | M.commands["resetAt"] = function(pid, level) 236 | level = tonumber(level) or nil 237 | if not level then 238 | MP.hSendChatMessage(pid, "Invalid level") 239 | return 240 | end 241 | 242 | M.options.resetAt = level 243 | MP.hSendChatMessage(pid, "Set reset level to " .. level) 244 | end 245 | 246 | M.commands["rainAmount"] = function(pid, amount) 247 | amount = tonumber(amount) or nil 248 | if not amount then 249 | MP.hSendChatMessage(pid, "Invalid amount") 250 | return 251 | end 252 | 253 | M.options.rainAmount = amount 254 | MP.hSendChatMessage(pid, "Set rain amount to " .. amount) 255 | MP.TriggerClientEvent(-1, "E_SetRainAmount", tostring(amount)) 256 | if M.options.rainVolume == -1 then -- Update the volume if it's set to auto 257 | MP.TriggerClientEvent(-1, "E_SetRainVolume", tostring(M.options.rainVolume)) 258 | end 259 | end 260 | 261 | M.commands["rainVolume"] = function(pid, volume) 262 | volume = tonumber(volume) or nil 263 | if not volume then 264 | MP.hSendChatMessage(pid, "Invalid volume") 265 | return 266 | end 267 | 268 | M.options.rainVolume = volume 269 | MP.hSendChatMessage(pid, "Set rain volume to " .. volume) 270 | MP.TriggerClientEvent(-1, "E_SetRainVolume", tostring(volume)) 271 | end 272 | 273 | M.commands["floodWithRain"] = function(pid, enabled) 274 | if not enabled then 275 | MP.hSendChatMessage(pid, "Invalid value") 276 | return 277 | end 278 | 279 | if string.lower(enabled) == "true" or enabled == "1" then 280 | enabled = true 281 | elseif string.lower(enabled) == "false" or enabled == "0" then 282 | enabled = false 283 | else 284 | MP.hSendChatMessage(pid, "Please use true/false or 1/0") 285 | return 286 | end 287 | 288 | M.options.floodWithRain = enabled 289 | MP.hSendChatMessage(pid, tostring(enabled and "Enabled" or "Disabled") .. " flooding with rain") 290 | end 291 | 292 | M.commands["decrease"] = function(pid, enabled) 293 | if not enabled then 294 | MP.hSendChatMessage(pid, "Invalid value") 295 | return 296 | end 297 | 298 | if string.lower(enabled) == "true" or enabled == "1" then 299 | enabled = true 300 | elseif string.lower(enabled) == "false" or enabled == "0" then 301 | enabled = false 302 | else 303 | MP.hSendChatMessage(pid, "Please use true/false or 1/0") 304 | return 305 | end 306 | 307 | if M.options.floodWithRain and M.options.rainAmount > 0.0 and enabled then 308 | MP.hSendChatMessage(pid, "What? You can't flood with rain and decrease at the same time!. Well, you can but I won't let you") 309 | return 310 | end 311 | 312 | M.options.decrease = enabled 313 | MP.hSendChatMessage(pid, "Set flood decrease to " .. tostring(enabled)) 314 | end 315 | 316 | M.commands["printSettings"] = function(pid) 317 | for k, v in pairs(M.options) do 318 | MP.hSendChatMessage(pid, k .. ": " .. tostring(v)) 319 | end 320 | end 321 | 322 | MP.RegisterEvent("onInit", "onInit") 323 | MP.RegisterEvent("onPlayerJoin", "onPlayerJoin") 324 | MP.RegisterEvent("E_OnInitiliaze", "E_OnInitialize") 325 | MP.RegisterEvent("ET_Update", "T_Update") 326 | MP.CreateEventTimer("ET_Update", 25) 327 | 328 | return M -------------------------------------------------------------------------------- /Resources/Server/Flood/main.lua: -------------------------------------------------------------------------------- 1 | require("multiplayer") 2 | 3 | local flood = require("flood") 4 | local commands = flood.commands 5 | local prefix = "/flood_" 6 | 7 | function chatMessageHandler(pid, name, message) 8 | if not message then -- console input 9 | message = pid 10 | pid = -2 11 | end 12 | 13 | if message:sub(1, #prefix) == "/flood_" then 14 | local command = message:sub(#prefix + 1) 15 | local args = {} 16 | for arg in command:gmatch("%S+") do 17 | table.insert(args, arg) 18 | end 19 | command = args[1] 20 | table.remove(args, 1) 21 | if commands[command] then 22 | commands[command](pid, table.unpack(args)) 23 | return 1 24 | else 25 | MP.hSendChatMessage(pid, "Unknown command: " .. command) 26 | return 1 27 | end 28 | end 29 | 30 | return 0 31 | end 32 | 33 | MP.RegisterEvent("onInit", "onInit") 34 | MP.RegisterEvent("onChatMessage", "chatMessageHandler") 35 | MP.RegisterEvent("onConsoleInput", "chatMessageHandler") -------------------------------------------------------------------------------- /Resources/Server/Flood/multiplayer.lua: -------------------------------------------------------------------------------- 1 | local oSendChatMessage = MP.SendChatMessage 2 | 3 | MP.hSendChatMessage = function(id, message) 4 | if id == -2 then -- console 5 | print(message) 6 | else -- player 7 | oSendChatMessage(id, message) 8 | end 9 | end -------------------------------------------------------------------------------- /automod.ps1: -------------------------------------------------------------------------------- 1 | # Helper utility to autozip and copy a mod to BeamMP's resource folder 2 | # Usage: automod.ps1 3 | # Example: automod.ps1 ".\automod.ps1 .\Resources\Client\floodBeamMP\ C:\Users\myUser\AppData\Roaming\BeamMP-Launcher\Resources" 4 | 5 | if ($args.Count -lt 2) { 6 | Write-Error "Usage: automod.ps1 " 7 | exit 8 | } 9 | 10 | $ModPath = $args[0] 11 | $BeamMPResourcePath = $args[1] 12 | 13 | if ($ModPath.EndsWith("\") -or $ModPath.EndsWith("/")) { 14 | $ModPath = $ModPath.Substring(0, $ModPath.Length - 1) 15 | } 16 | if ($BeamMPResourcePath.EndsWith("\") -or $BeamMPResourcePath.EndsWith("/")) { 17 | $BeamMPResourcePath = $BeamMPResourcePath.Substring(0, $BeamMPResourcePath.Length - 1) 18 | } 19 | 20 | # Check if the mod path exists 21 | if (!(Test-Path $ModPath)) { 22 | Write-Error "Mod path does not exist: $ModPath" 23 | exit 24 | } 25 | 26 | $ModName = Split-Path $ModPath -Leaf 27 | 28 | # Check if the BeamMP resource path exists 29 | if (!(Test-Path $BeamMPResourcePath)) { 30 | Write-Error "BeamMP resource path does not exist: $BeamMPResourcePath" 31 | exit 32 | } 33 | 34 | # Watcher stuff 35 | $Watcher = New-Object System.IO.FileSystemWatcher 36 | $Watcher.Path = $ModPath 37 | $Watcher.NotifyFilter = [System.IO.NotifyFilters]::LastWrite, [System.IO.NotifyFilters]::FileName, [System.IO.NotifyFilters]::DirectoryName 38 | $Watcher.Filter = "*.*" 39 | $Watcher.IncludeSubdirectories = $true 40 | $Watcher.EnableRaisingEvents = $true 41 | 42 | $ChangeTypes = [System.IO.WatcherChangeTypes]::Created, [System.IO.WatcherChangeTypes]::Changed, [System.IO.WatcherChangeTypes]::Deleted 43 | $WatcherTimeout = 1000 44 | $CopyTimeout = 2000 45 | $LastChange = [DateTime]::MinValue 46 | 47 | function Invoke-SomeAction { 48 | param ( 49 | [Parameter(Mandatory)] 50 | [System.IO.WaitForChangedResult] 51 | $ChangeInformation 52 | ) 53 | 54 | Write-Host "File updated: $($ChangeInformation.Name)" -ForegroundColor Green 55 | 56 | # Zip the folder 57 | $zip = "$ModPath.zip" 58 | if (Test-Path $zip) { 59 | Remove-Item $zip 60 | } 61 | Add-Type -AssemblyName System.IO.Compression.FileSystem 62 | [System.IO.Compression.ZipFile]::CreateFromDirectory($ModPath, $zip) 63 | 64 | # Delete the old folder in the BeamMP resource path 65 | $old = "$BeamMPResourcePath/$ModName.zip" 66 | if (Test-Path $old) { 67 | Remove-Item $old 68 | } 69 | 70 | # Copy the new folder to the BeamMP resource path 71 | Copy-Item $zip $old 72 | 73 | Write-Host "Sucessfully updated mod" -ForegroundColor Magenta 74 | } 75 | 76 | try { 77 | Write-Host "Watching $ModPath for changes..." -ForegroundColor DarkYellow 78 | while ($true) { 79 | $change = $Watcher.WaitForChanged($ChangeTypes, $WatcherTimeout) 80 | if ($change.TimedOut) { 81 | continue 82 | } 83 | 84 | # Wait a few seconds until we can copy the files again 85 | if ([DateTime]::Now - $LastChange -lt [TimeSpan]::FromMilliseconds($CopyTimeout)) { 86 | Write-Host "Waiting for changes to finish..." -ForegroundColor DarkYellow 87 | Start-Sleep -Milliseconds $CopyTimeout 88 | Invoke-SomeAction $change 89 | continue 90 | } 91 | 92 | $LastChange = [DateTime]::Now 93 | Invoke-SomeAction $change 94 | } 95 | } catch { 96 | Write-Error $_ 97 | } --------------------------------------------------------------------------------