|
local min_scale = 80 |
|
local max_scale = 120 |
|
local btn_percents = { 5, 10, 25, 33, 50,75, 90, 100 } |
|
|
|
DefineClass.SelectionEditorDlg = { |
|
__parents = { "XDialog" }, |
|
last_selection = false, |
|
last_viewed_idx = 0, |
|
} |
|
|
|
function SelectionEditorDlg:Init() |
|
self.editor_selection = editor.GetSel() |
|
self.sort_by = "number" |
|
self.count_all = false |
|
self.select_only_permanents = true |
|
end |
|
|
|
function SelectionEditorDlg:Done() |
|
self:SaveSectionsState() |
|
end |
|
|
|
function SelectionEditorDlg:Open(...) |
|
self:InitControls() |
|
self:InitList() |
|
self:InitColorize() |
|
|
|
self.idScaleMin:SetNumber(min_scale) |
|
self.idScaleMax:SetNumber(max_scale) |
|
|
|
for _, v in ipairs(btn_percents) do |
|
local str_perc = self["idPercent" .. v] |
|
if str_perc then |
|
str_perc.OnPress = function() |
|
self:SelectPerc(v) |
|
end |
|
end |
|
end |
|
|
|
|
|
self:LoadSectionsState() |
|
|
|
self.idFilterText:SetFocus() |
|
|
|
self:CreateThread("update_thread",function () |
|
while true do |
|
self:CheckEditorSelectionCache() |
|
Sleep(1000) |
|
end |
|
end) |
|
|
|
self:Update(true) |
|
XDialog.Open(self,...) |
|
end |
|
|
|
function SelectionEditorDlg:CheckEditorSelectionCache() |
|
local sel = editor.GetSel() |
|
if #sel > 0 and (#self.editor_selection ~= #sel or #table.subtraction(self.editor_selection, sel) > 0) then |
|
self.editor_selection = sel |
|
self:Update(true) |
|
end |
|
end |
|
|
|
function SelectionEditorDlg:SaveSectionsState() |
|
LocalStorage.editor = LocalStorage.editor or {} |
|
LocalStorage.editor.SelectionEditor = { |
|
selection = self:ResolveId("idButtons"):GetVisible(), |
|
view = self:ResolveId("idButtons1"):GetVisible(), |
|
randomize = self:ResolveId("idButtons2"):GetVisible(), |
|
rmin = self.idRMinSlider:GetScroll(), |
|
rmax = self.idRMaxSlider:GetScroll(), |
|
gmin = self.idGMinSlider:GetScroll(), |
|
gmax = self.idGMaxSlider:GetScroll(), |
|
bmin = self.idBMinSlider:GetScroll(), |
|
bmax = self.idBMaxSlider:GetScroll(), |
|
scale_min = self.idScaleMin.idEdit:GetText(), |
|
scale_max = self.idScaleMax.idEdit:GetText(), |
|
} |
|
SaveLocalStorage() |
|
end |
|
|
|
function SelectionEditorDlg:LoadSectionsState() |
|
local states = LocalStorage.editor and LocalStorage.editor.SelectionEditor or empty_table |
|
|
|
local buttons = self:ResolveId("idButtons") |
|
local state = states.selection or false |
|
buttons:SetVisible(state) |
|
buttons:SetDock(state and "bottom" or "ignore") |
|
|
|
buttons = self:ResolveId("idButtons1") |
|
state = states.view or false |
|
buttons:SetVisible(state) |
|
buttons:SetDock(state and "bottom" or "ignore") |
|
|
|
buttons = self:ResolveId("idButtons2") |
|
state = states.randomize or false |
|
buttons:SetVisible(state) |
|
buttons:SetDock(state and "bottom" or "ignore") |
|
|
|
if states.rmin then |
|
self.idRMinSlider:SetScroll(states.rmin) self.idRMin:SetText(tostring(states.rmin)) |
|
self.idRMaxSlider:SetScroll(states.rmax) self.idRMax:SetText(tostring(states.rmax)) |
|
self.idGMinSlider:SetScroll(states.gmin) self.idGMin:SetText(tostring(states.gmin)) |
|
self.idGMaxSlider:SetScroll(states.gmax) self.idGMax:SetText(tostring(states.gmax)) |
|
self.idBMinSlider:SetScroll(states.bmin) self.idBMin:SetText(tostring(states.bmin)) |
|
self.idBMaxSlider:SetScroll(states.bmax) self.idBMax:SetText(tostring(states.bmax)) |
|
end |
|
|
|
self.idScaleMin.idEdit:SetText(states.scale_min or "80") |
|
self.idScaleMax.idEdit:SetText(states.scale_max or "120") |
|
end |
|
|
|
function SelectionEditorDlg:InitControls() |
|
self.idStatList.OnShortcut = function(self, shortcut, ...) |
|
if shortcut == "Ctrl-D" then |
|
return |
|
end |
|
return XList.OnShortcut(self, shortcut, ...) |
|
end |
|
|
|
self.idFilterText.OnTextChanged = function(this, ...) |
|
DelayedCall(500, self.Update, self) |
|
return XEdit.OnTextChanged(this, ...) |
|
end |
|
|
|
self.idScaleMin.idEdit:SetRange(0, const.GameObjectMaxScale) |
|
self.idScaleMax.idEdit:SetRange(0, const.GameObjectMaxScale) |
|
|
|
self.idScale.OnPress = function() |
|
min_scale = self.idScaleMin:GetNumber() |
|
max_scale = self.idScaleMax:GetNumber() |
|
|
|
local list = self.idStatList |
|
if #list:GetSelection() == 0 then print("Select something from the list first") end |
|
if not min_scale or not max_scale then print("Specify two numbers for minimal and maximal scale") end |
|
|
|
self:ApplyToWorkingList(function(obj) |
|
obj:SetScaleClamped(min_scale + AsyncRand(max_scale - min_scale + 1)) |
|
end ) |
|
end |
|
self.idSelAll.OnPress = function() |
|
editor.ClearSel() |
|
table.iclear(self.editor_selection) |
|
self.count_all = true |
|
self:Update() |
|
end |
|
|
|
self.idSelVisible.OnPress = function() |
|
editor.ClearSel() |
|
editor.AddToSel(XEditorGetVisibleObjects()) |
|
end |
|
|
|
self.idClassStatic.OnPress = function() |
|
self.sort_by = "class" |
|
self:Update() |
|
return "break" |
|
end |
|
|
|
self.idNumberStatic.OnPress = function() |
|
self.sort_by = "number" |
|
self:Update() |
|
return "break" |
|
end |
|
|
|
self.idPercentStatic.OnPress = function() |
|
self.sort_by = "number" |
|
self:Update() |
|
return "break" |
|
end |
|
self.idSelUnderground.OnPress = function() |
|
local objs = MapGet("map", function(o) |
|
local center, radius = o:GetBSphere() |
|
return terrain.GetHeight(center) > center:z() + radius |
|
end) or empty_table |
|
editor.ClearSel() |
|
editor.AddToSel(objs) |
|
OpenGedGameObjectEditor(objs) |
|
self:Update() |
|
end |
|
|
|
self.idViewNext.OnPress = function() |
|
ViewNextObject("SelectionEditorDlg", editor.GetSel()) |
|
end |
|
|
|
self.idRotate.OnPress = function() |
|
self:ApplyToWorkingList(function(obj) |
|
obj:SetAngle(AsyncRand(360*60)) |
|
end) |
|
end |
|
|
|
self.idHide.OnPress = function () |
|
self:ApplyToWorkingList(function(obj) |
|
obj:ClearEnumFlags(const.efVisible) |
|
end) |
|
end |
|
|
|
self.idHideOthers.OnPress = function () |
|
self:ApplyToUnselectedList(function(obj) |
|
obj:ClearEnumFlags(const.efVisible) |
|
end) |
|
end |
|
|
|
self.idShow.OnPress = function() |
|
local multiple, obj_to_view |
|
local list = self.idStatList |
|
local selected = list:GetSelection()[1] |
|
self:ApplyToWorkingList(function(obj) |
|
if obj:GetEnumFlags(const.efVisible) == 0 and |
|
GetClassEnumFlags(obj.class, const.efVisible) then |
|
obj:SetEnumFlags(const.efVisible) |
|
end |
|
if selected and list[selected].class == obj.class then |
|
obj_to_view = not multiple and obj or false |
|
multiple = true |
|
end |
|
end ) |
|
if obj_to_view then |
|
ViewObject(obj_to_view) |
|
end |
|
end |
|
|
|
self.idSelDuplicate.OnPress = function() |
|
self.count_all = false |
|
local duplicates = FindDuplicates() |
|
self:SetEditorSelection(duplicates) |
|
end |
|
|
|
self.idSelectOnlyPermanentCheck:SetCheck(self.select_only_permanents) |
|
end |
|
|
|
function SelectionEditorDlg:InitColorize() |
|
local ControlRangeLoad = function(ctrl_range_min, ctrl_range_max, ctrl_min, ctrl_max) |
|
ctrl_range_min:SetScroll(100) |
|
ctrl_range_max:SetScroll(100) |
|
ctrl_min:SetText("100") |
|
ctrl_max:SetText("100") |
|
end |
|
|
|
ControlRangeLoad(self.idRMinSlider, self.idRMaxSlider, self.idRMin, self.idRMax) |
|
ControlRangeLoad(self.idGMinSlider, self.idGMaxSlider, self.idGMin, self.idGMax) |
|
ControlRangeLoad(self.idBMinSlider, self.idBMaxSlider, self.idBMin, self.idBMax) |
|
|
|
local CenterRange = function(ctrl_range_min, ctrl_range_max, left) |
|
if left then |
|
ctrl_range_min:SetScroll(100) |
|
ctrl_range_max:SetScroll(Max(ctrl_range_max:GetScroll(), 100)) |
|
else |
|
ctrl_range_min:SetScroll(Min(ctrl_range_min:GetScroll(), 100)) |
|
ctrl_range_max:SetScroll(100) |
|
end |
|
end |
|
|
|
local ControlRangeImpl = function (ctrl_range_min, ctrl_range_max, ctrl_min, ctrl_max) |
|
ctrl_range_min.OnScrollTo = function(this, value) |
|
ctrl_min:SetText(tostring(value)) |
|
if value > ctrl_range_max:GetScroll() then |
|
ctrl_range_max:SetScroll(value) |
|
ctrl_max:SetText(tostring(value)) |
|
end |
|
end |
|
ctrl_range_max.OnScrollTo = function(this, value) |
|
ctrl_max:SetText(tostring(value)) |
|
if value < ctrl_range_min:GetScroll() then |
|
ctrl_range_min:SetScroll(value) |
|
ctrl_min:SetText(tostring(value)) |
|
end |
|
end |
|
ctrl_min.OnPress = function(this) |
|
CenterRange(ctrl_range_min, ctrl_range_max, true) |
|
end |
|
ctrl_max.OnPress = function(this) |
|
CenterRange(ctrl_range_min, ctrl_range_max, false) |
|
end |
|
end |
|
|
|
ControlRangeImpl(self.idRMinSlider, self.idRMaxSlider, self.idRMin, self.idRMax) |
|
ControlRangeImpl(self.idGMinSlider, self.idGMaxSlider, self.idGMin, self.idGMax) |
|
ControlRangeImpl(self.idBMinSlider, self.idBMaxSlider, self.idBMin, self.idBMax) |
|
|
|
local function RandColor(rm, rM, gm, gM, bm, bM) |
|
return RGB(rm + AsyncRand(rM - rm), gm + AsyncRand(gM - gm), bm + AsyncRand(bM - bm)) |
|
end |
|
|
|
local ColorizeSelection = function(rm, rM, gm, gM, bm, bM, color_prop) |
|
color_prop = color_prop or "ColorModifier" |
|
self:ApplyToWorkingList(function(obj) |
|
local handler = "Set" .. color_prop |
|
if obj:HasMember(handler) then |
|
obj[handler](obj, RandColor(rm, rM, gm, gM, bm, bM)) |
|
elseif obj:HasMember(color_prop) then |
|
obj[color_prop] = RandColor(rm, rM, gm, gM, bm, bM) |
|
end |
|
end) |
|
end |
|
|
|
self.ctrlColorProp:SetItems{ |
|
{ name = "ColorModifier", id = "ColorModifier" }, |
|
{ name = "Color1", id = "Color1" }, |
|
{ name = "Color2", id = "Color2" }, |
|
{ name = "Color3", id = "Color3" }, |
|
} |
|
self.ctrlColorProp:SetValue("ColorModifier") |
|
self.idColorize.OnPress = function() |
|
local color_prop = self.ctrlColorProp:GetValue() |
|
ColorizeSelection( |
|
self.idRMinSlider:GetScroll(), self.idRMaxSlider:GetScroll(), |
|
self.idGMinSlider:GetScroll(), self.idGMaxSlider:GetScroll(), |
|
self.idBMinSlider:GetScroll(), self.idBMaxSlider:GetScroll(), |
|
color_prop) |
|
end |
|
end |
|
|
|
function SelectionEditorDlg:InitList() |
|
local list = self.idStatList |
|
list.OnSelection = function(list, focused_item, selection) |
|
local enable = #selection > 0 |
|
for _, v in ipairs(btn_percents) do |
|
local str_perc = self["idPercent"..v] |
|
str_perc:SetEnabled(enable) |
|
end |
|
end |
|
list.OnDoubleClick = function(list, idx) |
|
local item = list[idx] |
|
if item then |
|
self:SelectPerc(100) |
|
end |
|
end |
|
end |
|
|
|
function SelectionEditorDlg:GetListSelectionObjects() |
|
local list = self.idStatList |
|
local sel = list:GetSelection() |
|
local classes, objs = {}, {} |
|
for _, v in ipairs(sel) do |
|
local cls = list[v].idClass:GetText() |
|
classes[cls] = true |
|
end |
|
|
|
self:CheckEditorSelectionCache() |
|
|
|
self:CalcWorkingList(function (o) |
|
if classes[o.class] then |
|
objs[#objs + 1] = o |
|
end |
|
end) |
|
return objs |
|
end |
|
|
|
function SelectionEditorDlg:SelectPerc(perc) |
|
local objs = self:GetListSelectionObjects() |
|
local numb_to_remove = #objs - #objs * perc / 100 |
|
for i = 1, numb_to_remove do |
|
table.remove(objs, AsyncRand(#objs)+1) |
|
end |
|
editor.ClearSel() |
|
editor.AddToSel(objs) |
|
self.editor_selection = editor.GetSel() |
|
|
|
self:Update() |
|
end |
|
|
|
function SelectionEditorDlg:ApplyToWorkingList(f) |
|
local list = self.idStatList |
|
local sel = list:GetSelection() |
|
local classes = {} |
|
for _, v in ipairs(sel) do |
|
local cls = list[v].idClass:GetText() |
|
classes[cls] = true |
|
end |
|
|
|
self:CalcWorkingList(function (o) |
|
if classes[o.class] then |
|
f(o) |
|
end |
|
end) |
|
|
|
self:Update() |
|
end |
|
|
|
function SelectionEditorDlg:ApplyToUnselectedList(f) |
|
local list = self.idStatList |
|
local sel = list:GetSelection() |
|
local classes = {} |
|
for _, v in ipairs(sel) do |
|
local cls = list[v].idClass:GetText() |
|
classes[cls] = true |
|
end |
|
|
|
self:CalcWorkingList(function (o) |
|
if not classes[o.class] then |
|
f(o) |
|
end |
|
end) |
|
self:Update() |
|
end |
|
|
|
function SelectionEditorDlg:SetEditorSelection(sel) |
|
editor.ClearSel() |
|
table.iclear(self.editor_selection) |
|
if sel and #sel > 0 then |
|
editor.AddToSel(sel) |
|
self.editor_selection = editor.GetSel() |
|
end |
|
self:Update() |
|
end |
|
|
|
function SelectionEditorDlg:Update(rebuild) |
|
local list = self.idStatList |
|
local selection = list:GetSelection() |
|
local classes_selected = {} |
|
for _, v in ipairs(selection) do |
|
local cls = list[v].idClass:GetText() |
|
classes_selected[cls] = true |
|
end |
|
|
|
local items = {} |
|
local totalObjects = 0 |
|
|
|
local filter = string.lower(self.idFilterText:GetText()) |
|
local rejected_items = {} |
|
|
|
self:CalcWorkingList(function(v) |
|
if items[v.class] then |
|
items[v.class] = items[v.class] + 1 |
|
totalObjects = totalObjects + 1 |
|
elseif not rejected_items[v.class] then |
|
if filter ~= "" and not string.find(string.lower(v.class), filter, 1, true) then |
|
rejected_items[v.class] = true |
|
else |
|
items[v.class] = 1 |
|
totalObjects = totalObjects + 1 |
|
end |
|
end |
|
end) |
|
|
|
local list_items = {} |
|
for k, v in pairs(items) do |
|
table.insert(list_items, { class = k, number = v, perc = v * 10000 / totalObjects }) |
|
end |
|
|
|
if self.sort_by == "class" then |
|
table.sort(list_items, function(i1, i2) return i1.class < i2.class end) |
|
else |
|
table.sort(list_items, function(i1, i2) return i1.number > i2.number end) |
|
end |
|
list:Clear() |
|
local selection = {} |
|
for i = 1, #list_items do |
|
local item = XTemplateSpawn("ClassesListItem",list) |
|
item.selectable = true |
|
item.idClass:SetText(list_items[i].class) |
|
item.idCount:SetText(tostring(list_items[i].number)) |
|
local str_perc = "" |
|
local objperc = list_items[i].perc |
|
if objperc then |
|
str_perc = "100%" |
|
local perc = objperc/100 |
|
if perc < 10 then |
|
str_perc = string.format("%d.%02d%%", perc, objperc%100) |
|
elseif perc < 100 then |
|
str_perc = string.format("%d.%d%%", perc, objperc%10) |
|
end |
|
end |
|
item.idPercent:SetText(str_perc) |
|
if classes_selected[list_items[i].class] then |
|
table.insert(selection, i) |
|
end |
|
end |
|
|
|
if #selection == 0 or rebuild then |
|
list:SelectAll() |
|
else |
|
list:SetSelection(selection) |
|
end |
|
|
|
local objs_per_sq_meter = totalObjects * 100 / (terrain.GetMapHeight() / guim * terrain.GetMapWidth() / guim) |
|
self.idTotalCount:SetText(string.format("Total %d, %d.%02d per m2", totalObjects, objs_per_sq_meter / 100, objs_per_sq_meter % 100)) |
|
end |
|
|
|
function SelectionEditorDlg:CalcWorkingList(callback) |
|
if #self.editor_selection > 0 then |
|
for _, obj in ipairs(self.editor_selection) do |
|
if not self.select_only_permanents or obj:GetGameFlags(const.gofPermanent) ~= 0 then |
|
callback(obj) |
|
end |
|
end |
|
return |
|
end |
|
|
|
if not self.count_all then return end |
|
MapForEach("map", nil, nil, self.select_only_permanents and const.gofPermanent or 0, callback) |
|
end |
|
|
|
function SelectionEditorDlg:OnShortcut(shortcut, source, ...) |
|
if GetUIStyleGamepad() and shortcut == "ButtonB" or shortcut == "Escape" then |
|
self:Close() |
|
return "break" |
|
end |
|
if shortcut == "Delete" then |
|
local objs = self:GetListSelectionObjects() |
|
SuspendPassEdits("XEditorDeleteSel") |
|
XEditorUndo:BeginOp{ objects = objs, name = string.format("Deleted %d objects", #objs) } |
|
editor.RemoveFromSel(objs) |
|
Msg("EditorCallback", "EditorCallbackDelete", objs) |
|
for _, obj in ipairs(objs) do obj:delete() end |
|
XEditorUndo:EndOp() |
|
ResumePassEdits("XEditorDeleteSel") |
|
return "break" |
|
end |
|
end |
|
|