if config.ModdingToolsInUserMode then return end if FirstLoad then LocalStorage.XEditorFindAndReplaceToolMaps = LocalStorage.XEditorFindAndReplaceToolMaps or {} end DefineClass.XEditorFindAndReplaceObjects = { __parents = { "XEditorTool" }, properties = { persisted_setting = true, { id = "FindClass", name = "Find Class", editor = "choice", default = "", items = function() return XEditorPlaceableObjectsCombo end, }, { id = "ReplaceClass", name = "Replace Class", editor = "choice", default = "", items = function() return XEditorPlaceableObjectsCombo end, }, { id = "ScanButton", editor = "buttons", buttons = { { name = "Scan all maps", func = "Scan" } } }, { id = "Filter", name = "Map filter", editor = "text", default = "", name_on_top = true, persisted_setting = false, translate = false, }, { id = "Maps", editor = "text_picker", default = empty_table, multiple = true, filter_by_prop = "Filter", items = function(self) return LocalStorage.XEditorFindAndReplaceToolMaps end, virtual_items = true, }, { id = "ReplaceButton", editor = "buttons", buttons = { { name = "Replace", func = "Replace" } } } }, ToolTitle = "Find and replace object", Description = { "Scans all maps for objects of a class and lets you replace them with a new class.", }, ActionSortKey = "4", ActionIcon = "CommonAssets/UI/Editor/Tools/PlaceMultipleObject.tga", ActionShortcut = "Ctrl-F", ToolSection = "Misc", } function CountSubStr(base, pattern) if not base or not pattern then return 0 end return select(2, string.gsub(base, pattern, "")) end function XEditorFindAndReplaceObjects:Done() if self:IsThreadRunning("ScanThread") then LocalStorage.XEditorFindAndReplaceToolMaps = {} SaveLocalStorage() end end function XEditorFindAndReplaceObjects:ScanAndAddMap(map_name, obj_class) local maps = LocalStorage.XEditorFindAndReplaceToolMaps table.insert(maps, { text = string.format("%s%s", map_name, "Scanning..."), value = map_name } ) self:SetProperty("Maps", { map_name }) -- Update the UI ObjModified(self) local err, ini = AsyncFileToString("Maps/" .. map_name .. "/objects.lua") local count = CountSubStr(ini, string.format("PlaceObj%%('%s'", obj_class)) count = count + CountSubStr(ini, string.format("p%%(\"%s\"", obj_class)) if count and count > 0 then local last = maps[#maps] last.text = string.format("%s%s", map_name, count) last.count = count else table.remove(maps, #maps) end end function XEditorFindAndReplaceObjects:Scan(self, prop_id, socket) local obj_class = self:GetProperty("FindClass") if not obj_class or obj_class == "" then socket:ShowMessage("Error", "Please select a class to search for.") return end local maps = ListMaps() LocalStorage.XEditorFindAndReplaceToolMaps = {} self:DeleteThread("ScanThread") self:CreateThread("ScanThread", function() for _, map_name in ipairs(maps) do self:ScanAndAddMap(map_name, obj_class) end local maps_length = #LocalStorage.XEditorFindAndReplaceToolMaps if maps_length > 0 then self:SetProperty("Maps", { LocalStorage.XEditorFindAndReplaceToolMaps[maps_length].value }) else self:SetProperty("Maps", {}) end ObjModified(self) SaveLocalStorage() end) end function XEditorFindAndReplaceObjects:Replace(self, prop_id, socket) local chosen_maps = self:GetProperty("Maps") local maps_length = #chosen_maps local old_class = self:GetProperty("FindClass") local replace_class = self:GetProperty("ReplaceClass") if not chosen_maps or type(chosen_maps) ~= "table" or #chosen_maps == 0 then socket:ShowMessage("Error", "Please select map(s).") return end if not old_class or old_class == "" or not replace_class or replace_class == "" then socket:ShowMessage("Error", "Please select a class to search for and a class to replace with.") return end local others_text = "" if maps_length > 1 then others_text = string.format(" and %s others", maps_length - 1) end local message = string.format("Loop through the selected maps (%s%s) \nand replace all \"%s\" with \"%s\"?\n\nThe maps will be saved automatically.", chosen_maps[1], others_text, old_class, replace_class) if socket:WaitQuestion("Replace All", message, "Yes", "No") ~= "ok" then return false end local changes = #chosen_maps > 0 if changes and IsEditorActive() then EditorDeactivate() end for idx, map_name in ipairs(chosen_maps) do if map_name ~= GetMapName() then ChangeMap(map_name) end ReplaceAll(old_class, replace_class) SaveMap("no backup") local map_idx = table.find(LocalStorage.XEditorFindAndReplaceToolMaps, "value", map_name) if map_idx then local item = LocalStorage.XEditorFindAndReplaceToolMaps[map_idx] local done_text = string.format("Done (%d)", item.count) item.text = string.format("%s%s", map_name, done_text) ObjModified(self) SaveLocalStorage() end end if changes and not IsEditorActive() then EditorActivate() end end function XEditorFindAndReplaceObjects:OnEditorSetProperty(prop_id, old_value, ged) if prop_id ~= "FindClass" then return end local value = self:GetProperty("FindClass") if not value or value == "" then LocalStorage.XEditorFindAndReplaceToolMaps = {} self:SetProperty("Maps", {}) elseif self:IsThreadRunning("ScanThread") or #LocalStorage.XEditorFindAndReplaceToolMaps < 2 then self:DeleteThread("ScanThread") LocalStorage.XEditorFindAndReplaceToolMaps = {} self:ScanAndAddMap(GetMapName(), value) end ObjModified(self) SaveLocalStorage() end function XEditorFindAndReplaceObjects:OnPickerItemDoubleClicked(prop_id, item_id, socket) if prop_id ~= "Maps" then return end -- Thread needed to use socket:WaitQuestion() self:CreateThread("ReplaceThread", function() self:Replace(self, nil, socket) end) end