You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

662 lines
21 KiB

#include "selhero.h"
#include <algorithm>
#include <chrono>
#include <random>
5 years ago
#include <fmt/format.h>
#include "DiabloUI/diabloui.h"
#include "DiabloUI/dialogs.h"
#include "DiabloUI/scrollbar.h"
#include "DiabloUI/selgame.h"
#include "DiabloUI/selok.h"
#include "DiabloUI/selyesno.h"
#include "control.h"
#include "options.h"
#include "pfile.h"
#include "utils/language.h"
#ifdef __3DS__
#include "platform/ctr/keyboard.h"
#endif
namespace devilution {
bool selhero_endMenu;
bool selhero_isMultiPlayer;
bool (*gfnHeroInfo)(bool (*fninfofunc)(_uiheroinfo *));
bool (*gfnHeroCreate)(_uiheroinfo *);
void (*gfnHeroStats)(unsigned int, _uidefaultstats *);
namespace {
std::size_t selhero_SaveCount = 0;
_uiheroinfo selhero_heros[MAX_CHARACTERS];
_uiheroinfo selhero_heroInfo;
const size_t MaxViewportItems = 6;
char textStats[5][4];
char title[32];
_selhero_selections selhero_result;
bool selhero_navigateYesNo;
bool selhero_deleteEnabled;
std::vector<UiItemBase *> vecSelHeroDialog;
std::vector<UiListItem *> vecSelHeroDlgItems;
std::vector<UiItemBase *> vecSelDlgItems;
UiImage *SELHERO_DIALOG_HERO_IMG;
bool bUIElementsLoaded = false;
void SelheroListFocus(int value);
void SelheroListSelect(int value);
void SelheroListEsc();
void SelheroLoadFocus(int value);
void SelheroLoadSelect(int value);
void SelheroNameSelect(int value);
void SelheroNameEsc();
void SelheroClassSelectorFocus(int value);
void SelheroClassSelectorSelect(int value);
void SelheroClassSelectorEsc();
const char *SelheroGenerateName(HeroClass heroClass);
void SelheroUiFocusNavigationYesNo()
{
if (selhero_deleteEnabled)
UiFocusNavigationYesNo();
}
void SelheroFreeListItems()
{
for (auto pUIItem : vecSelHeroDlgItems) {
delete pUIItem;
}
vecSelHeroDlgItems.clear();
}
void SelheroFreeDlgItems()
{
for (auto pUIItem : vecSelDlgItems) {
delete pUIItem;
}
vecSelDlgItems.clear();
}
void SelheroFree()
{
ArtBackground.Unload();
for (auto pUIItem : vecSelHeroDialog) {
delete pUIItem;
}
vecSelHeroDialog.clear();
SelheroFreeDlgItems();
SelheroFreeListItems();
Fix memory leak in single-player difficulty selection Single-player difficulty selection was implemented in a very hacky way. Documents what's going on there and fixes a memory leak. Memory leaks that this fixes looked like this: ``` Direct leak of 64 byte(s) in 1 object(s) allocated from: #0 0x7f435b789f17 in operator new(unsigned long) (/lib/x86_64-linux-gnu/libasan.so.6+0xb1f17) #1 0x5567766c62b8 in dvl::selgame_GameSelection_Select(int) ../SourceX/DiabloUI/selgame.cpp:163 #2 0x5567766d8ee8 in dvl::selhero_Load_Select(int) ../SourceX/DiabloUI/selhero.cpp:464 #3 0x5567766d6ed1 in dvl::selhero_List_Select(int) ../SourceX/DiabloUI/selhero.cpp:325 #4 0x556776692683 in dvl::UiFocusNavigationSelect() ../SourceX/DiabloUI/diabloui.cpp:396 #5 0x55677669158e in HandleMenuAction ../SourceX/DiabloUI/diabloui.cpp:223 #6 0x5567766917b9 in dvl::UiFocusNavigation(SDL_Event*) ../SourceX/DiabloUI/diabloui.cpp:277 #7 0x556776695dff in dvl::UiPollAndRender() ../SourceX/DiabloUI/diabloui.cpp:626 #8 0x5567766d94ef in UiSelHeroDialog ../SourceX/DiabloUI/selhero.cpp:512 #9 0x5567766d997f in dvl::UiSelHeroSingDialog(int (*)(int (*)(dvl::_uiheroinfo*)), int (*)(dvl::_uiheroinfo*), int (*)(dvl::_uiheroinfo*), void (*)(unsigned int, dvl::_uidefaultstats*), int*, char (*) [16], int*) ../SourceX/DiabloUI/selhero.cpp:547 #10 0x556776a44f45 in mainmenu_select_hero_dialog ../Source/mainmenu.cpp:98 #11 0x5567765f9f15 in SNetInitializeProvider ../SourceX/storm/storm_net.cpp:102 #12 0x556776c996b9 in multi_init_single ../Source/multi.cpp:826 #13 0x556776c98b1e in NetInit ../Source/multi.cpp:770 #14 0x5567767d0c0b in StartGame ../Source/diablo.cpp:375 #15 0x556776a4493d in mainmenu_init_menu ../Source/mainmenu.cpp:45 #16 0x556776a44c05 in mainmenu_single_player ../Source/mainmenu.cpp:61 #17 0x556776a454a9 in mainmenu_loop ../Source/mainmenu.cpp:152 #18 0x5567767d2892 in DiabloMain ../Source/diablo.cpp:602 #19 0x5567766e3c69 in main ../SourceX/main.cpp:34 #20 0x7f435a69ecb1 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x28cb1) ```
5 years ago
UnloadScrollBar();
bUIElementsLoaded = false;
}
void SelheroSetStats()
{
SELHERO_DIALOG_HERO_IMG->m_frame = static_cast<int>(selhero_heroInfo.heroclass);
snprintf(textStats[0], sizeof(textStats[0]), "%i", selhero_heroInfo.level);
snprintf(textStats[1], sizeof(textStats[1]), "%i", selhero_heroInfo.strength);
snprintf(textStats[2], sizeof(textStats[2]), "%i", selhero_heroInfo.magic);
snprintf(textStats[3], sizeof(textStats[3]), "%i", selhero_heroInfo.dexterity);
snprintf(textStats[4], sizeof(textStats[4]), "%i", selhero_heroInfo.vitality);
}
std::size_t listOffset = 0;
UiArtTextButton *SELLIST_DIALOG_DELETE_BUTTON;
void SelheroUpdateViewportItems()
{
const std::size_t numViewportHeroes = std::min(selhero_SaveCount - listOffset, MaxViewportItems);
for (std::size_t i = 0; i < numViewportHeroes; i++) {
const std::size_t index = i + listOffset;
vecSelHeroDlgItems[i]->m_text = selhero_heros[index].name;
vecSelHeroDlgItems[i]->m_value = static_cast<int>(index);
}
if (numViewportHeroes < MaxViewportItems) {
vecSelHeroDlgItems[numViewportHeroes]->m_text = _("New Hero");
vecSelHeroDlgItems[numViewportHeroes]->m_value = static_cast<int>(selhero_SaveCount);
}
}
void SelheroScrollIntoView(std::size_t index)
{
std::size_t newOffset = listOffset;
if (index >= listOffset + MaxViewportItems)
newOffset = index - (MaxViewportItems - 1);
if (index < listOffset)
newOffset = index;
if (newOffset != listOffset) {
listOffset = newOffset;
SelheroUpdateViewportItems();
}
}
bool SelHeroGetHeroInfo(_uiheroinfo *pInfo)
{
selhero_heros[selhero_SaveCount] = *pInfo;
selhero_SaveCount++;
return true;
}
void SelheroListFocus(int value)
{
const auto index = static_cast<std::size_t>(value);
SelheroScrollIntoView(index);
int baseFlags = UIS_CENTER | UIS_BIG;
if (selhero_SaveCount != 0 && index < selhero_SaveCount) {
memcpy(&selhero_heroInfo, &selhero_heros[index], sizeof(selhero_heroInfo));
SelheroSetStats();
SELLIST_DIALOG_DELETE_BUTTON->m_iFlags = baseFlags | UIS_GOLD;
selhero_deleteEnabled = true;
return;
}
SELHERO_DIALOG_HERO_IMG->m_frame = static_cast<int>(enum_size<HeroClass>::value);
strncpy(textStats[0], "--", sizeof(textStats[0]) - 1);
strncpy(textStats[1], "--", sizeof(textStats[1]) - 1);
strncpy(textStats[2], "--", sizeof(textStats[2]) - 1);
strncpy(textStats[3], "--", sizeof(textStats[3]) - 1);
strncpy(textStats[4], "--", sizeof(textStats[4]) - 1);
SELLIST_DIALOG_DELETE_BUTTON->m_iFlags = baseFlags | UIS_DISABLED;
selhero_deleteEnabled = false;
}
bool SelheroListDeleteYesNo()
{
selhero_navigateYesNo = selhero_deleteEnabled;
return selhero_navigateYesNo;
}
void SelheroListSelect(int value)
{
if (static_cast<std::size_t>(value) == selhero_SaveCount) {
SelheroFreeDlgItems();
SDL_Rect rect1 = { (Sint16)(PANEL_LEFT + 264), (Sint16)(UI_OFFSET_Y + 211), 320, 33 };
vecSelDlgItems.push_back(new UiArtText(_("Choose Class"), rect1, UIS_CENTER | UIS_BIG));
SelheroFreeListItems();
int itemH = 33;
vecSelHeroDlgItems.push_back(new UiListItem(_("Warrior"), static_cast<int>(HeroClass::Warrior)));
vecSelHeroDlgItems.push_back(new UiListItem(_("Rogue"), static_cast<int>(HeroClass::Rogue)));
vecSelHeroDlgItems.push_back(new UiListItem(_("Sorcerer"), static_cast<int>(HeroClass::Sorcerer)));
if (gbIsHellfire) {
vecSelHeroDlgItems.push_back(new UiListItem(_("Monk"), static_cast<int>(HeroClass::Monk)));
}
if (gbBard || sgOptions.Gameplay.bTestBard) {
vecSelHeroDlgItems.push_back(new UiListItem(_("Bard"), static_cast<int>(HeroClass::Bard)));
}
if (gbBarbarian || sgOptions.Gameplay.bTestBarbarian) {
vecSelHeroDlgItems.push_back(new UiListItem(_("Barbarian"), static_cast<int>(HeroClass::Barbarian)));
}
if (vecSelHeroDlgItems.size() > 4)
itemH = 26;
int itemY = 246 + (176 - vecSelHeroDlgItems.size() * itemH) / 2;
vecSelDlgItems.push_back(new UiList(vecSelHeroDlgItems, PANEL_LEFT + 264, (UI_OFFSET_Y + itemY), 320, itemH, UIS_CENTER | UIS_MED | UIS_GOLD));
SDL_Rect rect2 = { (Sint16)(PANEL_LEFT + 279), (Sint16)(UI_OFFSET_Y + 429), 140, 35 };
vecSelDlgItems.push_back(new UiArtTextButton(_("OK"), &UiFocusNavigationSelect, rect2, UIS_CENTER | UIS_BIG | UIS_GOLD));
SDL_Rect rect3 = { (Sint16)(PANEL_LEFT + 429), (Sint16)(UI_OFFSET_Y + 429), 140, 35 };
vecSelDlgItems.push_back(new UiArtTextButton(_("Cancel"), &UiFocusNavigationEsc, rect3, UIS_CENTER | UIS_BIG | UIS_GOLD));
UiInitList(vecSelHeroDlgItems.size(), SelheroClassSelectorFocus, SelheroClassSelectorSelect, SelheroClassSelectorEsc, vecSelDlgItems);
memset(&selhero_heroInfo.name, 0, sizeof(selhero_heroInfo.name));
strncpy(title, _("New Single Player Hero"), sizeof(title) - 1);
if (selhero_isMultiPlayer) {
strncpy(title, _("New Multi Player Hero"), sizeof(title) - 1);
}
return;
}
if (selhero_heroInfo.hassaved) {
SelheroFreeDlgItems();
SDL_Rect rect1 = { (Sint16)(PANEL_LEFT + 264), (Sint16)(UI_OFFSET_Y + 211), 320, 33 };
vecSelDlgItems.push_back(new UiArtText(_("Save File Exists"), rect1, UIS_CENTER | UIS_BIG));
SelheroFreeListItems();
vecSelHeroDlgItems.push_back(new UiListItem(_("Load Game"), 0));
vecSelHeroDlgItems.push_back(new UiListItem(_("New Game"), 1));
vecSelDlgItems.push_back(new UiList(vecSelHeroDlgItems, PANEL_LEFT + 265, (UI_OFFSET_Y + 285), 320, 33, UIS_CENTER | UIS_MED | UIS_GOLD));
SDL_Rect rect2 = { (Sint16)(PANEL_LEFT + 279), (Sint16)(UI_OFFSET_Y + 427), 140, 35 };
vecSelDlgItems.push_back(new UiArtTextButton(_("OK"), &UiFocusNavigationSelect, rect2, UIS_CENTER | UIS_VCENTER | UIS_BIG | UIS_GOLD));
SDL_Rect rect3 = { (Sint16)(PANEL_LEFT + 429), (Sint16)(UI_OFFSET_Y + 427), 140, 35 };
vecSelDlgItems.push_back(new UiArtTextButton(_("Cancel"), &UiFocusNavigationEsc, rect3, UIS_CENTER | UIS_VCENTER | UIS_BIG | UIS_GOLD));
UiInitList(vecSelHeroDlgItems.size(), SelheroLoadFocus, SelheroLoadSelect, selhero_List_Init, vecSelDlgItems, true);
strncpy(title, _("Single Player Characters"), sizeof(title) - 1);
return;
}
SelheroLoadSelect(1);
}
void SelheroListEsc()
{
UiInitList_clear();
selhero_endMenu = true;
selhero_result = SELHERO_PREVIOUS;
}
void SelheroClassSelectorFocus(int value)
{
const auto heroClass = static_cast<HeroClass>(vecSelHeroDlgItems[value]->m_value);
_uidefaultstats defaults;
gfnHeroStats(static_cast<unsigned int>(heroClass), &defaults);
selhero_heroInfo.level = 1;
selhero_heroInfo.heroclass = heroClass;
selhero_heroInfo.strength = defaults.strength;
selhero_heroInfo.magic = defaults.magic;
selhero_heroInfo.dexterity = defaults.dexterity;
selhero_heroInfo.vitality = defaults.vitality;
SelheroSetStats();
}
bool ShouldPrefillHeroName()
{
#if defined __3DS__
return false;
#elif defined(PREFILL_PLAYER_NAME)
return true;
#else
return sgbControllerActive;
#endif
}
void SelheroClassSelectorSelect(int value)
{
auto hClass = static_cast<HeroClass>(vecSelHeroDlgItems[value]->m_value);
if (gbSpawned && (hClass == HeroClass::Rogue || hClass == HeroClass::Sorcerer || (hClass == HeroClass::Bard && hfbard_mpq == nullptr))) {
ArtBackground.Unload();
UiSelOkDialog(nullptr, _("The Rogue and Sorcerer are only available in the full retail version of Diablo. Visit https://www.gog.com/game/diablo to purchase."), false);
LoadBackgroundArt("ui_art\\selhero.pcx");
SelheroListSelect(selhero_SaveCount);
return;
}
strncpy(title, _("New Single Player Hero"), sizeof(title) - 1);
if (selhero_isMultiPlayer) {
strncpy(title, _("New Multi Player Hero"), sizeof(title) - 1);
}
memset(selhero_heroInfo.name, '\0', sizeof(selhero_heroInfo.name));
#if defined __3DS__
ctr_vkbdInput(_("Enter Name"), SelheroGenerateName(selhero_heroInfo.heroclass), selhero_heroInfo.name);
#endif
if (ShouldPrefillHeroName())
strncpy(selhero_heroInfo.name, SelheroGenerateName(selhero_heroInfo.heroclass), sizeof(selhero_heroInfo.name) - 1);
SelheroFreeDlgItems();
SDL_Rect rect1 = { (Sint16)(PANEL_LEFT + 264), (Sint16)(UI_OFFSET_Y + 211), 320, 33 };
vecSelDlgItems.push_back(new UiArtText(_("Enter Name"), rect1, UIS_CENTER | UIS_BIG));
SDL_Rect rect2 = { (Sint16)(PANEL_LEFT + 265), (Sint16)(UI_OFFSET_Y + 317), 320, 33 };
vecSelDlgItems.push_back(new UiEdit(selhero_heroInfo.name, 15, rect2, UIS_MED | UIS_GOLD));
SDL_Rect rect3 = { (Sint16)(PANEL_LEFT + 279), (Sint16)(UI_OFFSET_Y + 429), 140, 35 };
vecSelDlgItems.push_back(new UiArtTextButton(_("OK"), &UiFocusNavigationSelect, rect3, UIS_CENTER | UIS_BIG | UIS_GOLD));
SDL_Rect rect4 = { (Sint16)(PANEL_LEFT + 429), (Sint16)(UI_OFFSET_Y + 429), 140, 35 };
vecSelDlgItems.push_back(new UiArtTextButton(_("Cancel"), &UiFocusNavigationEsc, rect4, UIS_CENTER | UIS_BIG | UIS_GOLD));
UiInitList(0, nullptr, SelheroNameSelect, SelheroNameEsc, vecSelDlgItems);
}
void SelheroClassSelectorEsc()
{
SelheroFreeDlgItems();
SelheroFreeListItems();
if (selhero_SaveCount != 0) {
selhero_List_Init();
return;
}
SelheroListEsc();
}
void SelheroNameSelect(int value)
{
if (!UiValidPlayerName(selhero_heroInfo.name)) {
ArtBackground.Unload();
UiSelOkDialog(title, _("Invalid name. A name cannot contain spaces, reserved characters, or reserved words.\n"), false);
LoadBackgroundArt("ui_art\\selhero.pcx");
} else {
bool overwrite = true;
for (std::size_t i = 0; i < selhero_SaveCount; i++) {
if (strcasecmp(selhero_heros[i].name, selhero_heroInfo.name) == 0) {
ArtBackground.Unload();
char dialogText[256];
strncpy(dialogText, fmt::format(_(/* Error message when User tries to create multiple heros with the same name */ "Character already exists. Do you want to overwrite \"{:s}\"?"), selhero_heroInfo.name).c_str(), sizeof(dialogText));
overwrite = UiSelHeroYesNoDialog(title, dialogText);
LoadBackgroundArt("ui_art\\selhero.pcx");
break;
}
}
if (overwrite) {
if (gfnHeroCreate(&selhero_heroInfo)) {
SelheroLoadSelect(1);
return;
}
5 years ago
UiErrorOkDialog(_(/* TRANSLATORS: Error Message */ "Unable to create character."), vecSelDlgItems);
}
}
memset(selhero_heroInfo.name, '\0', sizeof(selhero_heroInfo.name));
SelheroClassSelectorSelect(0);
}
void SelheroNameEsc()
{
SelheroListSelect(selhero_SaveCount);
}
void SelheroLoadFocus(int value)
{
}
void SelheroLoadSelect(int value)
{
UiInitList_clear();
selhero_endMenu = true;
if (vecSelHeroDlgItems[value]->m_value == 0) {
selhero_result = SELHERO_CONTINUE;
return;
Fix memory leak in single-player difficulty selection Single-player difficulty selection was implemented in a very hacky way. Documents what's going on there and fixes a memory leak. Memory leaks that this fixes looked like this: ``` Direct leak of 64 byte(s) in 1 object(s) allocated from: #0 0x7f435b789f17 in operator new(unsigned long) (/lib/x86_64-linux-gnu/libasan.so.6+0xb1f17) #1 0x5567766c62b8 in dvl::selgame_GameSelection_Select(int) ../SourceX/DiabloUI/selgame.cpp:163 #2 0x5567766d8ee8 in dvl::selhero_Load_Select(int) ../SourceX/DiabloUI/selhero.cpp:464 #3 0x5567766d6ed1 in dvl::selhero_List_Select(int) ../SourceX/DiabloUI/selhero.cpp:325 #4 0x556776692683 in dvl::UiFocusNavigationSelect() ../SourceX/DiabloUI/diabloui.cpp:396 #5 0x55677669158e in HandleMenuAction ../SourceX/DiabloUI/diabloui.cpp:223 #6 0x5567766917b9 in dvl::UiFocusNavigation(SDL_Event*) ../SourceX/DiabloUI/diabloui.cpp:277 #7 0x556776695dff in dvl::UiPollAndRender() ../SourceX/DiabloUI/diabloui.cpp:626 #8 0x5567766d94ef in UiSelHeroDialog ../SourceX/DiabloUI/selhero.cpp:512 #9 0x5567766d997f in dvl::UiSelHeroSingDialog(int (*)(int (*)(dvl::_uiheroinfo*)), int (*)(dvl::_uiheroinfo*), int (*)(dvl::_uiheroinfo*), void (*)(unsigned int, dvl::_uidefaultstats*), int*, char (*) [16], int*) ../SourceX/DiabloUI/selhero.cpp:547 #10 0x556776a44f45 in mainmenu_select_hero_dialog ../Source/mainmenu.cpp:98 #11 0x5567765f9f15 in SNetInitializeProvider ../SourceX/storm/storm_net.cpp:102 #12 0x556776c996b9 in multi_init_single ../Source/multi.cpp:826 #13 0x556776c98b1e in NetInit ../Source/multi.cpp:770 #14 0x5567767d0c0b in StartGame ../Source/diablo.cpp:375 #15 0x556776a4493d in mainmenu_init_menu ../Source/mainmenu.cpp:45 #16 0x556776a44c05 in mainmenu_single_player ../Source/mainmenu.cpp:61 #17 0x556776a454a9 in mainmenu_loop ../Source/mainmenu.cpp:152 #18 0x5567767d2892 in DiabloMain ../Source/diablo.cpp:602 #19 0x5567766e3c69 in main ../SourceX/main.cpp:34 #20 0x7f435a69ecb1 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x28cb1) ```
5 years ago
}
if (!selhero_isMultiPlayer) {
// This is part of a dangerous hack to enable difficulty selection in single-player.
// FIXME: Dialogs should not refer to each other's variables.
// We disable `selhero_endMenu` and replace the background and art
// and the item list with the difficulty selection ones.
//
// This means selhero's render loop will render selgame's items,
// which happens to work because the render loops are similar.
selhero_endMenu = false;
SelheroFree();
LoadBackgroundArt("ui_art\\selgame.pcx");
selgame_GameSelection_Select(0);
}
selhero_result = SELHERO_NEW_DUNGEON;
}
const char *SelheroGenerateName(HeroClass heroClass)
{
static const char *const names[6][10] = {
{
// Warrior
"Aidan",
"Qarak",
"Born",
"Cathan",
"Halbu",
"Lenalas",
"Maximus",
"Vane",
"Myrdgar",
"Rothat",
},
{
// Rogue
"Moreina",
"Akara",
"Kashya",
"Flavie",
"Divo",
"Oriana",
"Iantha",
"Shikha",
"Basanti",
"Elexa",
},
{
// Sorcerer
"Jazreth",
"Drognan",
"Armin",
"Fauztin",
"Jere",
"Kazzulk",
"Ranslor",
"Sarnakyle",
"Valthek",
"Horazon",
},
{
// Monk
"Akyev",
"Dvorak",
"Kekegi",
"Kharazim",
"Mikulov",
"Shenlong",
"Vedenin",
"Vhalit",
"Vylnas",
"Zhota",
},
{
// Bard (uses Rogue names)
"Moreina",
"Akara",
"Kashya",
"Flavie",
"Divo",
"Oriana",
"Iantha",
"Shikha",
"Basanti",
"Elexa",
},
{
// Barbarian
"Alaric",
"Barloc",
"Egtheow",
"Guthlaf",
"Heorogar",
"Hrothgar",
"Oslaf",
"Qual-Kehk",
"Ragnar",
"Ulf",
},
};
int iRand = rand() % 10;
return names[static_cast<std::size_t>(heroClass) % 6][iRand];
}
} // namespace
void selhero_Init()
{
LoadBackgroundArt("ui_art\\selhero.pcx");
UiAddBackground(&vecSelHeroDialog);
UiAddLogo(&vecSelHeroDialog);
LoadScrollBar();
selhero_SaveCount = 0;
gfnHeroInfo(SelHeroGetHeroInfo);
std::reverse(selhero_heros, selhero_heros + selhero_SaveCount);
SelheroFreeDlgItems();
SDL_Rect rect1 = { (Sint16)(PANEL_LEFT + 24), (Sint16)(UI_OFFSET_Y + 161), 590, 35 };
vecSelHeroDialog.push_back(new UiArtText(title, rect1, UIS_CENTER | UIS_BIG));
SDL_Rect rect2 = { (Sint16)(PANEL_LEFT + 30), (Sint16)(UI_OFFSET_Y + 211), 180, 76 };
SELHERO_DIALOG_HERO_IMG = new UiImage(&ArtHero, static_cast<int>(enum_size<HeroClass>::value), rect2);
vecSelHeroDialog.push_back(SELHERO_DIALOG_HERO_IMG);
SDL_Rect rect3 = { (Sint16)(PANEL_LEFT + 39), (Sint16)(UI_OFFSET_Y + 323), 110, 21 };
vecSelHeroDialog.push_back(new UiArtText(_("Level:"), rect3, UIS_RIGHT));
SDL_Rect rect4 = { (Sint16)(PANEL_LEFT + 39), (Sint16)(UI_OFFSET_Y + 323), 110, 21 };
vecSelHeroDialog.push_back(new UiArtText(_("Level:"), rect4, UIS_RIGHT));
SDL_Rect rect5 = { (Sint16)(PANEL_LEFT + 159), (Sint16)(UI_OFFSET_Y + 323), 40, 21 };
vecSelHeroDialog.push_back(new UiArtText(textStats[0], rect5, UIS_CENTER));
SDL_Rect rect6 = { (Sint16)(PANEL_LEFT + 39), (Sint16)(UI_OFFSET_Y + 358), 110, 21 };
vecSelHeroDialog.push_back(new UiArtText(_("Strength:"), rect6, UIS_RIGHT));
SDL_Rect rect7 = { (Sint16)(PANEL_LEFT + 159), (Sint16)(UI_OFFSET_Y + 358), 40, 21 };
vecSelHeroDialog.push_back(new UiArtText(textStats[1], rect7, UIS_CENTER));
SDL_Rect rect8 = { (Sint16)(PANEL_LEFT + 39), (Sint16)(UI_OFFSET_Y + 380), 110, 21 };
vecSelHeroDialog.push_back(new UiArtText(_("Magic:"), rect8, UIS_RIGHT));
SDL_Rect rect9 = { (Sint16)(PANEL_LEFT + 159), (Sint16)(UI_OFFSET_Y + 380), 40, 21 };
vecSelHeroDialog.push_back(new UiArtText(textStats[2], rect9, UIS_CENTER));
SDL_Rect rect10 = { (Sint16)(PANEL_LEFT + 39), (Sint16)(UI_OFFSET_Y + 401), 110, 21 };
vecSelHeroDialog.push_back(new UiArtText(_("Dexterity:"), rect10, UIS_RIGHT));
SDL_Rect rect11 = { (Sint16)(PANEL_LEFT + 159), (Sint16)(UI_OFFSET_Y + 401), 40, 21 };
vecSelHeroDialog.push_back(new UiArtText(textStats[3], rect11, UIS_CENTER));
SDL_Rect rect12 = { (Sint16)(PANEL_LEFT + 39), (Sint16)(UI_OFFSET_Y + 422), 110, 21 };
vecSelHeroDialog.push_back(new UiArtText(_("Vitality:"), rect12, UIS_RIGHT));
SDL_Rect rect13 = { (Sint16)(PANEL_LEFT + 159), (Sint16)(UI_OFFSET_Y + 422), 40, 21 };
vecSelHeroDialog.push_back(new UiArtText(textStats[4], rect13, UIS_CENTER));
}
void selhero_List_Init()
{
listOffset = 0;
SelheroFreeDlgItems();
SDL_Rect rect1 = { (Sint16)(PANEL_LEFT + 264), (Sint16)(UI_OFFSET_Y + 211), 320, 33 };
vecSelDlgItems.push_back(new UiArtText(_("Select Hero"), rect1, UIS_CENTER | UIS_BIG));
SelheroFreeListItems();
const size_t numViewportHeroes = std::min(selhero_SaveCount + 1, MaxViewportItems);
for (std::size_t i = 0; i < numViewportHeroes; i++) {
vecSelHeroDlgItems.push_back(new UiListItem("", -1));
}
SelheroUpdateViewportItems();
vecSelDlgItems.push_back(new UiList(vecSelHeroDlgItems, PANEL_LEFT + 265, (UI_OFFSET_Y + 256), 320, 26, UIS_CENTER | UIS_MED | UIS_GOLD));
SDL_Rect rect2 = { (Sint16)(PANEL_LEFT + 585), (Sint16)(UI_OFFSET_Y + 244), 25, 178 };
auto *scrollBar = new UiScrollBar(&ArtScrollBarBackground, &ArtScrollBarThumb, &ArtScrollBarArrow, rect2);
vecSelDlgItems.push_back(scrollBar);
SDL_Rect rect3 = { (Sint16)(PANEL_LEFT + 239), (Sint16)(UI_OFFSET_Y + 429), 120, 35 };
vecSelDlgItems.push_back(new UiArtTextButton(_("OK"), &UiFocusNavigationSelect, rect3, UIS_CENTER | UIS_BIG | UIS_GOLD));
SDL_Rect rect4 = { (Sint16)(PANEL_LEFT + 364), (Sint16)(UI_OFFSET_Y + 429), 120, 35 };
SELLIST_DIALOG_DELETE_BUTTON = new UiArtTextButton(_("Delete"), &SelheroUiFocusNavigationYesNo, rect4, UIS_CENTER | UIS_BIG | UIS_DISABLED);
vecSelDlgItems.push_back(SELLIST_DIALOG_DELETE_BUTTON);
SDL_Rect rect5 = { (Sint16)(PANEL_LEFT + 489), (Sint16)(UI_OFFSET_Y + 429), 120, 35 };
vecSelDlgItems.push_back(new UiArtTextButton(_("Cancel"), &UiFocusNavigationEsc, rect5, UIS_CENTER | UIS_BIG | UIS_GOLD));
UiInitList(selhero_SaveCount + 1, SelheroListFocus, SelheroListSelect, SelheroListEsc, vecSelDlgItems, false, SelheroListDeleteYesNo);
UiInitScrollBar(scrollBar, MaxViewportItems, &listOffset);
if (selhero_isMultiPlayer) {
strcpy(title, _("Multi Player Characters"));
} else {
strcpy(title, _("Single Player Characters"));
}
}
static void UiSelHeroDialog(
bool (*fninfo)(bool (*fninfofunc)(_uiheroinfo *)),
bool (*fncreate)(_uiheroinfo *),
void (*fnstats)(unsigned int, _uidefaultstats *),
bool (*fnremove)(_uiheroinfo *),
_selhero_selections *dlgresult,
char (*name)[16])
{
bUIElementsLoaded = true;
do {
gfnHeroInfo = fninfo;
gfnHeroCreate = fncreate;
gfnHeroStats = fnstats;
selhero_result = *dlgresult;
selhero_navigateYesNo = false;
selhero_Init();
if (selhero_SaveCount != 0) {
selhero_List_Init();
} else {
SelheroListSelect(selhero_SaveCount);
}
selhero_endMenu = false;
while (!selhero_endMenu && !selhero_navigateYesNo) {
UiClearScreen();
UiRenderItems(vecSelHeroDialog);
UiPollAndRender();
}
SelheroFree();
if (selhero_navigateYesNo) {
char dialogTitle[32];
char dialogText[256];
if (selhero_isMultiPlayer) {
strncpy(dialogTitle, _("Delete Multi Player Hero"), sizeof(dialogTitle) - 1);
} else {
strncpy(dialogTitle, _("Delete Single Player Hero"), sizeof(dialogTitle) - 1);
}
strncpy(dialogText, fmt::format(_("Are you sure you want to delete the character \"{:s}\"?"), selhero_heroInfo.name).c_str(), sizeof(dialogText));
if (UiSelHeroYesNoDialog(dialogTitle, dialogText))
fnremove(&selhero_heroInfo);
}
} while (selhero_navigateYesNo);
*dlgresult = selhero_result;
strncpy(*name, selhero_heroInfo.name, sizeof(*name));
}
void UiSelHeroSingDialog(
bool (*fninfo)(bool (*fninfofunc)(_uiheroinfo *)),
bool (*fncreate)(_uiheroinfo *),
bool (*fnremove)(_uiheroinfo *),
void (*fnstats)(unsigned int, _uidefaultstats *),
_selhero_selections *dlgresult,
char (*name)[16],
_difficulty *difficulty)
{
selhero_isMultiPlayer = false;
UiSelHeroDialog(fninfo, fncreate, fnstats, fnremove, dlgresult, name);
*difficulty = nDifficulty;
}
void UiSelHeroMultDialog(
bool (*fninfo)(bool (*fninfofunc)(_uiheroinfo *)),
bool (*fncreate)(_uiheroinfo *),
bool (*fnremove)(_uiheroinfo *),
void (*fnstats)(unsigned int, _uidefaultstats *),
_selhero_selections *dlgresult,
char (*name)[16])
{
selhero_isMultiPlayer = true;
UiSelHeroDialog(fninfo, fncreate, fnstats, fnremove, dlgresult, name);
}
} // namespace devilution