2023-09-26 22:11:34 +02:00
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 + 0 xC ;
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 {
2023-09-27 01:23:13 +02:00
if ( UI :: Button ( Icons :: Refresh + " Refresh" )) {
2023-09-26 22:11:34 +02:00
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 ;
}
}