2022-09-06 02:51:31 +01:00
# include "prefab.h"
# include "prefabframe.h"
# include "ui_prefabframe.h"
# include "parseutil.h"
# include "currentselectedmetatilespixmapitem.h"
2022-09-08 02:38:30 +01:00
# include "log.h"
2022-09-10 02:09:28 +01:00
# include "project.h"
2022-09-06 02:51:31 +01:00
# include <QJsonDocument>
# include <QJsonObject>
# include <QJsonArray>
# include <QGraphicsPixmapItem>
# include <QWidget>
# include <QDir>
# include <QSpacerItem>
2022-09-10 02:53:07 +01:00
# include <QMessageBox>
2022-09-06 02:51:31 +01:00
2022-09-10 02:48:08 +01:00
using OrderedJson = poryjson : : Json ;
using OrderedJsonDoc = poryjson : : JsonDoc ;
2023-08-31 18:47:13 +01:00
const QString defaultFilepath = " prefabs.json " ;
2022-09-06 02:51:31 +01:00
void Prefab : : loadPrefabs ( ) {
this - > items . clear ( ) ;
2023-08-31 18:47:13 +01:00
QString filepath = projectConfig . getPrefabFilepath ( ) ;
2022-09-06 02:51:31 +01:00
if ( filepath . isEmpty ( ) ) return ;
ParseUtil parser ;
QJsonDocument prefabDoc ;
2022-09-11 16:19:13 +01:00
QFileInfo info ( filepath ) ;
if ( info . isRelative ( ) ) {
filepath = QDir : : cleanPath ( projectConfig . getProjectDir ( ) + QDir : : separator ( ) + filepath ) ;
}
2022-09-06 02:51:31 +01:00
if ( ! QFile : : exists ( filepath ) | | ! parser . tryParseJsonFile ( & prefabDoc , filepath ) ) {
2022-09-11 16:52:02 +01:00
logError ( QString ( " Failed to read prefab data from %1 " ) . arg ( filepath ) ) ;
2022-09-11 16:19:13 +01:00
return ;
2022-09-06 02:51:31 +01:00
}
QJsonArray prefabs = prefabDoc . array ( ) ;
if ( prefabs . size ( ) = = 0 ) {
logWarn ( QString ( " Prefabs array is empty or missing in %1. " ) . arg ( filepath ) ) ;
return ;
}
for ( int i = 0 ; i < prefabs . size ( ) ; i + + ) {
QJsonObject prefabObj = prefabs [ i ] . toObject ( ) ;
if ( prefabObj . isEmpty ( ) )
continue ;
2022-10-14 23:55:18 +01:00
int width = ParseUtil : : jsonToInt ( prefabObj [ " width " ] ) ;
int height = ParseUtil : : jsonToInt ( prefabObj [ " height " ] ) ;
2022-09-06 02:51:31 +01:00
if ( width < = 0 | | height < = 0 )
continue ;
2022-10-14 23:55:18 +01:00
QString name = ParseUtil : : jsonToQString ( prefabObj [ " name " ] ) ;
QString primaryTileset = ParseUtil : : jsonToQString ( prefabObj [ " primary_tileset " ] ) ;
QString secondaryTileset = ParseUtil : : jsonToQString ( prefabObj [ " secondary_tileset " ] ) ;
2022-09-06 02:51:31 +01:00
MetatileSelection selection ;
selection . dimensions = QPoint ( width , height ) ;
selection . hasCollision = true ;
for ( int j = 0 ; j < width * height ; j + + ) {
selection . metatileItems . append ( MetatileSelectionItem { false , 0 } ) ;
selection . collisionItems . append ( CollisionSelectionItem { false , 0 , 0 } ) ;
}
QJsonArray metatiles = prefabObj [ " metatiles " ] . toArray ( ) ;
for ( int j = 0 ; j < metatiles . size ( ) ; j + + ) {
QJsonObject metatileObj = metatiles [ j ] . toObject ( ) ;
2022-10-14 23:55:18 +01:00
int x = ParseUtil : : jsonToInt ( metatileObj [ " x " ] ) ;
int y = ParseUtil : : jsonToInt ( metatileObj [ " y " ] ) ;
2022-09-06 02:51:31 +01:00
if ( x < 0 | | x > = width | | y < 0 | | y > = height )
continue ;
int index = y * width + x ;
2022-10-14 23:55:18 +01:00
int metatileId = ParseUtil : : jsonToInt ( metatileObj [ " metatile_id " ] ) ;
2022-09-10 18:33:25 +01:00
if ( metatileId < 0 | | metatileId > = Project : : getNumMetatilesTotal ( ) )
continue ;
2022-10-14 23:55:18 +01:00
selection . metatileItems [ index ] . metatileId = ParseUtil : : jsonToInt ( metatileObj [ " metatile_id " ] ) ;
selection . collisionItems [ index ] . collision = ParseUtil : : jsonToInt ( metatileObj [ " collision " ] ) ;
selection . collisionItems [ index ] . elevation = ParseUtil : : jsonToInt ( metatileObj [ " elevation " ] ) ;
2022-09-10 18:33:25 +01:00
selection . metatileItems [ index ] . enabled = true ;
selection . collisionItems [ index ] . enabled = true ;
2022-09-06 02:51:31 +01:00
}
2022-09-10 01:37:25 +01:00
this - > items . append ( PrefabItem { QUuid : : createUuid ( ) , name , primaryTileset , secondaryTileset , selection } ) ;
2022-09-06 02:51:31 +01:00
}
}
2022-09-10 02:48:08 +01:00
void Prefab : : savePrefabs ( ) {
2023-08-31 18:47:13 +01:00
QString filepath = projectConfig . getPrefabFilepath ( ) ;
if ( filepath . isEmpty ( ) ) {
filepath = defaultFilepath ;
projectConfig . setPrefabFilepath ( filepath ) ;
}
2022-09-11 16:19:13 +01:00
QFileInfo info ( filepath ) ;
if ( info . isRelative ( ) ) {
filepath = QDir : : cleanPath ( projectConfig . getProjectDir ( ) + QDir : : separator ( ) + filepath ) ;
}
2022-09-10 02:48:08 +01:00
QFile prefabsFile ( filepath ) ;
if ( ! prefabsFile . open ( QIODevice : : WriteOnly ) ) {
logError ( QString ( " Error: Could not open %1 for writing " ) . arg ( filepath ) ) ;
2022-09-11 16:19:13 +01:00
QMessageBox messageBox ;
messageBox . setText ( " Failed to save prefabs file! " ) ;
messageBox . setInformativeText ( QString ( " Could not open \" %1 \" for writing " ) . arg ( filepath ) ) ;
messageBox . setDetailedText ( " Any created prefabs will not be available next time Porymap is opened. Please fix your prefabs_filepath in the Porymap project config file. " ) ;
messageBox . setIcon ( QMessageBox : : Warning ) ;
messageBox . exec ( ) ;
2022-09-10 02:48:08 +01:00
return ;
}
OrderedJson : : array prefabsArr ;
for ( auto item : this - > items ) {
OrderedJson : : object prefabObj ;
prefabObj [ " name " ] = item . name ;
prefabObj [ " width " ] = item . selection . dimensions . x ( ) ;
prefabObj [ " height " ] = item . selection . dimensions . y ( ) ;
prefabObj [ " primary_tileset " ] = item . primaryTileset ;
prefabObj [ " secondary_tileset " ] = item . secondaryTileset ;
OrderedJson : : array metatiles ;
for ( int y = 0 ; y < item . selection . dimensions . y ( ) ; y + + ) {
for ( int x = 0 ; x < item . selection . dimensions . x ( ) ; x + + ) {
int index = y * item . selection . dimensions . x ( ) + x ;
auto metatileItem = item . selection . metatileItems . at ( index ) ;
if ( metatileItem . enabled ) {
OrderedJson : : object metatileObj ;
metatileObj [ " x " ] = x ;
metatileObj [ " y " ] = y ;
metatileObj [ " metatile_id " ] = metatileItem . metatileId ;
2022-09-11 16:19:13 +01:00
if ( item . selection . hasCollision & & index < item . selection . collisionItems . size ( ) ) {
auto collisionItem = item . selection . collisionItems . at ( index ) ;
metatileObj [ " collision " ] = collisionItem . collision ;
metatileObj [ " elevation " ] = collisionItem . elevation ;
}
2022-09-10 02:48:08 +01:00
metatiles . push_back ( metatileObj ) ;
}
}
}
prefabObj [ " metatiles " ] = metatiles ;
prefabsArr . push_back ( prefabObj ) ;
}
OrderedJson prefabJson ( prefabsArr ) ;
OrderedJsonDoc jsonDoc ( & prefabJson ) ;
jsonDoc . dump ( & prefabsFile ) ;
prefabsFile . close ( ) ;
}
2022-09-06 02:51:31 +01:00
QList < PrefabItem > Prefab : : getPrefabsForTilesets ( QString primaryTileset , QString secondaryTileset ) {
QList < PrefabItem > filteredPrefabs ;
2022-09-10 15:14:52 +01:00
// Prefabs are only valid for the tileset(s) from which they were created.
// If, say, no metatiles in the prefab are from the primary tileset, then
// any primary tileset is valid for that prefab.
2022-09-06 02:51:31 +01:00
for ( auto item : this - > items ) {
if ( ( item . primaryTileset . isEmpty ( ) | | item . primaryTileset = = primaryTileset ) & &
( item . secondaryTileset . isEmpty ( ) | | item . secondaryTileset = = secondaryTileset ) ) {
filteredPrefabs . append ( item ) ;
}
}
return filteredPrefabs ;
}
2022-09-08 02:38:30 +01:00
void Prefab : : initPrefabUI ( MetatileSelector * selector , QWidget * prefabWidget , QLabel * emptyPrefabLabel , Map * map ) {
this - > selector = selector ;
this - > prefabWidget = prefabWidget ;
this - > emptyPrefabLabel = emptyPrefabLabel ;
2022-09-06 02:51:31 +01:00
this - > loadPrefabs ( ) ;
2022-09-08 02:38:30 +01:00
this - > updatePrefabUi ( map ) ;
}
2022-09-10 15:14:52 +01:00
// This function recreates the UI state for the prefab tab.
// We completely delete all the prefab widgets, and recreate new widgets
// from the relevant list of prefab items.
// This is not very efficient, but it gets the job done.
2022-09-08 02:38:30 +01:00
void Prefab : : updatePrefabUi ( Map * map ) {
2022-09-10 02:09:28 +01:00
if ( ! this - > selector )
return ;
2022-09-08 02:38:30 +01:00
// Cleanup the PrefabFrame to have a clean slate.
auto layout = this - > prefabWidget - > layout ( ) ;
while ( layout & & layout - > count ( ) > 1 ) {
auto child = layout - > takeAt ( 1 ) ;
if ( child - > widget ( ) ) {
delete child - > widget ( ) ;
}
delete child ;
}
QList < PrefabItem > prefabs = this - > getPrefabsForTilesets ( map - > layout - > tileset_primary_label , map - > layout - > tileset_secondary_label ) ;
2022-09-06 02:51:31 +01:00
if ( prefabs . isEmpty ( ) ) {
emptyPrefabLabel - > setVisible ( true ) ;
return ;
}
emptyPrefabLabel - > setVisible ( false ) ;
2022-09-10 15:14:52 +01:00
// Add all the prefab widgets to this wrapper parent frame because directly adding them
// to the main prefab widget will cause the UI to immediately render each new widget, which is slow.
// This avoids unpleasant UI lag when a large number of prefabs are present.
QFrame * parentFrame = new QFrame ( ) ;
parentFrame - > setLayout ( new QVBoxLayout ( ) ) ;
2022-09-10 01:37:25 +01:00
for ( auto item : prefabs ) {
2022-09-06 02:51:31 +01:00
PrefabFrame * frame = new PrefabFrame ( ) ;
frame - > ui - > label_Name - > setText ( item . name ) ;
auto scene = new QGraphicsScene ;
scene - > addPixmap ( drawMetatileSelection ( item . selection , map ) ) ;
scene - > setSceneRect ( scene - > itemsBoundingRect ( ) ) ;
frame - > ui - > graphicsView_Prefab - > setScene ( scene ) ;
frame - > ui - > graphicsView_Prefab - > setFixedSize ( scene - > itemsBoundingRect ( ) . width ( ) + 2 ,
scene - > itemsBoundingRect ( ) . height ( ) + 2 ) ;
2022-09-10 15:14:52 +01:00
parentFrame - > layout ( ) - > addWidget ( frame ) ;
2022-09-10 01:37:25 +01:00
// Clicking on the prefab graphics item selects it for painting.
2022-09-28 00:43:17 +01:00
QObject : : connect ( frame - > ui - > graphicsView_Prefab , & ClickableGraphicsView : : clicked , [ this , item , frame ] ( QMouseEvent * event ) {
2022-09-24 17:16:03 +01:00
selector - > setPrefabSelection ( item . selection ) ;
2022-09-28 00:43:17 +01:00
QToolTip : : showText ( frame - > ui - > graphicsView_Prefab - > mapToGlobal ( event - > pos ( ) ) , " Selected prefab! " , nullptr , { } , 1000 ) ;
2022-09-07 02:42:19 +01:00
} ) ;
2022-09-10 01:37:25 +01:00
// Clicking the delete button removes it from the list of known prefabs and updates the UI.
QObject : : connect ( frame - > ui - > pushButton_DeleteItem , & QPushButton : : clicked , [ this , item , map ] ( ) {
for ( int i = 0 ; i < this - > items . size ( ) ; i + + ) {
if ( this - > items [ i ] . id = = item . id ) {
2022-09-10 02:53:07 +01:00
QMessageBox msgBox ;
msgBox . setText ( " Confirm Prefab Deletion " ) ;
if ( this - > items [ i ] . name . isEmpty ( ) ) {
msgBox . setInformativeText ( " Are you sure you want to delete this prefab? " ) ;
} else {
msgBox . setInformativeText ( QString ( " Are you sure you want to delete prefab \" %1 \" ? " ) . arg ( this - > items [ i ] . name ) ) ;
}
QPushButton * deleteButton = msgBox . addButton ( " Delete " , QMessageBox : : DestructiveRole ) ;
msgBox . addButton ( QMessageBox : : Cancel ) ;
msgBox . setDefaultButton ( QMessageBox : : Cancel ) ;
msgBox . exec ( ) ;
if ( msgBox . clickedButton ( ) = = deleteButton ) {
this - > items . removeAt ( i ) ;
this - > savePrefabs ( ) ;
this - > updatePrefabUi ( map ) ;
}
2022-09-10 01:37:25 +01:00
break ;
}
}
} ) ;
2022-09-06 02:51:31 +01:00
}
2022-09-10 15:14:52 +01:00
prefabWidget - > layout ( ) - > addWidget ( parentFrame ) ;
auto verticalSpacer = new QSpacerItem ( 0 , 0 , QSizePolicy : : Ignored , QSizePolicy : : Expanding ) ;
prefabWidget - > layout ( ) - > addItem ( verticalSpacer ) ;
2022-09-06 02:51:31 +01:00
}
2022-09-08 02:38:30 +01:00
void Prefab : : addPrefab ( MetatileSelection selection , Map * map , QString name ) {
2022-09-10 15:14:52 +01:00
// First, determine which tilesets are actually used in this new prefab,
// based on the metatile ids.
2022-09-10 02:09:28 +01:00
bool usesPrimaryTileset = false ;
bool usesSecondaryTileset = false ;
for ( auto metatile : selection . metatileItems ) {
if ( ! metatile . enabled )
continue ;
if ( metatile . metatileId < Project : : getNumMetatilesPrimary ( ) ) {
usesPrimaryTileset = true ;
} else if ( metatile . metatileId < Project : : getNumMetatilesTotal ( ) ) {
usesSecondaryTileset = true ;
}
}
2022-09-10 01:37:25 +01:00
this - > items . append ( PrefabItem {
QUuid : : createUuid ( ) ,
name ,
2022-09-10 02:09:28 +01:00
usesPrimaryTileset ? map - > layout - > tileset_primary_label : " " ,
usesSecondaryTileset ? map - > layout - > tileset_secondary_label : " " ,
2022-09-10 01:37:25 +01:00
selection
} ) ;
2022-09-10 02:48:08 +01:00
this - > savePrefabs ( ) ;
2022-09-08 02:38:30 +01:00
this - > updatePrefabUi ( map ) ;
}
2023-08-31 18:47:13 +01:00
bool Prefab : : tryImportDefaultPrefabs ( QWidget * parent , BaseGameVersion version , QString filepath ) {
2022-09-24 21:28:31 +01:00
// Ensure we have default prefabs for the project's game version.
if ( version ! = BaseGameVersion : : pokeruby & & version ! = BaseGameVersion : : pokeemerald & & version ! = BaseGameVersion : : pokefirered )
2023-08-31 18:47:13 +01:00
return false ;
if ( filepath . isEmpty ( ) )
filepath = defaultFilepath ;
// Get the absolute filepath for writing/warnings
QString absFilepath ;
QFileInfo fileInfo ( filepath ) ;
if ( fileInfo . suffix ( ) . isEmpty ( ) )
filepath + = " .json " ;
if ( fileInfo . isRelative ( ) ) {
absFilepath = QDir : : cleanPath ( projectConfig . getProjectDir ( ) + QDir : : separator ( ) + filepath ) ;
} else {
absFilepath = filepath ;
}
2022-09-24 21:28:31 +01:00
2023-08-31 18:47:13 +01:00
// The warning message when importing defaults changes if there's a pre-existing file.
QString fileWarning ;
if ( ! QFileInfo : : exists ( absFilepath ) ) {
fileWarning = QString ( " This will create a file called '%1' " ) . arg ( absFilepath ) ;
} else {
fileWarning = QString ( " This will overwrite any existing prefabs in '%1' " ) . arg ( absFilepath ) ;
}
2022-09-24 21:28:31 +01:00
// Display a dialog box to the user, asking if the default prefabs should be imported
// into their project.
QMessageBox : : StandardButton prompt =
2023-08-31 18:47:13 +01:00
QMessageBox : : question ( parent ,
2022-09-24 21:28:31 +01:00
" Import Default Prefabs " ,
2023-08-31 18:47:13 +01:00
QString ( " Would you like to import the default prefabs for %1? %2. " )
. arg ( projectConfig . getBaseGameVersionString ( version ) )
. arg ( fileWarning ) ,
2022-09-24 21:28:31 +01:00
QMessageBox : : Yes | QMessageBox : : No ) ;
2023-08-31 18:47:13 +01:00
bool acceptedImport = ( prompt = = QMessageBox : : Yes ) ;
if ( acceptedImport ) {
2022-09-24 21:28:31 +01:00
// Sets up the default prefabs.json filepath.
2023-08-31 18:47:13 +01:00
projectConfig . setPrefabFilepath ( filepath ) ;
QFile prefabsFile ( absFilepath ) ;
2022-09-24 21:28:31 +01:00
if ( ! prefabsFile . open ( QIODevice : : WriteOnly ) ) {
projectConfig . setPrefabFilepath ( QString ( ) ) ;
2023-08-31 18:47:13 +01:00
logError ( QString ( " Error: Could not open %1 for writing " ) . arg ( absFilepath ) ) ;
QMessageBox messageBox ( parent ) ;
2022-09-24 21:28:31 +01:00
messageBox . setText ( " Failed to import default prefabs file! " ) ;
2023-08-31 18:47:13 +01:00
messageBox . setInformativeText ( QString ( " Could not open \" %1 \" for writing " ) . arg ( absFilepath ) ) ;
2022-09-24 21:28:31 +01:00
messageBox . setIcon ( QMessageBox : : Warning ) ;
messageBox . exec ( ) ;
2023-08-31 18:47:13 +01:00
return false ;
2022-09-24 21:28:31 +01:00
}
ParseUtil parser ;
QString content ;
switch ( version ) {
case BaseGameVersion : : pokeruby :
content = parser . readTextFile ( " :/text/prefabs_default_ruby.json " ) ;
break ;
case BaseGameVersion : : pokefirered :
content = parser . readTextFile ( " :/text/prefabs_default_firered.json " ) ;
break ;
case BaseGameVersion : : pokeemerald :
content = parser . readTextFile ( " :/text/prefabs_default_emerald.json " ) ;
break ;
}
prefabsFile . write ( content . toUtf8 ( ) ) ;
prefabsFile . close ( ) ;
2022-10-09 20:36:12 +01:00
this - > loadPrefabs ( ) ;
2022-09-24 21:28:31 +01:00
}
projectConfig . setPrefabImportPrompted ( true ) ;
2023-08-31 18:47:13 +01:00
return acceptedImport ;
2022-09-24 21:28:31 +01:00
}
2022-09-06 02:51:31 +01:00
Prefab prefab ;