Add DuplicateFinder
This commit is contained in:
		
							
								
								
									
										9
									
								
								DuplicateFinder/info.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								DuplicateFinder/info.toml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,9 @@
 | 
			
		||||
[meta]
 | 
			
		||||
name = "DuplicateFinder"
 | 
			
		||||
author = "Beu"
 | 
			
		||||
category = "Map Editor"
 | 
			
		||||
version = "1.1"
 | 
			
		||||
siteid = 220
 | 
			
		||||
 | 
			
		||||
[script]
 | 
			
		||||
timeout = 0
 | 
			
		||||
							
								
								
									
										303
									
								
								DuplicateFinder/main.as
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										303
									
								
								DuplicateFinder/main.as
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,303 @@
 | 
			
		||||
class Objects { //Items or Blocks
 | 
			
		||||
	string name;
 | 
			
		||||
	string type;
 | 
			
		||||
	int count;
 | 
			
		||||
	vec3 position;
 | 
			
		||||
	vec3 rotation;
 | 
			
		||||
	Objects(string name, string type, vec3 pos, vec3 rot ) {
 | 
			
		||||
		this.name = name;
 | 
			
		||||
		this.type = type;
 | 
			
		||||
		this.count = 2; // Object if added only if duplicate
 | 
			
		||||
		this.position = pos;
 | 
			
		||||
		this.rotation = rot;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
enum ESortColumn {
 | 
			
		||||
	ItemName,
 | 
			
		||||
	Count
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const float pi =  3.14159265359;
 | 
			
		||||
 | 
			
		||||
bool include_default_blocks;
 | 
			
		||||
bool menu_visibility = false;
 | 
			
		||||
bool refreshobject;
 | 
			
		||||
 | 
			
		||||
bool sort_reverse;
 | 
			
		||||
bool forcesort;
 | 
			
		||||
string infotext;
 | 
			
		||||
 | 
			
		||||
array<Objects@> objects = {};
 | 
			
		||||
array<Objects@> sortableobjects = {};
 | 
			
		||||
array<string> objectsindex = {};
 | 
			
		||||
 | 
			
		||||
int totalobjects;
 | 
			
		||||
int computedobjects;
 | 
			
		||||
 | 
			
		||||
ESortColumn sortCol = ESortColumn(-1);
 | 
			
		||||
 | 
			
		||||
void Main() {
 | 
			
		||||
	while (true) {
 | 
			
		||||
		if (refreshobject) {
 | 
			
		||||
			objects.Resize(0);
 | 
			
		||||
			objectsindex.Resize(0);
 | 
			
		||||
			sortableobjects.Resize(0);
 | 
			
		||||
			RefreshBlocksAndItems();
 | 
			
		||||
			sortableobjects = objects;
 | 
			
		||||
			refreshobject = false;
 | 
			
		||||
		}
 | 
			
		||||
		yield();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Force to split the refresh functions to bypass the script execution delay on heavy maps
 | 
			
		||||
void RefreshBlocksAndItems() {
 | 
			
		||||
	auto map = GetApp().RootMap;
 | 
			
		||||
	dictionary singleobjectdico = {};
 | 
			
		||||
 | 
			
		||||
	if (map !is null) {
 | 
			
		||||
		totalobjects = map.Blocks.Length +  map.AnchoredObjects.Length;
 | 
			
		||||
		computedobjects = 0;
 | 
			
		||||
 | 
			
		||||
		// Blocks
 | 
			
		||||
		auto blocks = map.Blocks;
 | 
			
		||||
 | 
			
		||||
		// Editor plugin API for GetVec3FromCoord function
 | 
			
		||||
		auto pluginmaptype = cast<CGameEditorPluginMapMapType>(cast<CGameCtnEditorFree>(GetApp().Editor).PluginMapType);
 | 
			
		||||
 | 
			
		||||
		for(uint i = 0; i < blocks.Length; i++) {
 | 
			
		||||
			if (i % 1000 == 0) yield(); // to avoid timeout
 | 
			
		||||
			computedobjects++;
 | 
			
		||||
			
 | 
			
		||||
			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);
 | 
			
		||||
 | 
			
		||||
			vec3 pos;
 | 
			
		||||
			vec3 rot;
 | 
			
		||||
			if (blocks[i].CoordX != 4294967295 && blocks[i].CoordZ != 4294967295) { // Not placed in free mapping
 | 
			
		||||
				if (!include_default_blocks) continue;
 | 
			
		||||
				if (pluginmaptype !is null) { // Editor plugin is available
 | 
			
		||||
					pos = pluginmaptype.GetVec3FromCoord(blocks[i].Coord);
 | 
			
		||||
				} else {
 | 
			
		||||
					pos.x = blocks[i].CoordX * 32;
 | 
			
		||||
					pos.y = (blocks[i].CoordY - 8) * 8;
 | 
			
		||||
					pos.z = blocks[i].CoordZ * 32;
 | 
			
		||||
				}
 | 
			
		||||
				switch(blocks[i].BlockDir) {
 | 
			
		||||
					case CGameCtnBlock::ECardinalDirections::East:
 | 
			
		||||
						rot.x = 90;
 | 
			
		||||
						break;
 | 
			
		||||
					case CGameCtnBlock::ECardinalDirections::South:
 | 
			
		||||
						rot.x = 180;
 | 
			
		||||
						break;
 | 
			
		||||
					case CGameCtnBlock::ECardinalDirections::West:
 | 
			
		||||
						rot.x = 270;
 | 
			
		||||
						break;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
			} else {
 | 
			
		||||
				uint16 FreeBlockPosOffset = Reflection::GetType("CGameCtnBlock").GetMember("Dir").Offset + 0x8;
 | 
			
		||||
				uint16 FreeBlockRotOffset = FreeBlockPosOffset + 0xC;    
 | 
			
		||||
				
 | 
			
		||||
				pos = Dev::GetOffsetVec3(blocks[i], FreeBlockPosOffset);
 | 
			
		||||
				rot = Dev::GetOffsetVec3(blocks[i], FreeBlockRotOffset) / pi * 180;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			string uniqueid = pos.x + ";" + pos.y + ";" + pos.z + ";;"  + rot.x + ";" + rot.y + ";" + rot.z + ";;"+ blockname;
 | 
			
		||||
 | 
			
		||||
			if (singleobjectdico.Exists(uniqueid)) {
 | 
			
		||||
				int index = objectsindex.Find(uniqueid);
 | 
			
		||||
				if (index >= 0) {
 | 
			
		||||
					objects[index].count++;
 | 
			
		||||
				} else {
 | 
			
		||||
					objects.InsertLast(Objects(blockname, "Block", pos, rot));
 | 
			
		||||
					objectsindex.InsertLast(uniqueid);
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				singleobjectdico.Set(uniqueid, 0);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		singleobjectdico.DeleteAll();
 | 
			
		||||
 | 
			
		||||
		auto items = map.AnchoredObjects;
 | 
			
		||||
 | 
			
		||||
		for(uint i = 0; i < items.Length; i++) {
 | 
			
		||||
			if (i % 1000 == 0) yield(); // to avoid timeout
 | 
			
		||||
			computedobjects++;
 | 
			
		||||
 | 
			
		||||
			string itemname = items[i].ItemModel.IdName;
 | 
			
		||||
 | 
			
		||||
			vec3 pos = items[i].AbsolutePositionInMap;
 | 
			
		||||
			vec3 rot;
 | 
			
		||||
			rot.x = items[i].Yaw;
 | 
			
		||||
			rot.y = items[i].Pitch;
 | 
			
		||||
			rot.z = items[i].Roll;
 | 
			
		||||
 | 
			
		||||
			string uniqueid = pos.x + ";" + pos.y + ";" + pos.z + ";;"  + rot.x + ";" + rot.y + ";" + rot.z + ";;"+ itemname;
 | 
			
		||||
 | 
			
		||||
			if (singleobjectdico.Exists(uniqueid)) {
 | 
			
		||||
				int index = objectsindex.Find(uniqueid);
 | 
			
		||||
				if (index >= 0) {
 | 
			
		||||
					objects[index].count++;
 | 
			
		||||
				} else {
 | 
			
		||||
					objects.InsertLast(Objects(itemname, "Item", pos, rot));
 | 
			
		||||
					objectsindex.InsertLast(uniqueid);
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				singleobjectdico.Set(uniqueid, 0);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool FocusCam(vec3 position) {
 | 
			
		||||
	auto editor = cast<CGameCtnEditorFree>(GetApp().Editor);
 | 
			
		||||
	auto camera = editor.OrbitalCameraControl;
 | 
			
		||||
	auto map = GetApp().RootMap;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	if (camera !is null) {
 | 
			
		||||
		camera.m_TargetedPosition = position;
 | 
			
		||||
		// Workaround to update camera TargetedPosition
 | 
			
		||||
		auto m_ParamScrollZoomPowe = camera.m_ParamScrollZoomPower;
 | 
			
		||||
		camera.m_ParamScrollZoomPower = 0;
 | 
			
		||||
        editor.ButtonZoomInOnClick();
 | 
			
		||||
        camera.m_ParamScrollZoomPower = m_ParamScrollZoomPowe;
 | 
			
		||||
 | 
			
		||||
		return true;
 | 
			
		||||
	}
 | 
			
		||||
	return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void GenerateRow(Objects@ object) {
 | 
			
		||||
	if (object.count <= 1) return;
 | 
			
		||||
	UI::TableNextRow();
 | 
			
		||||
	UI::TableNextColumn();
 | 
			
		||||
	if (UI::Button(Icons::Search + "###Search;" + object.position.x + ";" + object.position.y + ";" + object.position.z)) {
 | 
			
		||||
		FocusCam(object.position);
 | 
			
		||||
	}
 | 
			
		||||
	if (UI::IsItemHovered() && cast<CGameEditorPluginMapMapType>(cast<CGameCtnEditorFree>(GetApp().Editor).PluginMapType) is null) infotext = "Editor plugins are disabled, the coordinates of the blocks are estimated and can be imprecise";
 | 
			
		||||
	UI::SameLine();
 | 
			
		||||
	UI::Text(object.name);
 | 
			
		||||
	UI::TableNextColumn();
 | 
			
		||||
	UI::Text(object.type);
 | 
			
		||||
	UI::TableNextColumn();
 | 
			
		||||
	UI::Text("<" + object.position.x + ", " + object.position.y + ", " + object.position.z +">");
 | 
			
		||||
	UI::TableNextColumn();
 | 
			
		||||
	UI::Text("<" + object.rotation.x + ", " + object.rotation.y + ", " + object.rotation.z +">");
 | 
			
		||||
	UI::TableNextColumn();
 | 
			
		||||
	UI::Text(Text::Format("%lld",object.count));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Render() {
 | 
			
		||||
	if (!menu_visibility) {
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	CGameCtnEditorFree@ editor = cast<CGameCtnEditorFree>(GetApp().Editor);
 | 
			
		||||
	CGameCtnChallenge@ map = cast<CGameCtnChallenge>(GetApp().RootMap);
 | 
			
		||||
 | 
			
		||||
	if (map is null && editor is null) {
 | 
			
		||||
		menu_visibility = false;
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	infotext = "";
 | 
			
		||||
 | 
			
		||||
	UI::SetNextWindowSize(600, 400);
 | 
			
		||||
	UI::SetNextWindowPos(200, 200, UI::Cond::Once);
 | 
			
		||||
	UI::Begin("\\$cf9" + Icons::Table + "\\$z DuplicateFinder###DuplicateFinder", 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... (" + computedobjects + "/" + totalobjects + ")";
 | 
			
		||||
		} else {
 | 
			
		||||
			if (UI::Button(Icons::SyncAlt + " Refresh")) {
 | 
			
		||||
				refreshobject = true;
 | 
			
		||||
				forcesort = true;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		UI::SameLine();
 | 
			
		||||
		include_default_blocks = UI::Checkbox("Include Blocks not placed in Free Mapping", include_default_blocks);
 | 
			
		||||
		UI::Separator();
 | 
			
		||||
		vec2 winsize = UI::GetWindowSize();
 | 
			
		||||
		winsize.x = winsize.x - 10;
 | 
			
		||||
		winsize.y = winsize.y - 105;
 | 
			
		||||
		if (UI::BeginTable("DuplicateBlocksTable", 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("Block Name", UI::TableColumnFlags::None, 40.f, ESortColumn::ItemName);
 | 
			
		||||
			UI::TableSetupColumn("Type", UI::TableColumnFlags::NoSort, 10.f);
 | 
			
		||||
			UI::TableSetupColumn("Position", UI::TableColumnFlags::NoSort, 20.f);
 | 
			
		||||
			UI::TableSetupColumn("Rotation", UI::TableColumnFlags::NoSort, 20.f);
 | 
			
		||||
			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::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(uint i = 0; i < sortableobjects.Length; i++) {
 | 
			
		||||
					GenerateRow(sortableobjects[i]);
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				for(uint 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() {
 | 
			
		||||
	CGameCtnEditorFree@ editor = cast<CGameCtnEditorFree>(GetApp().Editor);
 | 
			
		||||
	CGameCtnChallenge@ map = cast<CGameCtnChallenge>(GetApp().RootMap);
 | 
			
		||||
 | 
			
		||||
	if (map is null && editor is null) {
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if(UI::MenuItem("\\$cf9" + Icons::Table + "\\$z DuplicateFinder", "", menu_visibility)) {
 | 
			
		||||
		menu_visibility = !menu_visibility;
 | 
			
		||||
		refreshobject = true;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user