2022-10-13 01:51:55 +04:00
/*
* validator . cpp , part of VCMI engine
*
* Authors : listed in file AUTHORS in main folder
*
* License : GNU General Public License v2 .0 or later
* Full text of license available in license . txt file , in main folder
*
*/
2022-09-18 03:23:17 +04:00
# include "StdInc.h"
# include "validator.h"
2023-04-17 03:01:29 +04:00
# include "mapcontroller.h"
2022-09-18 03:23:17 +04:00
# include "ui_validator.h"
2024-10-11 16:30:16 +00:00
# include "../lib/entities/hero/CHero.h"
2023-05-24 02:05:59 +03:00
# include "../lib/mapping/CMap.h"
2022-09-18 03:23:17 +04:00
# include "../lib/mapObjects/MapObjects.h"
2023-07-30 20:12:25 +03:00
# include "../lib/modding/CModHandler.h"
2024-11-09 20:29:07 +00:00
# include "../lib/modding/ModDescription.h"
2023-09-04 03:33:01 +04:00
# include "../lib/spells/CSpellHandler.h"
2022-09-18 03:23:17 +04:00
Validator : : Validator ( const CMap * map , QWidget * parent ) :
QDialog ( parent ) ,
ui ( new Ui : : Validator )
{
ui - > setupUi ( this ) ;
2025-02-09 19:19:36 +01:00
screenGeometry = QApplication : : primaryScreen ( ) - > availableGeometry ( ) ;
2025-01-06 13:39:14 +01:00
setWindowFlags ( Qt : : Dialog | Qt : : WindowTitleHint | Qt : : WindowCloseButtonHint ) ;
2025-02-09 19:19:36 +01:00
showValidationResults ( map ) ;
2022-09-18 03:23:17 +04:00
}
Validator : : ~ Validator ( )
{
delete ui ;
}
2024-08-20 00:53:48 +02:00
std : : set < Validator : : Issue > Validator : : validate ( const CMap * map )
2022-09-18 03:23:17 +04:00
{
2024-08-20 00:53:48 +02:00
std : : set < Validator : : Issue > issues ;
2022-09-18 03:23:17 +04:00
if ( ! map )
{
2024-08-20 00:53:48 +02:00
issues . insert ( { tr ( " Map is not loaded " ) , true } ) ;
2022-09-18 03:23:17 +04:00
return issues ;
}
try
{
//check player settings
int hplayers = 0 ;
int cplayers = 0 ;
2024-08-19 00:21:11 +02:00
std : : map < PlayerColor , int > amountOfTowns ;
std : : map < PlayerColor , int > amountOfHeroes ;
2022-09-18 03:23:17 +04:00
for ( int i = 0 ; i < map - > players . size ( ) ; + + i )
{
auto & p = map - > players [ i ] ;
2024-08-20 00:53:48 +02:00
if ( p . canAnyonePlay ( ) )
amountOfTowns [ PlayerColor ( i ) ] = 0 ;
2022-09-18 03:23:17 +04:00
if ( p . canComputerPlay )
+ + cplayers ;
if ( p . canHumanPlay )
+ + hplayers ;
if ( p . allowedFactions . empty ( ) )
2024-08-20 00:53:48 +02:00
issues . insert ( { tr ( " No factions allowed for player %1 " ) . arg ( i ) , true } ) ;
2022-09-18 03:23:17 +04:00
}
if ( hplayers + cplayers = = 0 )
2024-08-20 00:53:48 +02:00
issues . insert ( { tr ( " No players allowed to play this map " ) , true } ) ;
2022-09-18 03:23:17 +04:00
if ( hplayers + cplayers = = 1 )
2024-08-20 00:53:48 +02:00
issues . insert ( { tr ( " Map is allowed for one player and cannot be started " ) , true } ) ;
2022-09-18 03:23:17 +04:00
if ( ! hplayers )
2024-08-20 00:53:48 +02:00
issues . insert ( { tr ( " No human players allowed to play this map " ) , true } ) ;
2022-09-18 03:23:17 +04:00
2024-08-20 00:53:48 +02:00
std : : set < const CHero * > allHeroesOnMap ; //used to find hero duplicated
2023-04-22 20:08:52 +04:00
2022-09-18 03:23:17 +04:00
//checking all objects in the map
for ( auto o : map - > objects )
{
//owners for objects
if ( o - > getOwner ( ) = = PlayerColor : : UNFLAGGABLE )
{
2025-03-13 19:42:57 +00:00
if ( o - > asOwnable ( ) )
2022-09-18 03:23:17 +04:00
{
2025-03-13 19:42:57 +00:00
issues . insert ( { tr ( " Ownable object %1 is UNFLAGGABLE but must have NEUTRAL or player owner " ) . arg ( o - > instanceName . c_str ( ) ) , true } ) ;
2022-09-18 03:23:17 +04:00
}
}
2022-09-20 02:38:10 +04:00
if ( o - > getOwner ( ) ! = PlayerColor : : NEUTRAL & & o - > getOwner ( ) . getNum ( ) < map - > players . size ( ) )
{
if ( ! map - > players [ o - > getOwner ( ) . getNum ( ) ] . canAnyonePlay ( ) )
2024-08-20 00:53:48 +02:00
issues . insert ( { tr ( " Object %1 is assigned to non-playable player %2 " ) . arg ( o - > instanceName . c_str ( ) , o - > getOwner ( ) . toString ( ) . c_str ( ) ) , true } ) ;
2022-09-20 02:38:10 +04:00
}
2024-08-19 00:21:11 +02:00
//count towns
2024-08-20 00:53:48 +02:00
if ( auto * ins = dynamic_cast < CGTownInstance * > ( o . get ( ) ) )
2022-09-18 03:23:17 +04:00
{
2024-08-19 00:21:11 +02:00
+ + amountOfTowns [ ins - > getOwner ( ) ] ;
2022-09-18 03:23:17 +04:00
}
2024-08-19 00:21:11 +02:00
//checking and counting heroes and prisons
2024-08-20 00:53:48 +02:00
if ( auto * ins = dynamic_cast < CGHeroInstance * > ( o . get ( ) ) )
2022-09-18 03:23:17 +04:00
{
if ( ins - > ID = = Obj : : PRISON )
{
if ( ins - > getOwner ( ) ! = PlayerColor : : NEUTRAL )
2024-08-20 00:53:48 +02:00
issues . insert ( { tr ( " Prison %1 must be a NEUTRAL " ) . arg ( ins - > instanceName . c_str ( ) ) , true } ) ;
2022-09-18 03:23:17 +04:00
}
else
{
2024-08-19 00:21:11 +02:00
if ( ins - > getOwner ( ) = = PlayerColor : : NEUTRAL )
2024-08-20 00:53:48 +02:00
issues . insert ( { tr ( " Hero %1 must have an owner " ) . arg ( ins - > instanceName . c_str ( ) ) , true } ) ;
2024-08-19 00:21:11 +02:00
+ + amountOfHeroes [ ins - > getOwner ( ) ] ;
2022-09-18 03:23:17 +04:00
}
2024-10-05 19:37:52 +00:00
if ( ins - > getHeroTypeID ( ) . hasValue ( ) )
2022-09-18 03:23:17 +04:00
{
2024-10-05 19:37:52 +00:00
if ( map - > allowedHeroes . count ( ins - > getHeroTypeID ( ) ) = = 0 )
issues . insert ( { tr ( " Hero %1 is prohibited by map settings " ) . arg ( ins - > getHeroType ( ) - > getNameTranslated ( ) . c_str ( ) ) , false } ) ;
2023-04-22 20:08:52 +04:00
2024-10-05 19:37:52 +00:00
if ( ! allHeroesOnMap . insert ( ins - > getHeroType ( ) ) . second )
issues . insert ( { tr ( " Hero %1 has duplicate on map " ) . arg ( ins - > getHeroType ( ) - > getNameTranslated ( ) . c_str ( ) ) , false } ) ;
2022-09-18 03:23:17 +04:00
}
2023-09-10 16:57:41 +02:00
else if ( ins - > ID ! = Obj : : RANDOM_HERO )
2024-08-20 00:53:48 +02:00
issues . insert ( { tr ( " Hero %1 has an empty type and must be removed " ) . arg ( ins - > instanceName . c_str ( ) ) , true } ) ;
2022-09-18 03:23:17 +04:00
}
//checking for arts
2024-08-20 00:53:48 +02:00
if ( auto * ins = dynamic_cast < CGArtifact * > ( o . get ( ) ) )
2022-09-18 03:23:17 +04:00
{
if ( ins - > ID = = Obj : : SPELL_SCROLL )
{
2025-03-18 20:38:12 +00:00
if ( ins - > getArtifactInstance ( ) )
2022-09-18 03:23:17 +04:00
{
2025-03-18 20:38:12 +00:00
if ( map - > allowedSpells . count ( ins - > getArtifactInstance ( ) - > getScrollSpellID ( ) ) = = 0 )
issues . insert ( { tr ( " Spell scroll %1 is prohibited by map settings " ) . arg ( ins - > getArtifactInstance ( ) - > getScrollSpellID ( ) . toEntity ( LIBRARY - > spells ( ) ) - > getNameTranslated ( ) . c_str ( ) ) , false } ) ;
2022-09-18 03:23:17 +04:00
}
else
2024-09-13 15:02:09 +03:00
issues . insert ( { tr ( " Spell scroll %1 doesn't have instance assigned and must be removed " ) . arg ( ins - > instanceName . c_str ( ) ) , true } ) ;
2022-09-18 03:23:17 +04:00
}
else
{
2025-03-18 20:38:12 +00:00
if ( ins - > ID = = Obj : : ARTIFACT & & map - > allowedArtifact . count ( ins - > getArtifactType ( ) ) = = 0 )
2022-09-18 03:23:17 +04:00
{
2024-09-13 15:02:09 +03:00
issues . insert ( { tr ( " Artifact %1 is prohibited by map settings " ) . arg ( ins - > getObjectName ( ) . c_str ( ) ) , false } ) ;
2022-09-18 03:23:17 +04:00
}
}
}
}
//verification of starting towns
2024-08-20 00:53:48 +02:00
for ( const auto & [ player , counter ] : amountOfTowns )
{
if ( counter = = 0 )
{
// FIXME: heroesNames are empty even though heroes are on the map
// if(map->players[playerTCounter.first].heroesNames.empty())
if ( amountOfHeroes . count ( player ) = = 0 )
issues . insert ( { tr ( " Player %1 has no towns and heroes assigned " ) . arg ( player + 1 ) , true } ) ;
else
issues . insert ( { tr ( " Player %1 doesn't have any starting town " ) . arg ( player + 1 ) , false } ) ;
}
}
2022-09-18 03:23:17 +04:00
//verification of map name and description
if ( map - > name . empty ( ) )
2024-08-20 00:53:48 +02:00
issues . insert ( { tr ( " Map name is not specified " ) , false } ) ;
2022-09-18 03:23:17 +04:00
if ( map - > description . empty ( ) )
2024-08-20 00:53:48 +02:00
issues . insert ( { tr ( " Map description is not specified " ) , false } ) ;
2023-04-17 03:01:29 +04:00
2025-02-09 19:19:36 +01:00
//verification for mods
2023-04-17 03:01:29 +04:00
for ( auto & mod : MapController : : modAssessmentMap ( * map ) )
{
if ( ! map - > mods . count ( mod . first ) )
2025-05-13 12:51:31 +02:00
issues . insert ( { MapController : : modMissingMessage ( mod . second ) , true } ) ;
2023-04-17 03:01:29 +04:00
}
2022-09-18 03:23:17 +04:00
}
catch ( const std : : exception & e )
{
2024-08-20 00:53:48 +02:00
issues . insert ( { tr ( " Exception occurs during validation: %1 " ) . arg ( e . what ( ) ) , true } ) ;
2022-09-18 03:23:17 +04:00
}
catch ( . . . )
{
2024-08-20 00:53:48 +02:00
issues . insert ( { tr ( " Unknown exception occurs during validation " ) , true } ) ;
2022-09-18 03:23:17 +04:00
}
return issues ;
}
2025-02-09 19:19:36 +01:00
void Validator : : showValidationResults ( const CMap * map )
{
show ( ) ;
setAttribute ( Qt : : WA_DeleteOnClose ) ;
ui - > listWidget - > setItemDelegate ( new ValidatorItemDelegate ( ui - > listWidget ) ) ;
for ( auto const & issue : Validator : : validate ( map ) )
{
2025-04-16 23:44:17 +02:00
auto * item = new QListWidgetItem ( QIcon ( issue . critical ? " :/icons/mod-delete.png " : " :/icons/mod-update.png " ) ,
issue . message , ui - > listWidget ) ;
2025-02-09 19:19:36 +01:00
ui - > listWidget - > addItem ( item ) ;
}
if ( ui - > listWidget - > count ( ) = = 0 )
{
2025-05-13 12:51:31 +02:00
QPixmap greenTick ( " :/icons/mod-enabled.png " ) ;
2025-02-09 19:19:36 +01:00
QString validMessage = tr ( " The map is valid and has no issues. " ) ;
auto * item = new QListWidgetItem ( QIcon ( greenTick ) , validMessage , ui - > listWidget ) ;
ui - > listWidget - > addItem ( item ) ;
}
ui - > listWidget - > updateGeometry ( ) ;
adjustWindowSize ( ) ;
}
void Validator : : adjustWindowSize ( )
{
const int minWidth = 350 ;
const int minHeight = 50 ;
const int padding = 30 ; // reserved space for eventual scrollbars
const int screenMarginVertical = 300 ;
const int screenMarginHorizontal = 350 ;
int contentHeight = minHeight ;
int contentWidth = minWidth ;
QStyleOptionViewItem option ;
option . initFrom ( ui - > listWidget ) ;
int listWidgetWidth = ui - > listWidget - > viewport ( ) - > width ( ) ;
for ( int i = 0 ; i < ui - > listWidget - > count ( ) ; + + i )
{
option . rect = QRect ( 0 , 0 , listWidgetWidth , 0 ) ;
auto itemSize = ui - > listWidget - > itemDelegate ( ) - > sizeHint ( option , ui - > listWidget - > model ( ) - > index ( i , 0 ) ) ;
contentHeight + = itemSize . height ( ) ;
contentWidth = qMax ( contentWidth , itemSize . width ( ) ) ;
}
int screenWidth = screenGeometry . width ( ) ;
int screenHeight = screenGeometry . height ( ) ;
int finalWidth = qMin ( contentWidth + padding , screenWidth - screenMarginHorizontal ) ;
int finalHeight = qMin ( contentHeight + padding , screenHeight - screenMarginVertical ) ;
QWidget * parentWidget = ui - > listWidget - > parentWidget ( ) ;
if ( parentWidget )
{
2025-04-05 18:13:24 +02:00
parentWidget - > setMinimumWidth ( finalWidth + padding ) ;
parentWidget - > setMinimumHeight ( finalHeight + padding ) ;
2025-02-09 19:19:36 +01:00
}
ui - > listWidget - > resize ( finalWidth , finalHeight ) ;
move ( ( screenWidth - finalWidth ) / 2 , ( screenHeight - finalHeight ) / 2 ) ;
}
void ValidatorItemDelegate : : paint ( QPainter * painter , const QStyleOptionViewItem & option , const QModelIndex & index ) const
{
painter - > save ( ) ;
QStyleOptionViewItem opt ( option ) ;
QFontMetrics metrics ( option . fontMetrics ) ;
initStyleOption ( & opt , index ) ;
const QRect iconRect = option . rect . adjusted ( iconPadding , iconPadding , 0 , 0 ) ;
const QRect textRect = option . rect . adjusted ( offsetForIcon , 0 , - textPaddingRight , 0 ) ;
if ( ! opt . icon . isNull ( ) )
{
opt . icon . paint ( painter , iconRect , Qt : : AlignTop | Qt : : AlignLeft ) ;
}
QTextOption textOption ;
int textWidth = metrics . horizontalAdvance ( opt . text ) ;
if ( textWidth + offsetForIcon + textPaddingRight > screenGeometry . width ( ) - screenMargin )
{
textOption . setWrapMode ( QTextOption : : WordWrap ) ;
}
painter - > drawText ( textRect , opt . text , textOption ) ;
painter - > restore ( ) ;
}
QSize ValidatorItemDelegate : : sizeHint ( const QStyleOptionViewItem & option , const QModelIndex & index ) const
{
QFontMetrics metrics ( option . fontMetrics ) ;
QString text = index . data ( Qt : : DisplayRole ) . toString ( ) ;
QStringList lines = text . split ( ' \n ' ) ;
int textWidth = minItemWidth ;
int requiredHeight = 0 ;
for ( auto line : lines )
textWidth = std : : max ( metrics . horizontalAdvance ( line ) , textWidth ) ;
requiredHeight = qMax ( requiredHeight , lines . size ( ) * metrics . height ( ) ) ;
int finalWidth = qMax ( textWidth + offsetForIcon , minItemWidth ) ;
finalWidth = qMin ( finalWidth , screenGeometry . width ( ) - screenMargin - offsetForIcon ) ;
QRect textBoundingRect = metrics . boundingRect ( QRect ( 0 , 0 , finalWidth , 0 ) ,
Qt : : TextWordWrap , text ) ;
int finalHeight = qMax ( textBoundingRect . height ( ) + itemPaddingBottom , requiredHeight ) ;
return QSize ( finalWidth , finalHeight ) ;
}