2021-10-02 00:49:50 +02:00
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 < vec3 > 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 ;
2022-04-08 18:43:56 +02:00
uint camerafocusindex = 0 ;
2021-10-02 00:49:50 +02:00
bool include_default_objects = false ;
bool refreshobject ;
bool sort_reverse ;
bool forcesort ;
string infotext ;
array < Objects @ > objects = {};
array < Objects @ > sortableobjects = {};
array < string > 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 < CGameEditorPluginMapMapType > ( cast < CGameCtnEditorFree > ( GetApp (). Editor ). PluginMapType );
2022-04-08 18:43:56 +02:00
for ( uint i = 0 ; i < blocks . Length ; i ++ ) {
2021-10-02 00:49:50 +02:00
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
2022-04-08 18:43:56 +02:00
if ( pluginmaptype ! is null ) { // Editor plugin is available
2021-10-02 00:49:50 +02:00
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 ;
2022-04-08 18:43:56 +02:00
for ( uint i = 0 ; i < items . Length ; i ++ ) {
2021-10-02 00:49:50 +02:00
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 < CGameCtnCollector > ( cast < CGameItemModel > ( 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 < CGameCtnCollector > ( cast < CGameItemModel > ( 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 < CGameCtnEditorFree > ( 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 );
}
2022-04-08 18:43:56 +02:00
if ( UI :: IsItemHovered () && object . type == "Block" && 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" ;
2021-10-02 00:49:50 +02:00
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 < CGameCtnEditorFree > ( 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 ) {
2022-04-08 18:43:56 +02:00
for ( uint i = 0 ; i < sortableobjects . Length ; i ++ ) {
2021-10-02 00:49:50 +02:00
GenerateRow ( sortableobjects [ i ]);
}
} else {
2022-04-08 18:43:56 +02:00
for ( uint i = 0 ; i < objects . Length ; i ++ ) {
2021-10-02 00:49:50 +02:00
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 ;
}
}