diff --git a/42/icon.png b/42/icon.png new file mode 100644 index 0000000..1db8cb6 Binary files /dev/null and b/42/icon.png differ 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 0000000..aab7918 Binary files /dev/null and b/42/poster.png differ diff --git a/common/placeholder.txt b/common/placeholder.txt new file mode 100644 index 0000000..e69de29 diff --git a/mod.info b/mod.info new file mode 100644 index 0000000..617a567 --- /dev/null +++ b/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