File size: 5,893 Bytes
b6a38d7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
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("<left>%s<right>%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("<left>%s<right><color 0 190 255>%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("<left>%s<right><color 0 255 30>%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