File size: 14,327 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 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 |
-- return false to make an object disappear even from the All category, e.g. when placing the object causes a crash
function available_in_editor(entity, class_name)
local class = g_Classes[class_name]
return class and not rawget(class, "editor_force_excluded") and
(class.variable_entity or IsValidEntity(entity)) and
not IsTerrainEntityId(entity)
end
local new_artset = "<color 32 205 32>New"
local updated_artset = "<color 180 180 0>Updated"
local excluded_artset = "<color 205 32 32>Excluded"
local mods_artset = "<color 205 185 32>Mods"
local all_artset = "<color 185 32 205>All"
local bookmarks_artset = "<image CommonAssets/UI/Editor/fav_star 450 220 165 18>"
local extra_artsets = Platform.developer and
{ new_artset, updated_artset, excluded_artset, all_artset, bookmarks_artset } or
{ all_artset, bookmarks_artset }
local all_artsets
if not Platform.console and rawget(_G, "ArtSpecConfig") then
CreateRealTimeThread(function()
all_artsets = table.iappend(table.iappend({ "Any" }, ArtSpecConfig.ArtSets), extra_artsets)
end)
end
local function store_as_by_category(self, prop_meta) return prop_meta.id .. "_for_" .. self:GetCategory() end
DefineClass.XEditorObjectPalette = {
__parents = { "XEditorTool" },
properties = {
persisted_setting = true, auto_select_all = true, small_font = true,
{ id = "ArtSets", name = "Art sets", editor = "text_picker", horizontal = true, name_on_top = true, default = { "Any" }, multiple = true,
items = function(self)
local ret = table.copy(all_artsets)
if not self.update_times_cache_populated then
table.remove_value(ret, updated_artset)
end
if ModsLoaded and #ModsLoaded > 0 then
table.insert(ret, table.find(ret, all_artset), mods_artset)
end
return ret
end,
},
{ id = "Category", name = "Categories", editor = "text_picker", horizontal = true, name_on_top = true, default = "Any",
items = function() return table.iappend({ "Any" }, ArtSpecConfig.Categories) end,
no_edit = function(obj) return table.find(obj:GetArtSets(), excluded_artset) or table.find(obj:GetArtSets(), all_artset) end,
},
{ id = "SubCategory", editor = "text_picker", horizontal = true, hide_name = true, name_on_top = true, default = "Any",
items = function(obj) return table.iappend({ "Any" }, ArtSpecConfig[obj:GetCategory().."Categories"] or empty_table) end,
no_edit = function(obj) return table.find(obj:GetArtSets(), excluded_artset) or table.find(obj:GetArtSets(), all_artset) or not ArtSpecConfig[obj:GetCategory().."Categories"] end,
store_as = store_as_by_category, -- remember value separately per Category
},
{ id = "Filter", editor = "text", default = "", name_on_top = true, allowed_chars = EntityValidCharacters, translate = false, },
{ id = "ObjectClass", editor = "text_picker", default = empty_table, hide_name = true, multiple = true,
filter_by_prop = "Filter", items = function(self) return self:GetObjectClassList() end,
store_as = store_as_by_category, -- remember value separately per Category
virtual_items = true, bookmark_fn = "SetBookmark",
},
{ id = "_", editor = "buttons", buttons = { { name = "Clear bookmarks", func = "ClearBookmarks" } },
no_edit = function(obj) return not table.find(obj:GetArtSets(), bookmarks_artset) end,
},
},
ToolSection = "Objects",
FocusPropertyInSettings = "Filter",
update_times_cache_populated = false,
}
function XEditorObjectPalette:SetBookmark(id, value)
local bookmarks = LocalStorage.XEditorObjectBookmarks or {}
bookmarks[id] = value or nil
LocalStorage.XEditorObjectBookmarks = bookmarks
SaveLocalStorage()
end
function XEditorObjectPalette:ClearBookmarks()
LocalStorage.XEditorObjectBookmarks = {}
SaveLocalStorage()
self:SetArtSets{"Any"}
ObjModified(self)
end
function XEditorObjectPalette:Init()
-- select the classes of the objects from the current selection in the object palette
if #editor.GetSel() > 0 and not self.ToolKeepSelection then
local classes = {}
for _, obj in ipairs(editor.GetSel()) do
classes[obj.class] = true
end
editor.ClearSel()
local prop_meta = self:GetPropertyMetadata("ObjectClass")
local items = prop_eval(prop_meta.items, self, prop_meta)
local existing_classes = {}
local filtered_out_classes = {}
local filter_string = string.lower(self:GetFilter())
for _, item in ipairs(items) do
if string.find(string.lower(item.id), filter_string, 1, true) then
existing_classes[item.id] = true
else
filtered_out_classes[item.id] = true
end
end
local reset_sets, reset_filter
for class in pairs(classes) do
if filtered_out_classes[class] then
reset_filter = true
elseif not existing_classes[class] then
reset_sets = true
end
end
if reset_sets then
self:SetArtSets{"Any"}
self:SetCategory("Any")
self:SetSubCategory("Any")
self:SetFilter("")
elseif reset_filter then
self:SetFilter("")
end
self:SetObjectClass(table.keys(classes))
end
end
function XEditorObjectPalette:ValidatedArtSets()
local sets = self:GetArtSets()
if not Platform.developer then
table.remove_value(sets, new_artset)
table.remove_value(sets, updated_artset)
table.remove_value(sets, excluded_artset)
elseif not self.update_times_cache_populated then
table.remove_value(sets, updated_artset)
end
if table.find(sets, "Any") or #sets == 0 then return { "Any" }
elseif table.find(sets, new_artset) then return { new_artset }
elseif table.find(sets, updated_artset) then return { updated_artset }
elseif table.find(sets, excluded_artset) then return { excluded_artset }
else return sets end
end
function XEditorObjectPalette:OnEditorSetProperty(prop_id, old_value, ged)
local update
-- the Any, New, Updated, Excluded and All art sets can only be selected alone
if prop_id == "ArtSets" then
self:SetArtSets(self:ValidatedArtSets())
local prop = self:GetPropertyMetadata("Category")
if prop.no_edit(self) then
self:SetCategory("Any")
end
update = true
end
if prop_id == "ArtSets" or prop_id == "Category" then
local prop = self:GetPropertyMetadata("SubCategory")
if prop.no_edit(self) then
self:SetSubCategory("Any")
update = true
end
end
if update then
GedForceUpdateObject(self)
end
end
local function eval(val, ...)
if type(val) == "function" then
return val(...)
end
return val
end
if FirstLoad then
g_EditorObjectPaletteThread = false
end
function XEditorObjectPalette:PopulateModificationTimeCache()
if not self.update_times_cache_populated and not g_EditorObjectPaletteThread then
g_EditorObjectPaletteThread = CreateRealTimeThread(function()
local time, time1 = GetPreciseTicks(), GetPreciseTicks()
XEditorEnumPlaceableObjects(function(id, name, artset, category, subcategory, custom_tag, creation_time, modification_time, ...)
eval(modification_time, ...)
if GetPreciseTicks() - time >= 10 then
Sleep(20)
time = GetPreciseTicks()
end
end)
self.update_times_cache_populated = true
ObjModified(self)
end)
end
end
function XEditorObjectPalette:GetObjectClassList()
local sets, sets_by_key = self:ValidatedArtSets(), {}
for _, set in ipairs(sets) do
sets_by_key[set] = true
end
local single_set = #sets <= 1 and (sets[1] or "Any")
self:PopulateModificationTimeCache()
local ret, processed_ids = {}, {}
local now, week = Platform.developer and os.time(os.date("!*t")), 7*24*60*60
local cat = self:GetCategory()
local subcat = self:GetSubCategory()
local settings_hash = xxhash(0, table.hash(sets_by_key), self.update_times_cache_populated, cat, subcat)
if settings_hash == self.cached_settings_hash then
return self.cached_objects_list
end
local bookmarks = LocalStorage.XEditorObjectBookmarks or {}
XEditorEnumPlaceableObjects(function(id, name, artset, category, subcategory, custom_tag, creation_time, modification_time, data)
-- filter by artset / category / subcategory
if not processed_ids[id] and (cat == "Any" or category == cat) and (subcat == "Any" or subcategory == subcat) then
creation_time = creation_time and eval(creation_time, data)
modification_time = modification_time and self.update_times_cache_populated and eval(modification_time, data)
local is_new = creation_time and now - creation_time < week
local is_updated = modification_time and now - modification_time < week
if single_set == all_artset or
(single_set == excluded_artset and not artset) or
(single_set == bookmarks_artset and bookmarks[id]) or
(single_set == mods_artset and artset == "Mods") or
artset and ((single_set == new_artset and not custom_tag and is_new) or
(single_set == updated_artset and not custom_tag and not is_new and is_updated) or
(single_set == "Any" or sets_by_key[artset]))
then
local suffix
if custom_tag then
suffix = custom_tag
elseif is_new then
suffix = new_artset .. (single_set == new_artset and " " .. os.date("%d.%m", creation_time) or "")
elseif is_updated then
suffix = updated_artset .. (single_set == updated_artset and " " .. os.date("%d.%m", modification_time) or "")
end
ret[#ret + 1] = { id = id, text = suffix and (name .. "<right>" .. suffix) or name, bookmarked = bookmarks[id],
documentation = data and data.documentation,
}
end
end
processed_ids[id] = true
end)
table.sortby_field(ret, "text")
self.cached_objects_list = ret
self.cached_settings_hash = settings_hash
return ret
end
----- Objects palette generator - XEditorEnumPlaceableObjects
--
-- It must call the provided callback for each placeable object, passing the following parameters to the callback, in order:
-- "id" - the id with which XEditorPlaceObject will be called to create the object
-- "name" - the name with which to display the object
-- "editor_artset" - if == nil, the object will appear in the Excluded artset
-- "editor_category", "editor_subcategory" - classification categories for the objects palette
-- "custom_display_tag" (optional) - tag to be displayed to the right of the object's name
-- "creation_time", "modification_time" (optional) - functions to get the time the object was created and last modified
--
-- Call XEditorUpdateObjectPalette to force the editor to refresh the palette if it is currently open.
function XEditorEnumPlaceableObjects(callback)
ClassDescendantsList("CObject", function(name, class)
if name ~= "Light" and class:IsKindOf("Light") then
callback(name, "Light_" .. name, "Common", "Effects")
return
end
-- entity specs are only available in developer mode; skip WIP/Placeholder/New/Updated tags in this case
local entity = class:GetEntity()
local entity_spec = Platform.developer and EntitySpecPresets[entity]
local missing_spec = Platform.developer and not EntitySpecPresets[entity]
local placeholder = entity_spec and entity_spec.placeholder
local wip_entity = entity_spec and entity_spec.status ~= "Ready"
if available_in_editor(entity, name) then
local data = EntityData[entity] or empty_table
callback(name, name, data.editor_artset, data.editor_category, data.editor_subcategory,
missing_spec and "<color 145 254 32>No ArtSpec" or
placeholder and "<color 180 180 0>Proxy" or
wip_entity and "<color 205 32 32>WIP",
entity_spec and function(entity_spec) return entity_spec:GetCreationTime() end,
entity_spec and function(entity_spec) return entity_spec:GetModificationTime() end,
entity_spec)
end
end)
ForEachPreset("ParticleSystemPreset", function(parsys)
callback(parsys.id, "ParSys_" .. parsys.id, "Common", "Effects")
end)
ForEachPreset("FXSourcePreset", function(fxsource)
assert(not g_Classes[fxsource.id])
callback(fxsource.id, fxsource.id, "Common", "Effects")
end)
callback("WaterFill", "WaterLevel", "Common", "Markers")
callback("SoundSource", "SoundSource", "Common", "Markers")
if const.SlabSizeX then
callback("EditorLineGuide", "LineGuide", "Common", "Markers")
end
end
XEditorPlaceableObjectsComboCache = false
function XEditorPlaceableObjectsCombo()
return function()
if XEditorPlaceableObjectsComboCache then return XEditorPlaceableObjectsComboCache end
local ret = { "" }
XEditorEnumPlaceableObjects(function(id) ret[#ret + 1] = id end)
table.sort(ret)
XEditorPlaceableObjectsComboCache = ret
return ret
end
end
function XEditorPlaceObject(id, is_cursor_object)
if ParticleSystemPresets[id] then
return PlaceParticles(id)
end
if FXSourcePresets[id] then
local obj = FXSource:new()
obj:SetFxPreset(id)
obj:OnEditorSetProperty("FXPreset")
return obj
end
if g_Classes[id] then
local entity = g_Classes[id]:GetEntity()
if available_in_editor(entity, id) then -- the place tool might have remembered a class that is no longer available
return XEditorPlaceObjectByClass(id, nil, is_cursor_object)
end
end
end
function XEditorPlaceId(obj)
if IsKindOf(obj, "ParSystem") then
return obj:GetParticlesName()
else
return obj.class
end
end
function XEditorPlaceObjectByClass(class, obj_table, is_cursor_object)
obj_table = obj_table or {}
if is_cursor_object then
EditorCursorObjs[obj_table] = true
end
local colorizations = ColorizationMaterialsCount(g_Classes[class]:GetEntity()) or 0
local ok, res = pcall(PlaceObject, class, obj_table, colorizations > 0 and const.cofComponentColorizationMaterial)
if not ok then
print("Object", class, "failed to initialize and might not function properly in gameplay.")
end
return IsValid(res) and res or nil
end
-- boots up the place tool and selects object with the specified id (in most cases = object class) for placing
function XEditorStartPlaceObject(id)
local editor = OpenDialog("XEditor")
editor:SetMode("XPlaceObjectTool")
editor.mode_dialog:SetObjectClass{ id }
return editor.mode_dialog:CreateCursorObject(id)
end
function XEditorUpdateObjectPalette()
local tool_class = GetDialogMode("XEditor")
if tool_class and g_Classes[tool_class]:IsKindOf("XEditorObjectPalette") then
ObjModified(GetDialog("XEditor").mode_dialog)
end
end
function OnMsg.ClassesBuilt() CreateRealTimeThread(XEditorUpdateObjectPalette) end
|