From cacf4eb7017051725dcec16c4403042f060d7998 Mon Sep 17 00:00:00 2001 From: HRiggs Date: Sun, 15 Feb 2026 23:41:26 -0500 Subject: [PATCH] Init Commit --- 42/icon.png | Bin 0 -> 10039 bytes 42/media/lua/client/RVReassingRoomAddon.lua | 240 ++++++++++++++++++ .../remove_street_tiles_progressive.lua | 137 ++++++++++ 42/media/lua/server/RVReassignRoomServer.lua | 125 +++++++++ 42/mod.info | 7 + 42/poster.png | Bin 0 -> 28960 bytes common/placeholder.txt | 0 mod.info | 7 + 8 files changed, 516 insertions(+) create mode 100644 42/icon.png create mode 100644 42/media/lua/client/RVReassingRoomAddon.lua create mode 100644 42/media/lua/client/remove_street_tiles_progressive.lua create mode 100644 42/media/lua/server/RVReassignRoomServer.lua create mode 100644 42/mod.info create mode 100644 42/poster.png create mode 100644 common/placeholder.txt create mode 100644 mod.info diff --git a/42/icon.png b/42/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..1db8cb6bd89a2fe17798e13474a99baed056e0d4 GIT binary patch literal 10039 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 literal 0 HcmV?d00001 diff --git a/42/media/lua/client/RVReassingRoomAddon.lua b/42/media/lua/client/RVReassingRoomAddon.lua new file mode 100644 index 0000000..c182a55 --- /dev/null +++ b/42/media/lua/client/RVReassingRoomAddon.lua @@ -0,0 +1,240 @@ +-- RVReassignAddon.lua +if isServer() then return end + +local RVReassignAddon = {} +local SERVER_MODULE = "PROJECTRVTools" +local SERVER_COMMAND_REASSIGN = "ReassignVehicleToCurrentRoom" +local CLIENT_COMMAND_RESULT = "ReassignVehicleResult" + +-- Get the room type based on the player's coordinates +local function getRoomTypeAtPosition(x, y, z) + local RV = require("RVVehicleTypes") + local VehicleTypes = RV.VehicleTypes + + for roomType, typeDef in pairs(VehicleTypes) do + for _, room in ipairs(typeDef.rooms) do + local roomEndX = room.x + (typeDef.roomWidth or 2) + local roomEndY = room.y + (typeDef.roomHeight or 3) + + if x >= room.x and x < roomEndX and + y >= room.y and y < roomEndY and + z == (room.z or 0) then + return roomType, room, typeDef + end + end + end + return nil, nil, nil +end + +-- Reassign the vehicle to the current room +local function reassignVehicleToCurrentRoom(player) + local modData = ModData.getOrCreate("modPROJECTRVInterior") + local pmd = player:getModData() + + -- Verify the player has a recently tracked vehicle + if not pmd.projectRV_playerId then + player:Say("No recent vehicle is registered.") + return + end + + local playerData = modData.Players and modData.Players[pmd.projectRV_playerId] + if not playerData or not playerData.VehicleId then + player:Say("No recent vehicle was found.") + return + end + + local vehicleId = playerData.VehicleId + local originalVehicleType = playerData.RoomType or "normal" + + if not vehicleId then + player:Say("Vehicle data is incomplete.") + return + end + + -- Get the current room where the player is standing + local x, y, z = player:getX(), player:getY(), player:getZ() + local newRoomType, newRoom, newTypeDef = getRoomTypeAtPosition(x, y, z) + + if not newRoom then + player:Say("You are not in a valid room.") + return + end + + -- Remove the previous assignment from the original room type table + local originalAssignedKey = (originalVehicleType == "normal") and "AssignedRooms" or ("AssignedRooms" .. originalVehicleType) + if modData[originalAssignedKey] then + modData[originalAssignedKey][vehicleId] = nil + end + + -- Assign the new room in the new type table + local newAssignedKey = (newRoomType == "normal") and "AssignedRooms" or ("AssignedRooms" .. newRoomType) + modData[newAssignedKey] = modData[newAssignedKey] or {} + modData[newAssignedKey][vehicleId] = newRoom + + -- Update player-linked vehicle type data + playerData.RoomType = newRoomType + playerData.ActualRoom = newRoom + + -- Update vehicle modData type if the vehicle is currently loaded + local vehicles = getCell():getVehicles() + for i = 0, vehicles:size() - 1 do + local vehicle = vehicles:get(i) + local vmd = vehicle:getModData() + if vmd.projectRV_uniqueId and tostring(vmd.projectRV_uniqueId) == vehicleId then + vmd.projectRV_type = newRoomType + break + end + end + + -- Also persist the override for cases where the vehicle is not loaded + modData.VehicleTypeOverrides = modData.VehicleTypeOverrides or {} + modData.VehicleTypeOverrides[vehicleId] = newRoomType + + player:Say("Vehicle reassigned to room type: " .. newRoomType) + + -- Debug + print(string.format("[RVReassign] Vehicle %s (original: %s) reassigned to room type %s: x=%d, y=%d, z=%d", + vehicleId, originalVehicleType, newRoomType, newRoom.x, newRoom.y, newRoom.z)) +end + +local function requestReassignVehicleToCurrentRoom(player) + if isClient() then + sendClientCommand(SERVER_MODULE, SERVER_COMMAND_REASSIGN, {}) + return + end + + reassignVehicleToCurrentRoom(player) +end + +-- Check whether the player is inside a valid room +local function isPlayerInValidRoom(player) + local x, y, z = player:getX(), player:getY(), player:getZ() + local roomType, room, typeDef = getRoomTypeAtPosition(x, y, z) + return roomType ~= nil, roomType, room +end + +-- Get info for the player's most recent tracked vehicle +local function getLastVehicleInfo(player) + local modData = ModData.getOrCreate("modPROJECTRVInterior") + local pmd = player:getModData() + + if not pmd.projectRV_playerId then + return nil, nil, nil + end + + local playerData = modData.Players and modData.Players[pmd.projectRV_playerId] + if not playerData then + return nil, nil, nil + end + + return playerData.VehicleId, playerData.RoomType, playerData.ActualRoom +end + +local function canUseReassignInMP(player) + if not isClient() then + return false + end + + local pmd = player:getModData() + return pmd and pmd.projectRV_playerId ~= nil +end + +-- Main function that adds the context menu option +local function addReassignOption(player, context) + local inRoom, roomType, room = isPlayerInValidRoom(player) + + if not inRoom then + return + end + + local vehicleId, currentRoomType, currentRoom = getLastVehicleInfo(player) + + if not vehicleId and not canUseReassignInMP(player) then + return + end + + -- Add the option to the context menu + local optionText + if currentRoomType and currentRoomType ~= roomType then + optionText = getText("ContextMenu_ReassignVehicleToRoomDifferent") or + string.format("Reassign vehicle (%s) to this room (%s)", currentRoomType, roomType) + else + optionText = getText("ContextMenu_ReassignVehicleToRoom") or "Reassign vehicle to this room" + end + + context:addOption(optionText, player, requestReassignVehicleToCurrentRoom) +end + +-- Hook for world-object context menu +local function onFillWorldObjectContextMenu(player, context, worldObjects) + local playerObj = getSpecificPlayer(player) + if playerObj then + addReassignOption(playerObj, context) + end +end + +-- Hook for inventory context menu +local function onFillInventoryObjectContextMenu(player, context, items) + local playerObj = getSpecificPlayer(player) + if playerObj then + addReassignOption(playerObj, context) + end +end + +-- Apply saved type overrides when a vehicle is created/loaded +local function onVehicleCreate(vehicle) + local modData = ModData.getOrCreate("modPROJECTRVInterior") + local vmd = vehicle:getModData() + + if vmd.projectRV_uniqueId and modData.VehicleTypeOverrides then + local vehicleId = tostring(vmd.projectRV_uniqueId) + if modData.VehicleTypeOverrides[vehicleId] then + vmd.projectRV_type = modData.VehicleTypeOverrides[vehicleId] + end + end +end + +local function onServerCommand(module, command, args) + if module ~= SERVER_MODULE or command ~= CLIENT_COMMAND_RESULT then + return + end + + local player = getPlayer() + if not player then + return + end + + if args and args.message then + player:Say(tostring(args.message)) + end +end + +-- Initialize the addon +local function initReassignAddon() + Events.OnFillWorldObjectContextMenu.Add(onFillWorldObjectContextMenu) + Events.OnFillInventoryObjectContextMenu.Add(onFillInventoryObjectContextMenu) + Events.OnVehicleCreate.Add(onVehicleCreate) + Events.OnServerCommand.Add(onServerCommand) + + print("[RVReassignAddon] Vehicle reassignment addon loaded successfully") +end + +-- Load fallback translations if needed +local function loadTranslations() + if getText and getText("ContextMenu_ReassignVehicleToRoom") == "ContextMenu_ReassignVehicleToRoom" then + -- If translation key is missing, define English fallback values + -- Add additional localized values here if needed + local translations = { + ContextMenu_ReassignVehicleToRoom = "Reassign vehicle to this room", + ContextMenu_ReassignVehicleToRoomDifferent = "Reassign vehicle to this room (type change)" + } + end +end + +-- Initialize when the game starts +Events.OnGameStart.Add(function() + loadTranslations() + initReassignAddon() +end) + +return RVReassignAddon diff --git a/42/media/lua/client/remove_street_tiles_progressive.lua b/42/media/lua/client/remove_street_tiles_progressive.lua new file mode 100644 index 0000000..e6ac472 --- /dev/null +++ b/42/media/lua/client/remove_street_tiles_progressive.lua @@ -0,0 +1,137 @@ +-- remove_street_tiles_progressive.lua +-- Place in media/lua/client/ +-- Removes tiles in radius-6 "blocks" per tick until total radius ~60 is covered. + +local BLOCK_RADIUS = 6 -- radius processed each tick +local TOTAL_RADIUS = 60 -- final total radius +local BLOCK_STEP = (BLOCK_RADIUS * 2) + 1 -- 13 when BLOCK_RADIUS=6 + +local function makeBlocksList(totalRadius, step) + local blocks = {} + local start = -totalRadius + local finish = totalRadius + for oy = start, finish, step do + for ox = start, finish, step do + table.insert(blocks, {ox = ox, oy = oy}) + end + end + return blocks +end + +local function tryRemoveObjFromSquare(sq, obj) + if not sq or not obj then return false end + local deleted = false + if sq.RemoveTileObject then + pcall(function() sq:RemoveTileObject(obj) end) + deleted = true + end + if not deleted and sq.transmitRemoveItemFromSquare then + pcall(function() sq:transmitRemoveItemFromSquare(obj) end) + deleted = true + end + if not deleted and obj.removeFromWorld then + pcall(function() obj:removeFromWorld() end) + deleted = true + end + return deleted +end + +local function processBlockAt(centerX, centerY, centerZ, blockOffsetX, blockOffsetY, player, stats) + -- blockOffset are offsets relative to center (e.g. -60, -47, -34, ...) + local baseX = centerX + blockOffsetX + local baseY = centerY + blockOffsetY + for dx = -BLOCK_RADIUS, BLOCK_RADIUS do + for dy = -BLOCK_RADIUS, BLOCK_RADIUS do + local x = baseX + dx + local y = baseY + dy + local sq = pcall(function() return getCell():getGridSquare(x, y, centerZ) end) and getCell():getGridSquare(x, y, centerZ) or nil + if sq then + local objs = nil + local ok = pcall(function() objs = sq:getObjects() end) + if ok and objs and objs.size and objs:size() > 0 then + local n = objs:size() + for i = n - 1, 0, -1 do + local obj = nil + local ok2, o = pcall(function() return objs:get(i) end) + obj = ok2 and o or nil + if obj then + local ok3, spriteName = pcall(function() + local spr = obj.getSprite and obj:getSprite() + return (spr and spr.getName) and spr:getName() or nil + end) + spriteName = ok3 and spriteName or nil + if spriteName and string.find(string.lower(tostring(spriteName)), "street", 1, false) then + if tryRemoveObjFromSquare(sq, obj) then + stats.removed = stats.removed + 1 + if stats.examples < stats.maxExamples then + print(string.format("REMOVE_PROGRESS: removed @ %d,%d -> '%s'", x, y, tostring(spriteName))) + stats.examples = stats.examples + 1 + end + end + end + end + end + end + end + end + end +end + +local function startProgressiveRemoval(player) + if not player then return end + local px = math.floor(player:getX()) + local py = math.floor(player:getY()) + local pz = math.floor(player:getZ() or 0) + + local blocks = makeBlocksList(TOTAL_RADIUS, BLOCK_STEP) + local totalBlocks = #blocks + if totalBlocks == 0 then + if player.Say then player:Say("Nothing to process.") end + return + end + + local stats = { removed = 0, examples = 0, maxExamples = 10 } + local index = 1 + + if player.Say then player:Say("Starting progressive removal (radius "..tostring(TOTAL_RADIUS)..").") end + print("REMOVE_PROGRESS: start at "..px..","..py.." totalBlocks="..tostring(totalBlocks)) + + local function onTick() + -- Process one block per tick + if index > totalBlocks then + Events.OnTick.Remove(onTick) + local msg = "Removal complete: removed "..tostring(stats.removed).." tiles (filter 'street')." + print("REMOVE_PROGRESS: "..msg) + if player.Say then player:Say(msg) end + return + end + + local b = blocks[index] + processBlockAt(px, py, pz, b.ox, b.oy, player, stats) + + -- Periodic message every 10 blocks + if index % 10 == 0 then + local progressMsg = string.format("Progress: block %d/%d - removed so far: %d", index, totalBlocks, stats.removed) + print("REMOVE_PROGRESS: "..progressMsg) + if player.Say then player:Say(progressMsg) end + end + + index = index + 1 + end + + Events.OnTick.Add(onTick) +end + +-- Hook into the world context menu +local function onFillWorldObjectContextMenu(playerOrNum, context, worldObjects, test) + if test then return end + local player + if type(playerOrNum) == "number" then player = getSpecificPlayer(playerOrNum) else player = playerOrNum end + if not player then return end + + context:addOption("Progressively remove 'street' tiles (radius "..tostring(TOTAL_RADIUS)..")", worldObjects, function() + startProgressiveRemoval(player) + end) +end + +Events.OnFillWorldObjectContextMenu.Add(onFillWorldObjectContextMenu) diff --git a/42/media/lua/server/RVReassignRoomServer.lua b/42/media/lua/server/RVReassignRoomServer.lua new file mode 100644 index 0000000..e9e2398 --- /dev/null +++ b/42/media/lua/server/RVReassignRoomServer.lua @@ -0,0 +1,125 @@ +if not isServer() then return end + +local SERVER_MODULE = "PROJECTRVTools" +local SERVER_COMMAND_REASSIGN = "ReassignVehicleToCurrentRoom" +local CLIENT_COMMAND_RESULT = "ReassignVehicleResult" + +local function getRoomTypeAtPosition(x, y, z) + local RV = require("RVVehicleTypes") + local vehicleTypes = RV.VehicleTypes + + for roomType, typeDef in pairs(vehicleTypes) do + for _, room in ipairs(typeDef.rooms) do + local roomEndX = room.x + (typeDef.roomWidth or 2) + local roomEndY = room.y + (typeDef.roomHeight or 3) + + if x >= room.x and x < roomEndX and + y >= room.y and y < roomEndY and + z == (room.z or 0) then + return roomType, room + end + end + end + + return nil, nil +end + +local function findVehicleByPersistentId(vehicleId) + local cell = getCell() + if not cell or not cell.getVehicles then + return nil + end + + local vehicles = cell:getVehicles() + if not vehicles then + return nil + end + + for i = 0, vehicles:size() - 1 do + local vehicle = vehicles:get(i) + if vehicle then + local vmd = vehicle:getModData() + if vmd and vmd.projectRV_uniqueId and tostring(vmd.projectRV_uniqueId) == vehicleId then + return vehicle + end + end + end + + return nil +end + +local function reassignVehicleToCurrentRoom(player) + local modData = ModData.getOrCreate("modPROJECTRVInterior") + local pmd = player and player:getModData() or nil + + if not pmd or not pmd.projectRV_playerId then + return false, "No recent vehicle registered." + end + + local playerData = modData.Players and modData.Players[pmd.projectRV_playerId] + if not playerData or not playerData.VehicleId then + return false, "No recent vehicle found." + end + + local vehicleId = tostring(playerData.VehicleId) + local originalVehicleType = playerData.RoomType or "normal" + local x, y, z = player:getX(), player:getY(), player:getZ() + local newRoomType, newRoom = getRoomTypeAtPosition(x, y, z) + + if not newRoom then + return false, "You are not in a valid RV room." + end + + local originalAssignedKey = (originalVehicleType == "normal") and "AssignedRooms" or ("AssignedRooms" .. originalVehicleType) + if modData[originalAssignedKey] then + modData[originalAssignedKey][vehicleId] = nil + end + + local newAssignedKey = (newRoomType == "normal") and "AssignedRooms" or ("AssignedRooms" .. newRoomType) + modData[newAssignedKey] = modData[newAssignedKey] or {} + modData[newAssignedKey][vehicleId] = newRoom + + playerData.RoomType = newRoomType + playerData.ActualRoom = newRoom + + modData.VehicleTypeOverrides = modData.VehicleTypeOverrides or {} + modData.VehicleTypeOverrides[vehicleId] = newRoomType + + local vehicle = findVehicleByPersistentId(vehicleId) + if vehicle then + local vmd = vehicle:getModData() + vmd.projectRV_type = newRoomType + if vehicle.transmitModData then + vehicle:transmitModData() + end + end + + if ModData and ModData.transmit then + ModData.transmit("modPROJECTRVInterior") + end + + print(string.format( + "[RVReassignServer] Vehicle %s moved from type %s to %s at x=%d y=%d z=%d", + vehicleId, tostring(originalVehicleType), tostring(newRoomType), newRoom.x, newRoom.y, newRoom.z + )) + + return true, "Vehicle reassigned to room type: " .. tostring(newRoomType) +end + +local function onClientCommand(module, command, player, args) + if module ~= SERVER_MODULE then + return + end + + if command == SERVER_COMMAND_REASSIGN then + local ok, message = reassignVehicleToCurrentRoom(player) + if player then + sendServerCommand(player, SERVER_MODULE, CLIENT_COMMAND_RESULT, { + ok = ok, + message = message + }) + end + end +end + +Events.OnClientCommand.Add(onClientCommand) diff --git a/42/mod.info b/42/mod.info new file mode 100644 index 0000000..617a567 --- /dev/null +++ b/42/mod.info @@ -0,0 +1,7 @@ +version=2.0 +name=[B42]Project RV Tools +id=PROJECTRVTools42 +description=RV Tools +icon=icon.png +poster=poster.png +require=\PROJECTRVInterior42 \ No newline at end of file diff --git a/42/poster.png b/42/poster.png new file mode 100644 index 0000000000000000000000000000000000000000..aab791853688c82c173d7bd2b8d9d3956a39f79a GIT binary patch literal 28960 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

mpk{`)U13WCfr=U8N++I!flbN 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