diff --git a/Blocks&ItemsCounter/Source/Blocks&ItemsCounter.as b/Blocks&ItemsCounter/Source/Blocks&ItemsCounter.as new file mode 100644 index 0000000..d6576c5 --- /dev/null +++ b/Blocks&ItemsCounter/Source/Blocks&ItemsCounter.as @@ -0,0 +1,353 @@ +class Objects { //Items or Blocks + string name; + int trigger; // CGameItemModel::EnumWaypointType or CGameCtnBlockInfo::EWayPointType + string type; + string source; + int size; + int count; + bool icon; + array positions; + + Objects(string name, int trigger, bool icon, string type, string source, int size, vec3 pos ) { + this.name = name; + this.trigger = trigger; + this.count = 1; + this.type = type; + this.icon = icon; + this.source = source; + this.size = size; + this.positions = {pos}; + } +} + +enum ESortColumn { + ItemName, + Type, + Source, + Size, + Count +} + + +bool menu_visibility = false; +int camerafocusindex = 0; +bool include_default_objects = false; +bool refreshobject; + +bool sort_reverse; +bool forcesort; +string infotext; + +array objects = {}; +array sortableobjects = {}; +array objectsindex = {}; + + +ESortColumn sortCol = ESortColumn(-1); + +void Main() { + while (true) { + if (refreshobject) { + objects.Resize(0); + objectsindex.Resize(0); + sortableobjects.Resize(0); + RefreshBlocks(); + RefreshItems(); + sortableobjects = objects; + refreshobject = false; + } + yield(); + } +} + +// Force to split the refresh functions to bypass the script execution delay on heavy maps +void RefreshBlocks() { + auto map = GetApp().RootMap; + + if (map !is null) { + // Blocks + auto blocks = map.Blocks; + + // Editor plugin API for GetVec3FromCoord function + auto pluginmaptype = cast(cast(GetApp().Editor).PluginMapType); + + for(int i = 0; i < blocks.Length; i++) { + int idifexist = -1; + string blockname; + blockname = blocks[i].BlockModel.IdName; + if (blockname.ToLower().SubStr(blockname.Length - 22, 22) == ".block.gbx_customblock") blockname = blockname.SubStr(0, blockname.Length - 12); + if (include_default_objects || blockname.ToLower().SubStr(blockname.Length - 10, 10) == ".block.gbx") { + vec3 pos; + if (blocks[i].CoordX != 4294967295 && blocks[i].CoordZ != 4294967295) { // Not placed in free mapping + if (pluginmaptype != null) { // Editor plugin is available + pos = pluginmaptype.GetVec3FromCoord(blocks[i].Coord); + } else { + pos.x = blocks[i].CoordX * 32 + 16; + pos.y = (blocks[i].CoordY - 8) * 8 + 4; + pos.z = blocks[i].CoordZ * 32 + 16; + } + } else { + pos = Dev::GetOffsetVec3(blocks[i], 0x6c); + // center the coordinates in the middle of the block + pos.x += 16; + pos.y += 4; + pos.z += 16; + } + + + int index = objectsindex.Find(blockname); + + if (index >= 0) { + objects[index].count++; + objects[index].positions.InsertLast(pos); + } else { + int trigger = blocks[i].BlockModel.EdWaypointType; + AddNewObject(blockname, trigger, "Block", pos ); + objectsindex.InsertLast(blockname); + } + } + if (i % 100 == 0) yield(); // to avoid timeout + } + } +} + +// Force to split the refresh functions to bypass the script execution delay on heavy maps +void RefreshItems() { + auto map = GetApp().RootMap; + + if (map !is null) { + // Items + auto items = map.AnchoredObjects; + for(int i = 0; i < items.Length; i++) { + int idifexist = -1; + string itemname = items[i].ItemModel.IdName; + if (include_default_objects || itemname.ToLower().SubStr(itemname.Length - 9, 9) == ".item.gbx") { + int index = objectsindex.Find(itemname); + if (index >= 0) { + objects[index].count++; + objects[index].positions.InsertLast(items[i].AbsolutePositionInMap); + } else { + int trigger = items[i].ItemModel.WaypointType; + AddNewObject(itemname, trigger, "Item", items[i].AbsolutePositionInMap); + objectsindex.InsertLast(itemname); + } + } + if (i % 100 == 0) yield(); // to avoid timeout + } + } +} + +void AddNewObject(string objectname, int trigger, string type, vec3 pos) { + bool icon = false; + int size; + string source; + CSystemFidFile@ file; + CGameCtnCollector@ collector; + CSystemFidFile@ tempfile; + + if (type == "Item" && objectname.SubStr(0,5) == "club:") {// ItemCollections + source = "Club"; + @file = Fids::GetFake('MemoryTemp\\FavoriteClubItems\\' + objectname.SubStr(5,objectname.Length)); + @collector = cast(cast(file.Nod)); + if (collector is null || (collector.Icon !is null || file.ByteSize == 0)) { + @tempfile = Fids::GetFake('MemoryTemp\\CurrentMap_EmbeddedFiles\\ContentLoaded\\ClubItems\\' + objectname.SubStr(5,objectname.Length)); + } + } else { // Blocks and Items + source = "Local"; + @file = Fids::GetUser(type + 's\\' + objectname); + @collector = cast(cast(file.Nod)); + if (collector is null || (collector.Icon !is null || file.ByteSize == 0)) { + @tempfile = Fids::GetFake('MemoryTemp\\CurrentMap_EmbeddedFiles\\ContentLoaded\\' + type + 's\\' + objectname); + } + } + if (tempfile !is null) { + if (collector !is null && collector.Icon !is null && tempfile.ByteSize == 0) { + icon = true; + size = file.ByteSize; + } else { + size = tempfile.ByteSize; + } + if (file.ByteSize == 0 && tempfile.ByteSize == 0) { + source = "In-Game"; + } else if (file.ByteSize == 0 && tempfile.ByteSize > 0) { + source = "Embedded"; + } + } else { + size = file.ByteSize; + } + + objects.InsertLast(Objects(objectname, trigger, icon, type, source, size, pos)); +} + +bool FocusCam(string objectname) { + auto editor = cast(GetApp().Editor); + auto camera = editor.OrbitalCameraControl; + auto map = GetApp().RootMap; + + + if (camera !is null) { + int index = objectsindex.Find(objectname); + + camerafocusindex++; + + if (camerafocusindex > objects[index].positions.get_Length() - 1 ) { + camerafocusindex = 0; + } + + camera.m_TargetedPosition = objects[index].positions[camerafocusindex]; + // Workaround to update camera TargetedPosition + editor.ButtonZoomInOnClick(); + editor.ButtonZoomOutOnClick(); + return true; + } + return false; +} + +void GenerateRow(Objects@ object) { + UI::TableNextRow(); + UI::TableNextColumn(); + if (UI::Button(Icons::Search + "###" + object.name)) { + FocusCam(object.name); + } + if (UI::IsItemHovered() && object.type == "Block" && cast(cast(GetApp().Editor).PluginMapType) == null) infotext = "Editor plugins are disabled, the coordinates of the blocks are estimated and can be imprecise"; + UI::SameLine(); + switch(object.trigger){ + case CGameCtnBlockInfo::EWayPointType::Start: + UI::Text("\\$9f9" + object.name); + if (UI::IsItemHovered()) infotext = "It's a start block/item"; + break; + case CGameCtnBlockInfo::EWayPointType::Finish: + UI::Text("\\$f66" + object.name); + if (UI::IsItemHovered()) infotext = "It's a finish block/item"; + break; + case CGameCtnBlockInfo::EWayPointType::Checkpoint: + UI::Text("\\$99f" + object.name); + if (UI::IsItemHovered()) infotext = "It's a checkpoint block/item"; + break; + case CGameCtnBlockInfo::EWayPointType::StartFinish: + UI::Text("\\$ff6" + object.name); + if (UI::IsItemHovered()) infotext = "It's a multilap block/item"; + break; + default: + UI::Text(object.name); + break; + } + + UI::TableNextColumn(); + UI::Text(object.type); + UI::TableNextColumn(); + UI::Text(object.source); + UI::TableNextColumn(); + if (object.icon) { + UI::Text("\\$fc0" + Text::Format("%lld",object.size)); + if (UI::IsItemHovered()) infotext = "All items with size in orange contains the icon. You must re-open the map to have the real size."; + } else { + UI::Text(Text::Format("%lld",object.size)); + } + UI::TableNextColumn(); + UI::Text(Text::Format("%lld",object.count)); +} + +void Render() { + if (!menu_visibility) { + return; + } + infotext = ""; + auto editor = cast(GetApp().Editor); + + UI::SetNextWindowSize(600, 400); + UI::SetNextWindowPos(200, 200, UI::Cond::Once); + UI::Begin("\\$cf9" + Icons::Table + "\\$z Blocks & Items Counter###Blocks & Items Counter", menu_visibility); + if (editor !is null) { + if (refreshobject) { + if (Time::get_Now() % 3000 > 2000) { + UI::Button("Loading..."); + } else if (Time::get_Now() % 3000 > 1000) { + UI::Button("Loading.. "); + } else { + UI::Button("Loading. "); + } + if (UI::IsItemHovered()) infotext = "Parsing all blocks and items to generate the table. Please wait..."; + } else { + if (UI::Button(Icons::SyncAlt + " Refresh")) { + refreshobject = true; + forcesort = true; + } + } + UI::SameLine(); + include_default_objects = UI::Checkbox("Include In-Game Blocks and Items", include_default_objects); + UI::Separator(); + vec2 winsize = UI::GetWindowSize(); + winsize.x = winsize.x-10; + winsize.y = winsize.y-105; + if (UI::BeginTable("ItemsTable", 5, UI::TableFlags(UI::TableFlags::Resizable | UI::TableFlags::Sortable | UI::TableFlags::NoSavedSettings | UI::TableFlags::BordersInnerV | UI::TableFlags::SizingStretchProp | UI::TableFlags::ScrollY),winsize )) { + UI::TableSetupScrollFreeze(0, 1); + UI::TableSetupColumn("Item Name", UI::TableColumnFlags::None, 55.f, ESortColumn::ItemName); + UI::TableSetupColumn("Type", UI::TableColumnFlags::None, 7.f, ESortColumn::Type); + UI::TableSetupColumn("Source", UI::TableColumnFlags::None, 13.f, ESortColumn::Source); + UI::TableSetupColumn("Size", UI::TableColumnFlags::None, 15.f, ESortColumn::Size); + UI::TableSetupColumn("Count", UI::TableColumnFlags::DefaultSort, 10.f, ESortColumn::Count); + UI::TableHeadersRow(); + + UI::TableSortSpecs@ sortSpecs = UI::TableGetSortSpecs(); + if(sortSpecs !is null && sortSpecs.Specs.Length == 1 && sortableobjects.Length > 1) { + if(sortSpecs.Dirty || (forcesort && !refreshobject)) { + if(sortCol != ESortColumn(sortSpecs.Specs[0].ColumnUserID) || (forcesort && !refreshobject)) { + sortCol = ESortColumn(sortSpecs.Specs[0].ColumnUserID); + switch(sortCol) { + case ESortColumn::ItemName: + sortableobjects.Sort(function(a,b) { return a.name < b.name; }); + break; + case ESortColumn::Type: + sortableobjects.Sort(function(a,b) { return a.type < b.type; }); + break; + case ESortColumn::Source: + sortableobjects.Sort(function(a,b) { return a.source < b.source; }); + break; + case ESortColumn::Size: + sortableobjects.Sort(function(a,b) { return a.size < b.size; }); + break; + case ESortColumn::Count: + sortableobjects.Sort(function(a,b) { return a.count < b.count; }); + break; + } + if (forcesort && sort_reverse) { + sortableobjects.Reverse(); + } else { + sort_reverse = false; + } + } else if (sortCol == ESortColumn(sortSpecs.Specs[0].ColumnUserID)) { + sortableobjects.Reverse(); + sort_reverse = !sort_reverse; + } + + sortSpecs.Dirty = false; + forcesort = false; + } + } + if (sortableobjects.Length > 0 ) { + for(int i = 0; i < sortableobjects.Length; i++) { + GenerateRow(sortableobjects[i]); + } + } else { + for(int i = 0; i < objects.Length; i++) { + GenerateRow(objects[i]); + } + } + UI::EndTable(); + UI::Separator(); + UI::Text(Icons::Info + " " + infotext); + } + } else { + UI::Text("Open this plugin in the map editor"); + } + UI::End(); + +} + +void RenderMenu() { + if(UI::MenuItem("\\$cf9" + Icons::Table + "\\$z Blocks & Items Counter", "", menu_visibility)) { + menu_visibility = !menu_visibility; + refreshobject = true; + } +} diff --git a/Blocks&ItemsCounter/info.toml b/Blocks&ItemsCounter/info.toml new file mode 100644 index 0000000..b0cd2c7 --- /dev/null +++ b/Blocks&ItemsCounter/info.toml @@ -0,0 +1,12 @@ +[meta] +name = "Blocks & Items Counter" +author = "Beu" +category = "Map Editor" + +siteid = 97 +version = "1.1" + +blocks = [ "Plugin_Blocks&ItemsCounter" ] + +[script] +imports = [ "Icons.as" ]