diff --git a/D2Editor.exe b/D2Editor.exe
index 878a0c27..576f59d1 100644
Binary files a/D2Editor.exe and b/D2Editor.exe differ
diff --git a/UserGuide.pdf b/UserGuide.pdf
index ad375eee..733efea7 100644
Binary files a/UserGuide.pdf and b/UserGuide.pdf differ
diff --git a/readme.md b/readme.md
index 9b9c5a76..61886d12 100644
--- a/readme.md
+++ b/readme.md
@@ -9,7 +9,7 @@ ______________________________________________
### How To Use
1. Run the D2Editor.exe file
-2. Open a Diablo II character file (press CTRL-O or click on File, Open) which is located in the "save" directory of where you installed Diablo II.
+2. Open a Diablo II character file (press CTRL-O or click on File, Open) which is located in the "save" directory of where you installed Diablo II Resurrected.
3. Make changes (type in a value if editing level, gold, experience, etc. or use the mouse if changing title, class, or state)
4. Save changes (press CTRL-S or click on File, Save)
5. Play Diablo II.
@@ -48,7 +48,7 @@ You can edit the following stats:
### Known Issues
-- Tested with Diablo II Resurrected v1.1.67554 and Diablo II Classic v1.14d (It has unit test confirming it supports the older versions from v1.00 through to v1.09 as well but the files used in testing have not been tested in a real game)**
+- Tested with Diablo II Resurrected v1.2.68992 (PTR 2.4) and Diablo II Classic v1.14d (It has unit test confirming it supports the all versions from v1.00 and up as well but the files used in testing have not been tested in a real game)**
- Starting with Diablo II Classic V1.13c the maximum gold in your stash no longer depends on yoru character's level, and is now a flat cap of 2,500,000 instead. The editor will now use this value when editing Ressurrected files or Classic files marked as v1.10 or higher when determining the limit for your gold in your stash.
- You must close the character file before playing Diablo II. This is because there are no options to enable file sharing for the read and write functions I'm using in ANSI C++.
@@ -65,15 +65,27 @@ Check the following site for updates at https://github.com/WalterCouto/D2CE
### Useful Links
* https://github.com/zhaoleimxd/JamellaD2E
+* https://github.com/daidodo/diablo_edit
* https://github.com/dschu012/D2SLib
* https://github.com/squeek502/d2itemreader
* https://github.com/nokka/d2s
* https://github.com/krisives/d2s-format
* https://tristram-archives.github.io/diablo2_infodump//2013/just%20hosting%20these,%20Downloaded%20from%20Internet/documentation/d2s_save_file_format_1.13d.html
+* https://github.com/andersgong/d2bin2txt/
* https://diablo.antikrist.org/item-codes/
* [d2s Binary File Format](d2s_File_Format.md)
### Revision History
+**Version 2.15 (April 26, 2022)**
+- Updated: Reorganize resources and add txt file to allow for future localization and customizations.
+- Updated: Create GPS Dialog now chooses a beltable item by default when launched from the belt inventory.
+- Updated: Fixed some bugs related to item tooltip strings
+- Updated: Fixed bugs retrieving skill names for magical attributes for non-character specific skills
+- Updated: Fixed D2R items with realm GUID. D2R now stores 128 bits instead of the 96 bits for realm GUID
+- Updated: Properly calculate the displayed requirements for items. (level, dexterity and Strength)
+- Updated: Fixed player name reading for PTR2.4 and allow use of UTF-8 characters in player name
+
+
**Version 2.14 (Mar 11, 2022)**
- Updated: Updated jewel alternate images.
- Updated: Updated item context menus across forms showing items.
diff --git a/source/D2AddGemsForm.cpp b/source/D2AddGemsForm.cpp
index 8542fbb1..3e599184 100644
--- a/source/D2AddGemsForm.cpp
+++ b/source/D2AddGemsForm.cpp
@@ -21,7 +21,8 @@
#include "D2Editor.h"
#include "D2AddGemsForm.h"
#include "afxdialogex.h"
-#include "d2ce\helpers\ItemHelpers.h"
+#include "d2ce/helpers/ItemHelpers.h"
+#include
#ifdef _DEBUG
#define new DEBUG_NEW
@@ -38,7 +39,14 @@ namespace
return _T("");
}
- return CString(itemType.name.c_str());
+ auto uName = utf8::utf8to16(itemType.name);
+ return CString(reinterpret_cast(uName.c_str()));
+ }
+
+ bool IsBeltableFromCode(const std::array& gemCode)
+ {
+ const auto& itemType = d2ce::ItemHelpers::getItemTypeHelper(gemCode);
+ return itemType.isBeltable();
}
}
@@ -83,8 +91,18 @@ BOOL CD2AddGemsForm::OnInitDialog()
{
__super::OnInitDialog();
- // Fill in to from combo
- bool isPotionSelected = false;
+ // Fill in from combo
+ bool isBeltable = false;
+ bool mustBeBeltable = false;
+ if (ItemsFormPtr != nullptr)
+ {
+ auto itemLocation = static_cast(ItemsFormPtr->CurrItemLocation[0]);
+ if (itemLocation == d2ce::EnumItemLocation::BELT)
+ {
+ mustBeBeltable = true;
+ }
+ }
+
CComboBox* pFromCombo = (CComboBox*)GetDlgItem(IDC_FROM_COMBO);
if (pFromCombo != nullptr)
{
@@ -94,13 +112,15 @@ BOOL CD2AddGemsForm::OnInitDialog()
std::array selectedGemCode;
if (ItemPtr->getItemCode(selectedGemCode))
{
- selectedItemData = *reinterpret_cast(selectedGemCode.data());
- isPotionSelected = ItemPtr->isPotion();
+ if (!mustBeBeltable || ItemPtr->isBeltable())
+ {
+ selectedItemData = *reinterpret_cast(selectedGemCode.data());
+ }
}
-
}
- int selectedIdx = 0;
+ bool hasSelectedBeltable = false;
+ int selectedIdx = 0;
std::array< std::uint8_t, 4> gemCode = { 0x20, 0x20, 0x20, 0x20 };
std::uint32_t& itemData = *reinterpret_cast(gemCode.data());
std::vector gpsCodes;
@@ -121,6 +141,20 @@ BOOL CD2AddGemsForm::OnInitDialog()
if (itemData == selectedItemData)
{
selectedIdx = insertedIdx;
+ isBeltable = IsBeltableFromCode(gemCode);
+ }
+ else if (mustBeBeltable && !hasSelectedBeltable)
+ {
+ if (IsBeltableFromCode(gemCode))
+ {
+ hasSelectedBeltable = true;
+ selectedIdx = insertedIdx;
+ isBeltable = true;
+ }
+ }
+ else if (insertedIdx == selectedIdx)
+ {
+ isBeltable = IsBeltableFromCode(gemCode);
}
}
@@ -134,7 +168,7 @@ BOOL CD2AddGemsForm::OnInitDialog()
if (SharedStashFormPtr != nullptr)
{
CString str;
- str.Format(_T("Shared Stash %ld"), SharedStashFormPtr->getCurrentPage() + 1);
+ str.Format(_T("Shared Stash %ld"), (int)SharedStashFormPtr->getCurrentPage() + 1);
auto insertedIdx = pLocationCombo->AddString(str);
pLocationCombo->SetItemData(insertedIdx, std::uint64_t(SharedStashFormPtr->getCurrentPage()));
pLocationCombo->SetCurSel(0);
@@ -149,9 +183,12 @@ BOOL CD2AddGemsForm::OnInitDialog()
std::uint16_t selectedItemData = 0;
if (ItemsFormPtr != nullptr)
{
- selectedItemData = *reinterpret_cast(ItemsFormPtr->CurrItemLocation.data());
+ if (!mustBeBeltable || isBeltable)
+ {
+ selectedItemData = *reinterpret_cast(ItemsFormPtr->CurrItemLocation.data());
+ }
}
- int selectedIdx = isPotionSelected ? 0 : 1;
+ int selectedIdx = isBeltable ? 0 : 1;
locationID = static_cast>(d2ce::EnumItemLocation::BELT);
altLocationId = static_cast>(d2ce::EnumAltItemLocation::UNKNOWN);
@@ -226,7 +263,7 @@ void CD2AddGemsForm::OnBnClickedAdd()
CString temp;
pFromCombo->GetLBText(fromIdx, temp);
msg += temp;
- msg += _T(" was added to ");
+ msg += _T(" could not be added to ");
pLocationCombo->GetLBText(toIdx, temp);
msg += temp;
AfxMessageBox(msg, MB_ICONINFORMATION | MB_OK);
@@ -248,10 +285,10 @@ void CD2AddGemsForm::OnBnClickedAdd()
CString temp;
pFromCombo->GetLBText(fromIdx, temp);
msg += temp;
- msg += _T(" was added to ");
+ msg += _T(" could not be added to ");
pLocationCombo->GetLBText(toIdx, temp);
msg += temp;
- AfxMessageBox(msg, MB_ICONINFORMATION | MB_OK);
+ AfxMessageBox(msg, MB_ICONEXCLAMATION | MB_OK);
}
}
else if (!MainForm.addItem(static_cast(locationID), static_cast(altLocationId), gemCode))
diff --git a/source/D2Editor.cpp b/source/D2Editor.cpp
index 38ecd828..6c7079b1 100644
--- a/source/D2Editor.cpp
+++ b/source/D2Editor.cpp
@@ -18,6 +18,21 @@
Revision History
================
+Version 2.15 (April 26, 2022)
+ - Updated: Reorganize resources and add txt file to allow for
+ future localization and customizations.
+ - Updated: Create GPS Dialog now chooses a beltable item by
+ default when launched from the belt inventory.
+ - Updated: Fixed some bugs related to item tooltip strings
+ - Updated: Fixed bugs retrieving skill names for magical
+ attributes for non-character specific skills
+ - Updated: Fixed D2R items with realm GUID. D2R now stores
+ 128 bits instead of the 96 bits for realm GUID
+ - Updated: Properly calculate the displayed requirements
+ for items. (level, dexterity and Strength)
+ - Updated: Fixed player name reading for PTR2.4 and allow
+ use of UTF-8 characters in player name
+
Version 2.14 (Mar 11, 2022)
- Updated: Updated jewel alternate images.
- Updated: Updated item context menus across forms showing
@@ -173,7 +188,7 @@ Version 2.00 (June 18, 2021)
- Updated: The original Diablo II icon with transparant backgroud is used.
- Updated: Level Requirements will show requirments using the version of
from the character file. V1.10 is assumed to have the latest
- Level Requirements from Diable II 1.14b, while versions
+ Level Requirements from Diablo II 1.14b, while versions
1.07-1.09 will level requirements for those versions and any
version below 1.07 will show the level requirements tha
existed since 1.00. When loading the Level Requirements dialog
@@ -343,6 +358,8 @@ CD2EditorApp theApp;
// CD2EditorApp initialization
BOOL CD2EditorApp::InitInstance()
{
+ Gdiplus::GdiplusStartup(&m_gdiplusToken, &m_gdiplusStartupInput, NULL);
+
// InitCommonControlsEx() is required on Windows XP if an application
// manifest specifies use of ComCtl32.dll version 6 or later to enable
// visual styles. Otherwise, any window creation will fail.
@@ -426,3 +443,9 @@ CDocument* CD2EditorApp::OpenDocumentFile(LPCTSTR lpszFileName)
return((CDocument*)1); // CWinApp::OpenDocumentFile(lpszFileName);
}
//---------------------------------------------------------------------------
+int CD2EditorApp::ExitInstance()
+{
+ Gdiplus::GdiplusShutdown(m_gdiplusToken);
+ return __super::ExitInstance();
+}
+//---------------------------------------------------------------------------
diff --git a/source/D2Editor.h b/source/D2Editor.h
index f5eab2ab..e0b30f65 100644
--- a/source/D2Editor.h
+++ b/source/D2Editor.h
@@ -46,9 +46,13 @@ class CD2EditorApp : public CWinAppEx
protected:
HACCEL m_haccel = NULL;
+
+ Gdiplus::GdiplusStartupInput m_gdiplusStartupInput;
+ ULONG_PTR m_gdiplusToken = 0;
public:
virtual BOOL ProcessMessageFilter(int code, LPMSG lpMsg);
virtual CDocument* OpenDocumentFile(LPCTSTR lpszFileName);
+ virtual int ExitInstance();
};
//---------------------------------------------------------------------------
extern CD2EditorApp theApp;
diff --git a/source/D2Editor.rc b/source/D2Editor.rc
index 9f093ea4..b616294d 100644
Binary files a/source/D2Editor.rc and b/source/D2Editor.rc differ
diff --git a/source/D2Editor.vcxproj b/source/D2Editor.vcxproj
index c98868c1..a185f188 100644
--- a/source/D2Editor.vcxproj
+++ b/source/D2Editor.vcxproj
@@ -95,7 +95,8 @@
pch.h
true
stdcpp17
- .\;$(SolutionDir)d2ce\thirdparty\jsoncpp\include;$(SolutionDir)d2ce
+ .\;$(SolutionDir)d2ce\thirdparty\jsoncpp\include;$(SolutionDir)d2ce\thirdparty\rapidcsv\include;$(SolutionDir)d2ce\thirdparty\utf8\include;$(SolutionDir)d2ce
+ /Zc:__cplusplus %(AdditionalOptions)
Windows
@@ -120,7 +121,8 @@
pch.h
stdcpp17
true
- .\;$(SolutionDir)d2ce\thirdparty\jsoncpp\include;$(SolutionDir)d2ce
+ .\;$(SolutionDir)d2ce\thirdparty\jsoncpp\include;$(SolutionDir)d2ce\thirdparty\rapidcsv\include;$(SolutionDir)d2ce\thirdparty\utf8\include;$(SolutionDir)d2ce
+ /Zc:__cplusplus %(AdditionalOptions)
Windows
@@ -147,7 +149,8 @@
pch.h
true
stdcpp17
- .\;$(SolutionDir)d2ce\thirdparty\jsoncpp\include;$(SolutionDir)d2ce
+ .\;$(SolutionDir)d2ce\thirdparty\jsoncpp\include;$(SolutionDir)d2ce\thirdparty\rapidcsv\include;$(SolutionDir)d2ce\thirdparty\utf8\include;$(SolutionDir)d2ce
+ /Zc:__cplusplus %(AdditionalOptions)
Windows
@@ -182,7 +185,8 @@
pch.h
true
stdcpp17
- .\;$(SolutionDir)d2ce\thirdparty\jsoncpp\include;$(SolutionDir)d2ce
+ .\;$(SolutionDir)d2ce\thirdparty\jsoncpp\include;$(SolutionDir)d2ce\thirdparty\rapidcsv\include;$(SolutionDir)d2ce\thirdparty\utf8\include;$(SolutionDir)d2ce
+ /Zc:__cplusplus %(AdditionalOptions)
Windows
@@ -215,6 +219,7 @@
+
@@ -232,6 +237,11 @@
+
+
+
+
+
@@ -260,7 +270,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -307,7 +351,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
@@ -330,7 +386,6 @@
-
@@ -339,17 +394,11 @@
-
-
-
-
-
-
@@ -367,10 +416,6 @@
-
-
-
-
@@ -378,21 +423,13 @@
-
-
-
-
-
-
-
-
@@ -452,8 +489,6 @@
-
-
@@ -487,7 +522,6 @@
-
@@ -510,7 +544,6 @@
-
@@ -560,11 +593,6 @@
-
-
-
-
-
@@ -584,6 +612,217 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -691,12 +930,40 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/source/D2Editor.vcxproj.filters b/source/D2Editor.vcxproj.filters
index 0ccb9d30..23219e61 100644
--- a/source/D2Editor.vcxproj.filters
+++ b/source/D2Editor.vcxproj.filters
@@ -28,6 +28,108 @@
{59645a1f-9baf-4dd6-8ab5-fdf86c4d77d2}
+
+ {32809611-18ac-4a64-94cc-8e3736c1ccdc}
+
+
+ {11c54458-13b6-49fc-87ac-d15c46600715}
+
+
+ {693349b5-e556-40d1-a5f7-044c7b00f115}
+
+
+ {18b60a1e-c0a0-42b1-8c73-d368a96952b0}
+
+
+ {546ab00e-ae5a-4654-b66d-fe01e2d88899}
+
+
+ {7e5ccc3f-4b6c-4e7c-a515-a57eaf234be7}
+
+
+ {21ccd7b4-5d54-4e79-bb12-6f9252677068}
+
+
+ {0346a663-68b6-4040-810a-729d26455e6e}
+
+
+ {5cf39168-0b67-40e7-8d67-05b7cdde1ecf}
+
+
+ {8890b230-9511-4de9-96c4-8e20b1fd275a}
+
+
+ {608273ea-03c9-4f8f-8068-73fad4277537}
+
+
+ {168bb61b-cfec-46f8-ac16-5c2f0bf552ad}
+
+
+ {e911676c-a1eb-4535-b753-382f3714735d}
+
+
+ {bcf14d83-07a4-4bc6-8a91-976742ddd96b}
+
+
+ {59adcf98-489c-45f3-9d25-3e6f76fd5d75}
+
+
+ {c76ae5a0-75df-4304-b247-1a129a4f2832}
+
+
+ {11662c25-f79b-4116-9d8f-7313d4a0206a}
+
+
+ {4b01d42a-53a2-4576-b6fb-54abbb76f4f5}
+
+
+ {edc58910-728f-4872-addf-738a69b73573}
+
+
+ {c257dad3-997a-4bbd-87ef-5f7d82e790e2}
+
+
+ {080870a4-c3ec-474f-94db-732b1ad4518b}
+
+
+ {1eb70608-158f-4101-a683-8636fb93a4a3}
+
+
+ {cc9e960a-ecff-4acf-bb85-d4ea954ca37c}
+
+
+ {18c2e8b1-000e-490b-8c07-57ef2b9854ad}
+
+
+ {58d81b68-0e6b-45da-ba7c-175646431e4d}
+
+
+ {41cff87f-5534-4fc0-9629-446b5d054a95}
+
+
+ {2123bbe5-38cc-4196-9817-d038390bb419}
+
+
+ {32e93d4f-b50f-4787-b5f9-881c7957ed2d}
+
+
+ {1fe8995a-f7b3-4f2b-9ce3-7af10d4e99f1}
+
+
+ {2b38910e-96eb-452a-b344-1171ff2a86ef}
+
+
+ {411c5705-7630-4a7c-85f1-9499ab2b5de8}
+
+
+ {338fc447-c8c7-45a9-9e8e-230af3e693bb}
+
+
+ {b32fd661-ff94-4015-9910-8064e812a414}
+
+
+ {0f27b7aa-8a9f-4d06-b499-b07fac52cf42}
+
@@ -159,9 +261,6 @@
Header Files\rapidcsv
-
- Header Files\d2ce
-
Header Files
@@ -171,6 +270,27 @@
Header Files
+
+ Header Files\utf8
+
+
+ Header Files\utf8
+
+
+ Header Files\utf8
+
+
+ Header Files\utf8
+
+
+ Header Files\utf8
+
+
+ Header Files\d2ce\Helpers
+
+
+ Header Files\d2ce\Helpers
+
@@ -230,9 +350,6 @@
Source Files\jsoncpp
-
- Source Files\d2ce
-
Source Files
@@ -242,6 +359,111 @@
Source Files
+
+ Source Files\d2ce\Helpers
+
+
+ Source Files\d2ce\Helpers
+
+
+ Source Files\d2ce\Helpers
+
+
+ Source Files\d2ce\Helpers
+
+
+ Source Files\d2ce\Helpers
+
+
+ Source Files\d2ce\Helpers
+
+
+ Source Files\d2ce\Helpers
+
+
+ Source Files\d2ce\Helpers
+
+
+ Source Files\d2ce\Helpers
+
+
+ Source Files\d2ce\Helpers
+
+
+ Source Files\d2ce\Helpers
+
+
+ Source Files\d2ce\Helpers
+
+
+ Source Files\d2ce\Helpers
+
+
+ Source Files\d2ce\Helpers
+
+
+ Source Files\d2ce\Helpers
+
+
+ Source Files\d2ce\Helpers
+
+
+ Source Files\d2ce\Helpers
+
+
+ Source Files\d2ce\Helpers
+
+
+ Source Files\d2ce\Helpers
+
+
+ Source Files\d2ce\Helpers
+
+
+ Source Files\d2ce\Helpers
+
+
+ Source Files\d2ce\Helpers
+
+
+ Source Files\d2ce\Helpers
+
+
+ Source Files\d2ce\Helpers
+
+
+ Source Files\d2ce\Helpers
+
+
+ Source Files\d2ce\Helpers
+
+
+ Source Files\d2ce\Helpers
+
+
+ Source Files\d2ce\Helpers
+
+
+ Source Files\d2ce\Helpers
+
+
+ Source Files\d2ce\Helpers
+
+
+ Source Files\d2ce\Helpers
+
+
+ Source Files\d2ce\Helpers
+
+
+ Source Files\d2ce\Helpers
+
+
+ Source Files\d2ce\Helpers
+
+
+ Source Files\d2ce\Helpers
+
@@ -261,14 +483,47 @@
Header Files\rapidcsv
+
+ Header Files\utf8
+
+
+ Resource Files\TXT\LNG
+
+
+ Resource Files\TXT\LNG
+
+
+ Resource Files\TXT\LNG
+
+
+ Resource Files\TXT\LNG
+
+
+ Resource Files\TXT\LNG
+
+
+ Resource Files\TXT\LNG
+
+
+ Resource Files\TXT\LNG
+
+
+ Resource Files\TXT\LNG
+
+
+ Resource Files\TXT\LNG
+
+
+ Resource Files\TXT\LNG
+
+
+ Resource Files\TXT\LNG
+
Resource Files
-
- Resource Files
-
Resource Files
@@ -287,1139 +542,1771 @@
Resource Files
-
+
Resource Files
-
+
Resource Files
-
+
Resource Files
-
+
Resource Files
-
+
Resource Files
-
+
Resource Files
-
+
Resource Files
-
+
Resource Files
-
+
Resource Files
+
+ Resource Files\Armor
+
+
+ Resource Files\Boots
+
+
+ Resource Files\Shield
+
+
+ Resource Files\Helm
+
+
+ Resource Files\Armor
+
+
+ Resource Files\Armor
+
+
+ Resource Files\Armor
+
+
+ Resource Files\Armor
+
+
+ Resource Files\Armor
+
+
+ Resource Files\Armor
+
+
+ Resource Files\Armor
+
- Resource Files
+ Resource Files\Armor
- Resource Files
+ Resource Files\Armor
- Resource Files
+ Resource Files\Armor
- Resource Files
-
-
- Resource Files
+ Resource Files\Armor
- Resource Files
+ Resource Files\Armor
+
+
+ Resource Files\Armor
- Resource Files
+ Resource Files\Armor
-
- Resource Files
+
+ Resource Files\Belt
+
+
+ Resource Files\Belt
+
+
+ Resource Files\Belt
+
+
+ Resource Files\Belt
+
+
+ Resource Files\Belt
- Resource Files
+ Resource Files\Boots
- Resource Files
+ Resource Files\Boots
- Resource Files
+ Resource Files\Boots
- Resource Files
+ Resource Files\Boots
-
- Resource Files
-
-
- Resource Files
+
+ Resource Files\Gloves
-
- Resource Files
+
+ Resource Files\Gloves
-
- Resource Files
+
+ Resource Files\Gloves
-
- Resource Files
+
+ Resource Files\Gloves
-
- Resource Files
+
+ Resource Files\Gloves
- Resource Files
+ Resource Files\Helm
- Resource Files
+ Resource Files\Helm
- Resource Files
+ Resource Files\Helm
- Resource Files
+ Resource Files\Helm
- Resource Files
+ Resource Files\Helm
- Resource Files
-
-
- Resource Files
+ Resource Files\Helm
- Resource Files
+ Resource Files\Helm\Barbarian
- Resource Files
+ Resource Files\Helm\Barbarian
- Resource Files
+ Resource Files\Helm\Barbarian
- Resource Files
+ Resource Files\Helm\Barbarian
- Resource Files
-
-
- Resource Files
-
-
- Resource Files
-
-
- Resource Files
+ Resource Files\Helm\Barbarian
-
- Resource Files
+
+ Resource Files\Helm
- Resource Files
+ Resource Files\Helm\Circlet
- Resource Files
+ Resource Files\Helm\Circlet
- Resource Files
+ Resource Files\Helm\Circlet
- Resource Files
+ Resource Files\Helm\Circlet
- Resource Files
+ Resource Files\Helm\Druid
- Resource Files
+ Resource Files\Helm\Druid
- Resource Files
+ Resource Files\Helm\Druid
- Resource Files
+ Resource Files\Helm\Druid
- Resource Files
-
-
- Resource Files
-
-
- Resource Files
-
-
- Resource Files
-
-
- Resource Files
-
-
- Resource Files
-
-
- Resource Files
-
-
- Resource Files
-
-
- Resource Files
-
-
- Resource Files
-
-
- Resource Files
-
-
- Resource Files
+ Resource Files\Helm\Druid
- Resource Files
+ Resource Files\Shield
- Resource Files
+ Resource Files\Shield
- Resource Files
+ Resource Files\Shield
- Resource Files
+ Resource Files\Shield
- Resource Files
+ Resource Files\Shield
- Resource Files
+ Resource Files\Shield
- Resource Files
+ Resource Files\Shield
-
- Resource Files
+
+ Resource Files\Shield\Necromancer
-
- Resource Files
+
+ Resource Files\Shield\Necromancer
-
- Resource Files
+
+ Resource Files\Shield\Necromancer
-
- Resource Files
-
-
- Resource Files
-
-
- Resource Files
-
-
- Resource Files
-
-
- Resource Files
-
-
- Resource Files
+
+ Resource Files\Shield\Necromancer
- Resource Files
+ Resource Files\Shield\Necromancer
- Resource Files
+ Resource Files\Shield\Paladin
- Resource Files
+ Resource Files\Shield\Paladin
- Resource Files
+ Resource Files\Shield\Paladin
- Resource Files
+ Resource Files\Shield\Paladin
- Resource Files
+ Resource Files\Shield\Paladin
+
+
+ Resource Files\Weapons\Club
+
+
+ Resource Files\Weapons\Amazon
+
+
+ Resource Files\Weapons\Amazon
+
+
+ Resource Files\Weapons\Amazon
+
+
+ Resource Files\Weapons\Amazon
+
+
+ Resource Files\Weapons\Amazon
+
+
+ Resource Files\Weapons\Assassin
+
+
+ Resource Files\Weapons\Assassin
+
+
+ Resource Files\Weapons\Assassin
+
+
+ Resource Files\Weapons\Assassin
- Resource Files
+ Resource Files\Weapons\Axe
- Resource Files
+ Resource Files\Weapons\Axe
- Resource Files
+ Resource Files\Weapons\Axe
- Resource Files
+ Resource Files\Weapons\Axe
- Resource Files
+ Resource Files\Weapons\Axe
- Resource Files
+ Resource Files\Weapons\Axe
- Resource Files
+ Resource Files\Weapons\Axe
- Resource Files
+ Resource Files\Weapons\Axe
+
+
+ Resource Files\Weapons\Axe
- Resource Files
+ Resource Files\Weapons\Axe
-
- Resource Files
+
+ Resource Files\Weapons\Bow
-
- Resource Files
+
+ Resource Files\Weapons\Bow
-
- Resource Files
+
+ Resource Files\Weapons\Bow
-
- Resource Files
+
+ Resource Files\Weapons\Bow
-
- Resource Files
+
+ Resource Files\Weapons\Bow
-
- Resource Files
+
+ Resource Files\Weapons\Bow
+
+
+ Resource Files\Weapons\Bow
+
+
+ Resource Files\Weapons\Bow
+
+
+ Resource Files\Weapons\Bow
+
+
+ Resource Files\Weapons\Bow
+
+
+ Resource Files\Weapons\Bow
+
+
+ Resource Files\Weapons\Bow
- Resource Files
+ Resource Files\Weapons\Club
- Resource Files
+ Resource Files\Weapons\Club
- Resource Files
+ Resource Files\Weapons\Club
- Resource Files
+ Resource Files\Weapons\Club
- Resource Files
+ Resource Files\Weapons\Club
- Resource Files
+ Resource Files\Weapons\Club
- Resource Files
+ Resource Files\Weapons\Club
-
- Resource Files
+
+ Resource Files\Weapons\Javelin
-
- Resource Files
+
+ Resource Files\Weapons\Javelin
-
- Resource Files
+
+ Resource Files\Weapons\Javelin
-
- Resource Files
+
+ Resource Files\Weapons\Javelin
-
- Resource Files
+
+ Resource Files\Weapons\Javelin
-
- Resource Files
+
+ Resource Files\Weapons\Necromancer
-
- Resource Files
+
+ Resource Files\Weapons\Necromancer
-
- Resource Files
+
+ Resource Files\Weapons\Necromancer
-
- Resource Files
+
+ Resource Files\Weapons\Necromancer
-
- Resource Files
+
+ Resource Files\Weapons\Orb
-
- Resource Files
+
+ Resource Files\Weapons\Orb
-
- Resource Files
+
+ Resource Files\Weapons\Orb
-
- Resource Files
+
+ Resource Files\Weapons\Orb
-
- Resource Files
+
+ Resource Files\Weapons\Orb
-
- Resource Files
+
+ Resource Files\Weapons\Paladin
-
- Resource Files
+
+ Resource Files\Weapons\Paladin
-
- Resource Files
+
+ Resource Files\Weapons\Paladin
-
- Resource Files
+
+ Resource Files\Weapons\Potion
-
- Resource Files
+
+ Resource Files\Weapons\Potion
-
- Resource Files
+
+ Resource Files\Weapons\Potion
-
- Resource Files
+
+ Resource Files\Weapons\Potion
-
- Resource Files
+
+ Resource Files\Weapons\Potion
-
- Resource Files
+
+ Resource Files\Weapons\Potion
-
- Resource Files
+
+ Resource Files\Weapons\Quest
-
- Resource Files
+
+ Resource Files\Weapons\Quest
-
- Resource Files
+
+ Resource Files\Weapons\Quest
-
- Resource Files
+
+ Resource Files\Weapons\Quest
-
- Resource Files
+
+ Resource Files\Weapons\Quest
-
- Resource Files
+
+ Resource Files\Weapons\Quest
-
- Resource Files
+
+ Resource Files\Weapons\Quest
-
- Resource Files
+
+ Resource Files\Weapons\Quest
+
+
+ Resource Files\Weapons\Quest
- Resource Files
+ Resource Files\Weapons\Spear
- Resource Files
+ Resource Files\Weapons\Spear
- Resource Files
+ Resource Files\Weapons\Spear
- Resource Files
+ Resource Files\Weapons\Spear
- Resource Files
+ Resource Files\Weapons\Spear
- Resource Files
+ Resource Files\Weapons\Spear
- Resource Files
+ Resource Files\Weapons\Spear
- Resource Files
+ Resource Files\Weapons\Spear
- Resource Files
+ Resource Files\Weapons\Spear
- Resource Files
+ Resource Files\Weapons\Spear
- Resource Files
+ Resource Files\Weapons\Spear
- Resource Files
+ Resource Files\Weapons\Staff
- Resource Files
+ Resource Files\Weapons\Staff
- Resource Files
+ Resource Files\Weapons\Staff
- Resource Files
+ Resource Files\Weapons\Staff
- Resource Files
+ Resource Files\Weapons\Staff
-
- Resource Files
+
+ Resource Files\Weapons\Sword
-
- Resource Files
+
+ Resource Files\Weapons\Sword
-
- Resource Files
+
+ Resource Files\Weapons\Sword
-
- Resource Files
+
+ Resource Files\Weapons\Sword
-
- Resource Files
+
+ Resource Files\Weapons\Sword
-
- Resource Files
+
+ Resource Files\Weapons\Sword
-
- Resource Files
+
+ Resource Files\Weapons\Sword
-
- Resource Files
+
+ Resource Files\Weapons\Sword
-
- Resource Files
+
+ Resource Files\Weapons\Sword
-
- Resource Files
+
+ Resource Files\Weapons\Sword
-
- Resource Files
+
+ Resource Files\Weapons\Sword
-
- Resource Files
+
+ Resource Files\Weapons\Sword
-
- Resource Files
+
+ Resource Files\Weapons\Sword
-
- Resource Files
+
+ Resource Files\Weapons\Sword
-
- Resource Files
+
+ Resource Files\Weapons\Sword
-
- Resource Files
+
+ Resource Files\Weapons\Sword
-
- Resource Files
+
+ Resource Files\Weapons\Sword
-
- Resource Files
+
+ Resource Files\Weapons\Sword
-
- Resource Files
+
+ Resource Files\Weapons\Throwing
-
- Resource Files
-
-
- Resource Files
-
-
- Resource Files
-
-
- Resource Files
-
-
- Resource Files
-
-
- Resource Files
-
-
- Resource Files
-
-
- Resource Files
-
-
- Resource Files
-
-
- Resource Files
-
-
- Resource Files
-
-
- Resource Files
-
-
- Resource Files
-
-
- Resource Files
-
-
- Resource Files
-
-
- Resource Files
-
-
- Resource Files
-
-
- Resource Files
-
-
- Resource Files
-
-
- Resource Files
+
+ Resource Files\Weapons\Throwing
-
- Resource Files
+
+ Resource Files\Weapons\Throwing
-
- Resource Files
+
+ Resource Files\Weapons\Throwing
- Resource Files
+ Resource Files\Misc
- Resource Files
+ Resource Files\Misc
- Resource Files
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
- Resource Files
+ Resource Files\Misc
- Resource Files
+ Resource Files\Misc
- Resource Files
+ Resource Files\Misc
- Resource Files
+ Resource Files\Misc
- Resource Files
+ Resource Files\Misc
- Resource Files
+ Resource Files\Misc
- Resource Files
+ Resource Files\Misc
- Resource Files
+ Resource Files\Misc
- Resource Files
+ Resource Files\Misc
- Resource Files
+ Resource Files\Misc
- Resource Files
+ Resource Files\Misc
- Resource Files
+ Resource Files\Misc
- Resource Files
+ Resource Files\Misc
- Resource Files
+ Resource Files\Misc
- Resource Files
+ Resource Files\Misc
- Resource Files
+ Resource Files\Misc
- Resource Files
+ Resource Files\Misc
- Resource Files
+ Resource Files\Misc
- Resource Files
+ Resource Files\Misc
- Resource Files
+ Resource Files\Misc
- Resource Files
+ Resource Files\Misc
- Resource Files
+ Resource Files\Misc
- Resource Files
+ Resource Files\Misc
- Resource Files
+ Resource Files\Misc
- Resource Files
+ Resource Files\Misc
- Resource Files
+ Resource Files\Misc
- Resource Files
+ Resource Files\Misc
- Resource Files
+ Resource Files\Misc
- Resource Files
+ Resource Files\Misc
- Resource Files
+ Resource Files\Misc
- Resource Files
+ Resource Files\Misc
- Resource Files
+ Resource Files\Misc
- Resource Files
+ Resource Files\Misc
- Resource Files
+ Resource Files\Misc
- Resource Files
+ Resource Files\Misc
- Resource Files
+ Resource Files\Misc
- Resource Files
+ Resource Files\Misc
- Resource Files
+ Resource Files\Misc
- Resource Files
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
- Resource Files
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
- Resource Files
+ Resource Files\Misc
- Resource Files
+ Resource Files\Misc
- Resource Files
+ Resource Files\Misc
- Resource Files
+ Resource Files\Misc
- Resource Files
+ Resource Files\Misc
- Resource Files
+ Resource Files\Misc
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
+ Resource Files\Misc
+
+
Resource Files
-
- Resource Files
+
+ Resource Files\Misc
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
-
- Resource Files
+
+ Resource Files\Skills
+
+
+ Resource Files\Skills
+
+
+ Resource Files\TXT
+
+
+ Resource Files\TXT
+
+
+ Resource Files\TXT
+
+
+ Resource Files\TXT
+
+
+ Resource Files\TXT
+
+
+ Resource Files\TXT
+
+
+ Resource Files\TXT
+
+
+ Resource Files\TXT
+
+
+ Resource Files\TXT
+
+
+ Resource Files\TXT
+
+
+ Resource Files\TXT
+
+
+ Resource Files\TXT
+
+
+ Resource Files\TXT
+
+
+ Resource Files\TXT
+
+
+ Resource Files\TXT
+
+
+ Resource Files\TXT
+
+
+ Resource Files\TXT
+
+
+ Resource Files\TXT
+
+
+ Resource Files\TXT
+
+
+ Resource Files\TXT
+
+
+ Resource Files\TXT
+
+
+ Resource Files\TXT
+
+
+ Resource Files\TXT
+
+
+ Resource Files\TXT
+
+
+ Resource Files\TXT
+
+
+ Resource Files\TXT
+
+
+ Resource Files\TXT
+
+
\ No newline at end of file
diff --git a/source/D2GemsForm.cpp b/source/D2GemsForm.cpp
index ab783b3a..e174c28d 100644
--- a/source/D2GemsForm.cpp
+++ b/source/D2GemsForm.cpp
@@ -22,7 +22,7 @@
#include "D2Editor.h"
#include "D2GemsForm.h"
#include "afxdialogex.h"
-#include "d2ce\helpers\ItemHelpers.h"
+#include "d2ce/helpers/ItemHelpers.h"
#ifdef _DEBUG
#define new DEBUG_NEW
diff --git a/source/D2ItemToolTipCtrl.cpp b/source/D2ItemToolTipCtrl.cpp
index d3cb7a4e..8242b4cf 100644
--- a/source/D2ItemToolTipCtrl.cpp
+++ b/source/D2ItemToolTipCtrl.cpp
@@ -20,6 +20,8 @@
#include "pch.h"
#include "D2ItemToolTipCtrl.h"
#include "resource.h" // main symbols
+#include
+#include "helpers/ItemHelpers.h"
#ifdef _DEBUG
#define new DEBUG_NEW
@@ -87,8 +89,9 @@ CSize CD2ItemToolTipCtrl::OnDrawLabel(CDC* pDC, CRect rect, BOOL bCalcOnly)
return __super::OnDrawLabel(pDC, rect, bCalcOnly);
}
- static const COLORREF colors[] = { RGB(255,255,255), RGB(94,94,255), RGB(0,255,0), RGB(255,255,0), RGB(148,128,100), RGB(255,128,0), RGB(255, 0, 0), RGB(117, 117, 117) };
- enum { WHITE = 0, BLUE, GREEN, RARE, UNIQUE, CRAFT, RED, GRAY };
+ // color codes as described in the text files
+ static const COLORREF colors[] = { RGB(255,255,255), RGB(255, 0, 0), RGB(0,255,0), RGB(94,94,255), RGB(148,128,100), RGB(117, 117, 117), RGB(255,255,255), RGB(255,255,255), RGB(255,128,0), RGB(255,255,0) };
+ enum { WHITE = 0, RED = 1, GREEN = 2, BLUE = 3, UNIQUE = 4, GRAY = 5, CRAFT = 8, RARE = 9 };
// Get color of top text
COLORREF color = colors[WHITE];
@@ -137,13 +140,13 @@ CSize CD2ItemToolTipCtrl::OnDrawLabel(CDC* pDC, CRect rect, BOOL bCalcOnly)
}
pDC->SetTextColor(color);
- CFont* pOldFont = (CFont*)pDC->SelectObject(&(GetGlobalData()->fontDefaultGUIBold));
-
- CString strText(CurrItem->getDisplayedItemName().c_str());
+ std::u16string uText = utf8::utf8to16(CurrItem->getDisplayedItemName());
+ CString strText(reinterpret_cast(uText.c_str()));
CSize sizeText(CalcTextSize(pDC, strText, rect, bCalcOnly));
// draw possible rune name
- strText = CurrItem->getDisplayedSocketedRunes().c_str();
+ uText = utf8::utf8to16(CurrItem->getDisplayedSocketedRunes());
+ strText = reinterpret_cast(uText.c_str());
if (!strText.IsEmpty())
{
CSize prevSizeText = sizeText;
@@ -155,7 +158,8 @@ CSize CD2ItemToolTipCtrl::OnDrawLabel(CDC* pDC, CRect rect, BOOL bCalcOnly)
// Other non-magical
auto charLevel = IsMerc ? CharInfo.getMercenaryInfo().getLevel() : CharInfo.getLevel();
- strText = CurrItem->getDisplayedItemAttributes(CharInfo.getClass(), charLevel).c_str();
+ uText = utf8::utf8to16(CurrItem->getDisplayedItemAttributes(CharInfo.getClass(), charLevel));
+ strText = reinterpret_cast(uText.c_str());
if (!strText.IsEmpty())
{
CSize prevSizeText = sizeText;
@@ -169,7 +173,9 @@ CSize CD2ItemToolTipCtrl::OnDrawLabel(CDC* pDC, CRect rect, BOOL bCalcOnly)
if (CurrItem->getDurability(durability) && durability.Max == 0)
{
// Indestructible without the need for the magical attribute of indestructibility
- strText = "Indestructible";
+ std::string u8Text;
+ uText = utf8::utf8to16(d2ce::LocalizationHelpers::GetIndestructibleStringTxtValue(u8Text));
+ strText = reinterpret_cast(uText.c_str());
CSize prevSizeText = sizeText;
pDC->SetTextColor(colors[BLUE]);
@@ -190,7 +196,8 @@ CSize CD2ItemToolTipCtrl::OnDrawLabel(CDC* pDC, CRect rect, BOOL bCalcOnly)
continue;
}
- strText = attrib.Desc.c_str();
+ uText = utf8::utf8to16(attrib.Desc);
+ strText = reinterpret_cast(uText.c_str());
if (strText.IsEmpty())
{
continue;
@@ -209,12 +216,18 @@ CSize CD2ItemToolTipCtrl::OnDrawLabel(CDC* pDC, CRect rect, BOOL bCalcOnly)
{
CSize prevSizeText = sizeText;
pDC->SetTextColor(colors[BLUE]);
- strText = _T("Ethereal (Cannot be Repaired)");
+
+ std::string u8Text;
+ uText = utf8::utf8to16(d2ce::LocalizationHelpers::GetEtherealStringTxtValue(u8Text));
+ strText = reinterpret_cast(uText.c_str());
if (CurrItem->isSocketed())
{
// Socketed text
+ strText += _T(", ");
+ uText = utf8::utf8to16(d2ce::LocalizationHelpers::GetSocketedStringTxtValue(u8Text));
+ CString socketedText(reinterpret_cast(uText.c_str()));
CString temp;
- temp.Format(_T(", Socketed (%d)"), (int)CurrItem->getDisplayedSocketCount());
+ temp.Format(socketedText, (int)CurrItem->getDisplayedSocketCount());
strText += temp;
}
sizeText = CalcTextSize(pDC, strText, rect, bCalcOnly);
@@ -226,13 +239,19 @@ CSize CD2ItemToolTipCtrl::OnDrawLabel(CDC* pDC, CRect rect, BOOL bCalcOnly)
// Socketed text
CSize prevSizeText = sizeText;
pDC->SetTextColor(colors[BLUE]);
- strText.Format(_T("Socketed (%d)"), (int)CurrItem->getDisplayedSocketCount());
+
+ std::string u8Text;
+ uText = utf8::utf8to16(d2ce::LocalizationHelpers::GetSocketedStringTxtValue(u8Text));
+ CString socketedText(reinterpret_cast(uText.c_str()));
+ CString temp;
+ temp.Format(socketedText, (int)CurrItem->getDisplayedSocketCount());
+ strText = temp;
+
sizeText = CalcTextSize(pDC, strText, rect, bCalcOnly);
sizeText.cy += prevSizeText.cy;
sizeText.cx = std::max(prevSizeText.cx, sizeText.cx);
}
- pDC->SelectObject(pOldFont);
return sizeText;
}
diff --git a/source/D2ItemsForm.cpp b/source/D2ItemsForm.cpp
index e73bbe45..dc1e6c15 100644
--- a/source/D2ItemsForm.cpp
+++ b/source/D2ItemsForm.cpp
@@ -26,7 +26,9 @@
#include "D2AddGemsForm.h"
#include "D2MercenaryForm.h"
#include "D2SharedStashForm.h"
+#include "d2ce/helpers/ItemHelpers.h"
#include
+#include
#ifdef _DEBUG
#define new DEBUG_NEW
@@ -1169,7 +1171,14 @@ CRect CD2ItemsGridStatic::GetInvRect(const d2ce::Item& item) const
CSize itemPos = CSize(cx, cy);
if (GetDlgCtrlID() == IDC_INV_BELT_GRID) // Belt
{
- itemPos = CSize(cx % 4, GridBoxSize.cy - cx / 4 - 1);
+ LONG row = cx / 4 + 1;
+ if( row > GridBoxSize.cy )
+ {
+ // out of bounds
+ return rect;
+ }
+
+ itemPos = CSize(cx % 4, GridBoxSize.cy - row);
}
if (((itemPos.cx + dimension.Width) > GridBoxSize.cx) || ((itemPos.cy + dimension.Height) > GridBoxSize.cy))
@@ -1491,11 +1500,16 @@ void CD2ItemsForm::LoadCorpseItemImages()
{
InvCorpseHandRightBox.SetUseAltImage(IsWeaponII);
InvCorpseHandLeftBox.SetUseAltImage(IsWeaponII);
+ std::string strValue;
+ std::string gender = MainForm.isFemaleCharacter() ? "[fs]" : "[ms]";
+ d2ce::LocalizationHelpers::GetStringTxtValue("Corpse", strValue, gender, "Corpse");
+ auto uName = utf8::utf8to16(strValue);
+ CString windowText(reinterpret_cast(uName.c_str()));
+ CorpseGroupBox.SetWindowText(windowText);
const auto& corpseItems = MainForm.getCorpseItems();
if (corpseItems.empty())
{
- CorpseGroupBox.SetWindowText(_T("No Corpse"));
InvCorpseHeadBox.ShowWindow(SW_HIDE);
InvCorpseNeckBox.ShowWindow(SW_HIDE);
InvCorpseHandRightBox.ShowWindow(SW_HIDE);
@@ -1509,7 +1523,6 @@ void CD2ItemsForm::LoadCorpseItemImages()
return;
}
- CorpseGroupBox.SetWindowText(_T("Corpse"));
InvCorpseHeadBox.ShowWindow(SW_SHOW);
InvCorpseNeckBox.ShowWindow(SW_SHOW);
InvCorpseHandRightBox.ShowWindow(SW_SHOW);
@@ -1639,9 +1652,9 @@ void CD2ItemsForm::LoadCorpseItemImages()
//---------------------------------------------------------------------------
void CD2ItemsForm::LoadMercItemImages()
{
- if (!MainForm.isExpansionCharacter() || !Merc.isHired())
+ if (!MainForm.isExpansionCharacter())
{
- MercGroupBox.SetWindowText(_T("Mercenary Not Hired"));
+ MercGroupBox.ShowWindow(SW_HIDE);
InvMercHeadBox.ShowWindow(SW_HIDE);
InvMercHandRightBox.ShowWindow(SW_HIDE);
InvMercTorsoBox.ShowWindow(SW_HIDE);
@@ -1649,27 +1662,41 @@ void CD2ItemsForm::LoadMercItemImages()
return;
}
- InvMercHeadBox.ShowWindow(SW_SHOW);
- InvMercHandRightBox.ShowWindow(SW_SHOW);
- InvMercTorsoBox.ShowWindow(SW_SHOW);
- InvMercHandLeftBox.ShowWindow(SW_SHOW);
-
- auto sMercClass = d2ce::MercClassNames[static_cast>(Merc.getClass())];
- CString groupStr(sMercClass.c_str());
- if (groupStr.IsEmpty())
+ if (!Merc.isHired())
{
- groupStr = _T("Mercenary Not Hired");
+ InvMercHeadBox.ShowWindow(SW_HIDE);
+ InvMercHandRightBox.ShowWindow(SW_HIDE);
+ InvMercTorsoBox.ShowWindow(SW_HIDE);
+ InvMercHandLeftBox.ShowWindow(SW_HIDE);
}
else
{
- CString attribName(Merc.getAttributeName().c_str());
+ InvMercHeadBox.ShowWindow(SW_SHOW);
+ InvMercHandRightBox.ShowWindow(SW_SHOW);
+ InvMercTorsoBox.ShowWindow(SW_SHOW);
+ InvMercHandLeftBox.ShowWindow(SW_SHOW);
+ }
+
+ auto uName = utf8::utf8to16(Merc.getClassName());
+ CString windowText(reinterpret_cast(uName.c_str()));
+ if (!windowText.IsEmpty())
+ {
+ uName = utf8::utf8to16(Merc.getAttributeName());
+ CString attribName(reinterpret_cast(uName.c_str()));
if (!attribName.IsEmpty())
{
- groupStr += _T(" - ") + attribName;
+ windowText += _T(" - ") + attribName;
}
}
+ else
+ {
+ std::string strValue;
+ d2ce::LocalizationHelpers::GetStringTxtValue("MiniPanelHire", strValue, "Mercenary");
+ uName = utf8::utf8to16(strValue);
+ windowText = reinterpret_cast(uName.c_str());
+ }
- MercGroupBox.SetWindowText(groupStr);
+ MercGroupBox.SetWindowText(windowText);
for (const auto& item : Merc.getItems())
{
@@ -1719,13 +1746,17 @@ void CD2ItemsForm::LoadGolemItemImages()
{
if (!MainForm.isExpansionCharacter() || (MainForm.getCharacterClass() != d2ce::EnumCharClass::Necromancer))
{
- GolemGroupBox.SetWindowText(_T("No Golem"));
+ GolemGroupBox.ShowWindow(SW_HIDE);
InvGolemBox.ShowWindow(SW_HIDE);
return;
}
- InvGolemBox.ShowWindow(SW_SHOW);
- GolemGroupBox.SetWindowText(_T("Iron Golem"));
+ InvGolemBox.ShowWindow(SW_SHOW);
+ std::string strValue;
+ d2ce::LocalizationHelpers::GetStringTxtValue("IronGolem", strValue, "Iron Golem");
+ auto uText = utf8::utf8to16(strValue);
+ CString windowText(reinterpret_cast(uText.c_str()));
+ GolemGroupBox.SetWindowText(windowText);
if (!MainForm.hasGolem())
{
return;
@@ -1747,15 +1778,24 @@ void CD2ItemsForm::LoadGridItemImages()
InvBeltGrid.LoadItemImages();
InvStashGrid.LoadItemImages();
+ std::string strValue;
+ d2ce::LocalizationHelpers::GetStringTxtValue("StrHelp21", strValue, "Belt");
+ auto uName = utf8::utf8to16(strValue);
+ CString windowText(reinterpret_cast(uName.c_str()));
+ BeltGroupBox.SetWindowText(windowText);
+
+ d2ce::LocalizationHelpers::GetStringTxtValue("box", strValue, "Horadric Cube");
+ uName = utf8::utf8to16(strValue);
+ windowText = reinterpret_cast(uName.c_str());
+ CubeGroupBox.SetWindowText(windowText);
if (getHasHoradricCube())
{
- CubeGroupBox.SetWindowText(_T("Cube"));
InvCubeGrid.LoadItemImages();
InvCubeGrid.ShowWindow(SW_SHOW);
}
else
{
- CubeGroupBox.SetWindowText(_T("No Cube"));
+ CubeGroupBox.ShowWindow(SW_HIDE);
InvCubeGrid.ShowWindow(SW_HIDE);
}
}
@@ -2652,6 +2692,12 @@ BOOL CD2ItemsForm::OnInitDialog()
{
__super::OnInitDialog();
+ std::string strValue;
+ d2ce::LocalizationHelpers::GetStringTxtValue("strpanel4", strValue, "Inventory");
+ auto uText = utf8::utf8to16(strValue);
+ CString windowText(reinterpret_cast(uText.c_str()));
+ SetWindowText(windowText);
+
EnableToolTips(TRUE);
CheckToolTipCtrl();
@@ -2669,6 +2715,13 @@ BOOL CD2ItemsForm::OnInitDialog()
auto* pWnd = GetDlgItem(IDC_SHARED_STASH_BUTTON);
if (pWnd != nullptr)
{
+ d2ce::LocalizationHelpers::GetStringTxtValue("stash", strValue, "Stash");
+ uText = utf8::utf8to16(strValue);
+ windowText = reinterpret_cast(uText.c_str());
+ d2ce::LocalizationHelpers::GetStringTxtValue("ConcatenatedStringEnding", strValue, "...");
+ uText = utf8::utf8to16(strValue);
+ windowText += reinterpret_cast(uText.c_str());
+ pWnd->SetWindowText(windowText);
pWnd->EnableWindow(FALSE);
}
}
@@ -2863,6 +2916,8 @@ void CD2ItemsForm::OnContextMenu(CWnd* /*pWnd*/, CPoint point)
bool isGem = !isStackable && !isArmor && !isWeapon && CurrItem->isGem();
bool isPotion = !isStackable && !isArmor && !isWeapon && !isGem && CurrItem->isPotion();
bool isRune = !isStackable && !isArmor && !isWeapon && !isGem && !isPotion && CurrItem->isRune();
+ bool canHaveSockets = CurrItem->canHaveSockets();
+ bool canPersonalize = CurrItem->canPersonalize();
if (isArmor || isWeapon || isStackable)
{
CMenu menu;
@@ -2876,49 +2931,36 @@ void CD2ItemsForm::OnContextMenu(CWnd* /*pWnd*/, CPoint point)
pPopup->DeleteMenu(ID_ITEM_CONTEXT_LOAD, MF_BYCOMMAND);
}
- if (!isArmor && !isWeapon)
+ if (!canHaveSockets || (CurrItem->isSocketed() && (CurrItem->getMaxSocketCount() <= CurrItem->socketCount())))
{
- pPopup->DeleteMenu(ID_ITEM_CONTEXT_FIX, MF_BYCOMMAND);
- pPopup->DeleteMenu(ID_ITEM_CONTEXT_MAXDURABILITY, MF_BYCOMMAND);
- pPopup->DeleteMenu(ID_ITEM_CONTEXT_INDESTRUCTIBLE, MF_BYCOMMAND);
pPopup->DeleteMenu(ID_ITEM_CONTEXT_ADDSOCKET, MF_BYCOMMAND);
pPopup->DeleteMenu(ID_ITEM_CONTEXT_MAXSOCKETS, MF_BYCOMMAND);
+ }
+
+ if (!canPersonalize)
+ {
pPopup->DeleteMenu(ID_ITEM_CONTEXT_PERSONALIZE, MF_BYCOMMAND);
pPopup->DeleteMenu(ID_ITEM_CONTEXT_REMOVE_PERSONALIZATION, MF_BYCOMMAND);
}
else
{
- if (CurrItem->isIndestructible())
- {
- pPopup->DeleteMenu(ID_ITEM_CONTEXT_FIX, MF_BYCOMMAND);
- pPopup->DeleteMenu(ID_ITEM_CONTEXT_MAXDURABILITY, MF_BYCOMMAND);
- pPopup->DeleteMenu(ID_ITEM_CONTEXT_INDESTRUCTIBLE, MF_BYCOMMAND);
- }
-
- if (!CurrItem->canHaveSockets() || (CurrItem->isSocketed() && (CurrItem->getMaxSocketCount() <= CurrItem->socketCount())))
- {
- pPopup->DeleteMenu(ID_ITEM_CONTEXT_ADDSOCKET, MF_BYCOMMAND);
- pPopup->DeleteMenu(ID_ITEM_CONTEXT_MAXSOCKETS, MF_BYCOMMAND);
- }
-
- if (getCharacterVersion() < d2ce::EnumCharVersion::v109)
+ if (CurrItem->isPersonalized())
{
pPopup->DeleteMenu(ID_ITEM_CONTEXT_PERSONALIZE, MF_BYCOMMAND);
- pPopup->DeleteMenu(ID_ITEM_CONTEXT_REMOVE_PERSONALIZATION, MF_BYCOMMAND);
}
else
{
- if (CurrItem->isPersonalized())
- {
- pPopup->DeleteMenu(ID_ITEM_CONTEXT_PERSONALIZE, MF_BYCOMMAND);
- }
- else
- {
- pPopup->DeleteMenu(ID_ITEM_CONTEXT_REMOVE_PERSONALIZATION, MF_BYCOMMAND);
- }
+ pPopup->DeleteMenu(ID_ITEM_CONTEXT_REMOVE_PERSONALIZATION, MF_BYCOMMAND);
}
}
+ if ((!isArmor && !isWeapon) || CurrItem->isIndestructible())
+ {
+ pPopup->DeleteMenu(ID_ITEM_CONTEXT_FIX, MF_BYCOMMAND);
+ pPopup->DeleteMenu(ID_ITEM_CONTEXT_MAXDURABILITY, MF_BYCOMMAND);
+ pPopup->DeleteMenu(ID_ITEM_CONTEXT_INDESTRUCTIBLE, MF_BYCOMMAND);
+ }
+
pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, this);
}
else if (isGem | isPotion | isRune)
diff --git a/source/D2LevelInfoForm.cpp b/source/D2LevelInfoForm.cpp
index d672dc4a..9086ec34 100644
--- a/source/D2LevelInfoForm.cpp
+++ b/source/D2LevelInfoForm.cpp
@@ -23,7 +23,9 @@
#include "D2LevelInfoForm.h"
#include "afxdialogex.h"
#include "D2MainForm.h"
-#include "d2ce\ExperienceConstants.h"
+#include "d2ce/ExperienceConstants.h"
+#include "d2ce/helpers/ItemHelpers.h"
+#include
namespace
{
@@ -79,15 +81,10 @@ namespace
IMPLEMENT_DYNAMIC(CD2LevelInfoForm, CDialogEx)
//---------------------------------------------------------------------------
-CD2LevelInfoForm::CD2LevelInfoForm(CWnd* pParent /*=nullptr*/)
- : CDialogEx(CD2LevelInfoForm::IDD, pParent)
+CD2LevelInfoForm::CD2LevelInfoForm(CD2MainForm& form)
+ : CDialogEx(CD2LevelInfoForm::IDD, (CWnd*)&form), MainForm(form)
{
Modal = FALSE;
- Version = d2ce::APP_CHAR_VERSION;
- if (pParent != nullptr && pParent->IsKindOf(RUNTIME_CLASS(CD2MainForm)))
- {
- Version = ((CD2MainForm*)pParent)->getCharacterVersion();
- }
}
//---------------------------------------------------------------------------
CD2LevelInfoForm::~CD2LevelInfoForm()
@@ -112,10 +109,24 @@ BOOL CD2LevelInfoForm::OnInitDialog()
// setup the paths selection list
LevelInfoGrid.SendMessage(LVM_SETEXTENDEDLISTVIEWSTYLE, 0, LVS_EX_FULLROWSELECT);
- LevelInfoGrid.InsertColumn(0, _T("LEVEL"), LVCFMT_RIGHT, 46);
- LevelInfoGrid.InsertColumn(1, _T("MIN EXP REQ"), LVCFMT_RIGHT, 90);
- LevelInfoGrid.InsertColumn(2, _T("BELT MAX GOLD"), LVCFMT_RIGHT, 110);
- LevelInfoGrid.InsertColumn(3, _T("STASH MAX GOLD"), LVCFMT_RIGHT, 122);
+ std::string strValue;
+ std::u16string uText;
+ d2ce::LocalizationHelpers::GetStringTxtValue("strchrlvl", strValue, "Level");
+ uText = utf8::utf8to16(strValue);
+ LevelInfoGrid.InsertColumn(0, reinterpret_cast(uText.c_str()), LVCFMT_RIGHT, 46);
+
+ d2ce::LocalizationHelpers::GetStringTxtValue("strExpGained", strValue, "Exp Gained");
+ uText = utf8::utf8to16(strValue);
+ LevelInfoGrid.InsertColumn(1, reinterpret_cast(uText.c_str()), LVCFMT_RIGHT, 90);
+
+ d2ce::LocalizationHelpers::GetStringTxtValue("strGoldLabel", strValue, "Gold");
+ uText = utf8::utf8to16(strValue);
+ LevelInfoGrid.InsertColumn(2, reinterpret_cast(uText.c_str()), LVCFMT_RIGHT, 110);
+
+ d2ce::LocalizationHelpers::GetStringTxtValue("strGoldInStash", strValue, "Gold in Stash");
+ uText = utf8::utf8to16(strValue);
+ LevelInfoGrid.InsertColumn(3, reinterpret_cast(uText.c_str()), LVCFMT_RIGHT, 122);
+
FillCells();
return TRUE; // return TRUE unless you set the focus to a control
@@ -124,23 +135,93 @@ BOOL CD2LevelInfoForm::OnInitDialog()
//---------------------------------------------------------------------------
void CD2LevelInfoForm::FillCells()
{
+ auto version = MainForm.getCharacterVersion();
+ auto maxLevel = MainForm.getCharacterMaxLevel();
+
+ std::string strValue;
+ std::u16string uText;
+ TCHAR name[255];
+ LVCOLUMN col;
+ col.mask = LVCF_TEXT;
+ col.pszText = name;
+ col.cchTextMax = sizeof(name) / sizeof(TCHAR);
+ CString localizedString;
+ if (d2ce::LocalizationHelpers::GetStringTxtValue("strchrlvl", strValue))
+ {
+ uText = utf8::utf8to16(strValue);
+ LevelInfoGrid.GetColumn(0, &col);
+ localizedString = reinterpret_cast(uText.c_str());
+ _tcscpy_s(name, sizeof(name) / sizeof(TCHAR), localizedString.GetString());
+ LevelInfoGrid.SetColumn(0, &col);
+ }
+
+ if (d2ce::LocalizationHelpers::GetStringTxtValue("strExpGained", strValue))
+ {
+ uText = utf8::utf8to16(strValue);
+ LevelInfoGrid.GetColumn(1, &col);
+ localizedString = reinterpret_cast(uText.c_str());
+ _tcscpy_s(name, sizeof(name) / sizeof(TCHAR), localizedString.GetString());
+ LevelInfoGrid.SetColumn(1, &col);
+ }
+
+ if (d2ce::LocalizationHelpers::GetStringTxtValue("strGoldLabel", strValue))
+ {
+ uText = utf8::utf8to16(strValue);
+ LevelInfoGrid.GetColumn(2, &col);
+ localizedString = reinterpret_cast(uText.c_str());
+ _tcscpy_s(name, sizeof(name) / sizeof(TCHAR), localizedString.GetString());
+ LevelInfoGrid.SetColumn(2, &col);
+ }
+
+ if (d2ce::LocalizationHelpers::GetStringTxtValue("strGoldInStash", strValue))
+ {
+ auto pos = strValue.find(":");
+ if (pos != strValue.npos)
+ {
+ strValue = strValue.erase(pos);
+ }
+ uText = utf8::utf8to16(strValue);
+ LevelInfoGrid.GetColumn(3, &col);
+ localizedString = reinterpret_cast(uText.c_str());
+ _tcscpy_s(name, sizeof(name) / sizeof(TCHAR), localizedString.GetString());
+ LevelInfoGrid.SetColumn(3, &col);
+ }
+
+ CString text;
+ CStringA textA;
+ CWnd* pWnd = nullptr;
+ if (d2ce::LocalizationHelpers::GetStringTxtValue("ok", strValue))
+ {
+ pWnd = GetDlgItem(IDOK);
+ if (pWnd != nullptr)
+ {
+ pWnd->GetWindowText(text);
+ textA = text;
+ if (textA.CompareNoCase(strValue.c_str()) != 0)
+ {
+ uText = utf8::utf8to16(strValue);
+ pWnd->SetWindowText(reinterpret_cast(uText.c_str()));
+ }
+ }
+ }
+
LevelInfoGrid.DeleteAllItems();
std::uint32_t goldValue = d2ce::GOLD_IN_STASH_LIMIT;
- for (std::uint32_t i = 1; i <= d2ce::NUM_OF_LEVELS; ++i)
+ for (std::uint32_t i = 1; i <= maxLevel; ++i)
{
AddListColData(LevelInfoGrid, i - 1, 0, i);
- AddListColData(LevelInfoGrid, i - 1, 1, d2ce::MinExpRequired[i - 1]);
- AddListColData(LevelInfoGrid, i - 1, 2, i * 10000);
+ AddListColData(LevelInfoGrid, i - 1, 1, MainForm.getCharacterMinExperience(i));
+ AddListColData(LevelInfoGrid, i - 1, 2, std::min(i, 99ui32) * 10000);
- if (Version < d2ce::EnumCharVersion::v110) // 1.00 - 1.09 character
+ if (version < d2ce::EnumCharVersion::v110) // 1.00 - 1.09 character
{
if (i < 31) // 1.00 - 1.09 character
{
goldValue = (i / 10 + 1) * 50000;
}
- else if (Version >= d2ce::EnumCharVersion::v107) // 1.07 - 1.09 character
+ else if (version >= d2ce::EnumCharVersion::v107) // 1.07 - 1.09 character
{
- goldValue = (i / 2 + 1) * 50000;
+ goldValue = (std::min(i, 99ui32) / 2 + 1) * 50000;
}
else // 1.00 - 1.06 character
{
@@ -154,9 +235,9 @@ void CD2LevelInfoForm::FillCells()
//---------------------------------------------------------------------------
INT_PTR CD2LevelInfoForm::DoModal()
{
- Modal = true;
+ Modal = TRUE;
auto ret = __super::DoModal();
- Modal = false;
+ Modal = FALSE;
return ret;
}
//---------------------------------------------------------------------------
@@ -186,12 +267,6 @@ BOOL CD2LevelInfoForm::Show(CWnd* pParent)
{
if (!::IsWindow(GetSafeHwnd()))
{
- Version = d2ce::APP_CHAR_VERSION;
- if (pParent != nullptr && pParent->IsKindOf(RUNTIME_CLASS(CD2MainForm)))
- {
- Version = ((CD2MainForm*)pParent)->getCharacterVersion();
- }
-
Modal = FALSE;
BOOL bCreated = __super::Create(CD2LevelInfoForm::IDD, pParent);
if (!bCreated)
@@ -204,17 +279,13 @@ BOOL CD2LevelInfoForm::Show(CWnd* pParent)
return TRUE;
}
//---------------------------------------------------------------------------
-void CD2LevelInfoForm::ResetVersion(d2ce::EnumCharVersion version)
+void CD2LevelInfoForm::ResetView()
{
if (!::IsWindow(GetSafeHwnd()))
{
return;
}
- if (version != Version)
- {
- Version = version;
- FillCells();
- }
+ FillCells();
}
//---------------------------------------------------------------------------
\ No newline at end of file
diff --git a/source/D2LevelInfoForm.h b/source/D2LevelInfoForm.h
index b8182a73..2ed643be 100644
--- a/source/D2LevelInfoForm.h
+++ b/source/D2LevelInfoForm.h
@@ -20,7 +20,8 @@
#pragma once
-#include "d2ce\CharacterConstants.h"
+#include "D2MainForm.h"
+#include "d2ce/CharacterConstants.h"
#include "resource.h"
//---------------------------------------------------------------------------
@@ -29,7 +30,7 @@ class CD2LevelInfoForm : public CDialogEx
DECLARE_DYNAMIC(CD2LevelInfoForm)
public:
- CD2LevelInfoForm(CWnd* pParent = nullptr); // standard constructor
+ CD2LevelInfoForm(CD2MainForm& form); // standard constructor
virtual ~CD2LevelInfoForm();
// Dialog Data
@@ -50,12 +51,12 @@ class CD2LevelInfoForm : public CDialogEx
virtual BOOL OnInitDialog();
private:
- d2ce::EnumCharVersion Version = d2ce::APP_CHAR_VERSION;
+ CD2MainForm& MainForm;
BOOL Modal = FALSE;
public:
virtual INT_PTR DoModal();
BOOL Show(CWnd* pParentWnd = NULL);
- void ResetVersion(d2ce::EnumCharVersion version);
+ void ResetView();
};
//---------------------------------------------------------------------------
diff --git a/source/D2MainForm.cpp b/source/D2MainForm.cpp
index 4d3b6d2a..c3e5a8cc 100644
--- a/source/D2MainForm.cpp
+++ b/source/D2MainForm.cpp
@@ -31,11 +31,14 @@
#include "D2MercenaryForm.h"
#include "D2ItemsForm.h"
#include "D2SharedStashForm.h"
-#include "d2ce\ExperienceConstants.h"
-#include "d2ce\Constants.h"
+#include "d2ce/ExperienceConstants.h"
+#include "d2ce/Constants.h"
+#include "d2ce/helpers/ItemHelpers.h"
+#include
#include "afxdialogex.h"
#include "resource.h"
#include
+#include
#ifdef _DEBUG
#define new DEBUG_NEW
@@ -43,6 +46,44 @@
namespace
{
+ const std::string& GetResourceHeader()
+ {
+ static std::string resourceHeader;
+ if (resourceHeader.empty())
+ {
+ HINSTANCE hInstance = ::GetModuleHandle(NULL);
+ HRSRC hres = ::FindResource(hInstance, MAKEINTRESOURCE(IDR_RESOURCE_HEADER), _T("TEXT"));
+ if (hres == NULL)
+ {
+ return resourceHeader;
+ }
+
+ DWORD dwSizeBytes = ::SizeofResource(hInstance, hres);
+ if (dwSizeBytes == 0)
+ {
+ return resourceHeader;
+ }
+
+ HGLOBAL hglobal = ::LoadResource(hInstance, hres);
+ if (hglobal == NULL)
+ {
+ return resourceHeader;
+ }
+
+ BYTE* pRes = (BYTE*)LockResource(hglobal);
+ if (pRes == nullptr)
+ {
+ ::FreeResource(hglobal);
+ return resourceHeader;
+ }
+
+ resourceHeader.assign((char*)pRes, dwSizeBytes);
+ ::FreeResource(hglobal);
+ }
+
+ return resourceHeader;
+ }
+
void ScaleImage(CDC* pDC, CBitmap& image, const CRect& rect)
{
BITMAP bmp;
@@ -82,7 +123,7 @@ namespace
button.SetBitmap(image);
}
- static CD2LevelInfoForm s_levelInfo;
+ static CD2LevelInfoForm* s_pLevelInfo = nullptr;
CString ExtractFilePath(LPCTSTR fullPath)
{
if (fullPath == nullptr)
@@ -179,6 +220,97 @@ namespace
return p.replace_extension().c_str();
}
+ bool HasBackupFile(LPCTSTR fullPath)
+ {
+ if (fullPath == nullptr)
+ {
+ return false;
+ }
+
+ CString temp = (LPCTSTR)fullPath;
+ if (temp.IsEmpty())
+ {
+ return false;
+ }
+
+ std::filesystem::path p = fullPath;
+ auto fileExt = p.extension().wstring();
+ if (fileExt == L".d2s")
+ {
+ p.replace_extension();
+ }
+
+ auto parentPath = p.parent_path();
+ auto filenameMatch = p.filename().wstring() + L".";
+ for (const auto& entry : std::filesystem::directory_iterator(parentPath))
+ {
+ if (entry.is_regular_file())
+ {
+ const auto& full_path = entry.path();
+ if (const auto filename = full_path.filename().wstring(); (filename.find(filenameMatch) == 0) && (filename.rfind(L".bak") == (filename.size() - 4)))
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ CString GetLastBackupFile(LPCTSTR fullPath)
+ {
+ if (fullPath == nullptr)
+ {
+ return false;
+ }
+
+ CString temp = (LPCTSTR)fullPath;
+ if (temp.IsEmpty())
+ {
+ return false;
+ }
+
+ std::wstring oldBackupFile;
+ std::set backupFiles;
+
+ std::filesystem::path p = fullPath;
+ auto fileExt = p.extension().wstring();
+ if (fileExt == L"d2s")
+ {
+ p.replace_extension();
+ }
+
+ std::wstring backupExt(L".bak");
+ auto parentPath = p.parent_path();
+ auto filenameMatch = p.filename().wstring() + L".";
+ for (const auto& entry : std::filesystem::directory_iterator(parentPath))
+ {
+ if (entry.is_regular_file())
+ {
+ const auto& full_path = entry.path();
+ if (const auto filename = full_path.filename().wstring(); (filename.find(filenameMatch) == 0) && (filename.rfind(backupExt) == (filename.size() - backupExt.size())))
+ {
+ if (filename.rfind(backupExt) == (filenameMatch.length() - 1))
+ {
+ // old format
+ oldBackupFile = full_path.wstring();
+ }
+ else
+ {
+ backupFiles.insert(full_path.wstring());
+ }
+ }
+ }
+ }
+
+ if (!backupFiles.empty())
+ {
+ return CString(backupFiles.rbegin()->c_str());
+ }
+
+ return CString(oldBackupFile.c_str());
+ }
+
bool FileExists(LPCTSTR fullPath)
{
if (fullPath == nullptr)
@@ -350,6 +482,11 @@ CCharNameEdit::CCharNameEdit()
CCharNameEdit::~CCharNameEdit()
{
}
+//---------------------------------------------------------------------------
+void CCharNameEdit::SetASCIIOnly(BOOL bFlag)
+{
+ m_bASCII = bFlag;
+}
//---------------------------------------------------------------------------
BEGIN_MESSAGE_MAP(CCharNameEdit, CEdit)
@@ -364,7 +501,22 @@ END_MESSAGE_MAP()
//---------------------------------------------------------------------------
void CCharNameEdit::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
- if (isalpha(nChar) || nChar == '_' || nChar == '-')
+ switch (nChar)
+ {
+ case VK_CANCEL:
+ case VK_BACK:
+ case VK_PRIOR:
+ case VK_NEXT:
+ case VK_DELETE:
+ __super::OnChar(nChar, nRepCnt, nFlags);
+ return;
+ }
+
+ if ((nChar >= 'a' && nChar <= 'z') || (nChar >= 'A' && nChar <= 'Z') ||
+ nChar == '_' || nChar == '-' || (!m_bASCII &&
+ (nChar == 0x8A || nChar == 0x8C || nChar == 0x8E
+ || nChar == 0x0A || nChar == 0x9C || nChar == 0x9E || nChar == 0x9F
+ || (nChar >= 0xC0 && nChar <= 0xD0) || (nChar >= 0xD8 && nChar <= 0xF6) || nChar >= 0xF8)))
{
if (nChar == '_' || nChar == '-')
{
@@ -385,10 +537,7 @@ void CCharNameEdit::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
}
}
__super::OnChar(nChar, nRepCnt, nFlags);
- }
- else if (nChar == VK_BACK)
- {
- __super::OnChar(nChar, nRepCnt, nFlags);
+ return;
}
}
//---------------------------------------------------------------------------
@@ -447,29 +596,12 @@ CString CCharNameEdit::GetValidText(LPCTSTR value) const
return _T("");
}
- CString strNewText;
- CString strText(value);
- UINT maxSize = GetLimitText();
-
- // Remove any invalid characters from the number
- for (UINT iPos = 0, nNewLen = 0, NumberOfUnderscores = 0, nLen = strText.GetLength(); iPos < nLen && nNewLen < maxSize; ++iPos)
- {
- TCHAR c = strText[iPos];
-
- if (isalpha(c))
- {
- strNewText += c;
- ++nNewLen;
- }
- else if ((c == '_' || c == '-') && nNewLen != 0 && NumberOfUnderscores < 1)
- {
- strNewText += c;
- ++nNewLen;
- ++NumberOfUnderscores;
- }
- }
-
- return strNewText.TrimRight(_T("_-"));
+ CStringW wValue(value);
+ auto curName = utf8::utf16to8(reinterpret_cast(wValue.GetString()));
+ d2ce::LocalizationHelpers::CheckCharName(curName, (m_bASCII ? true : false));
+ auto uText = utf8::utf8to16(curName);
+ wValue = reinterpret_cast(uText.c_str());
+ return CString(wValue);
}
//---------------------------------------------------------------------------
// CStatsLeftImage
@@ -1050,9 +1182,10 @@ void CD2MainForm::OnSysCommand(UINT nID, LPARAM lParam)
return;
}
- if (::IsWindow(s_levelInfo.GetSafeHwnd()))
+ if((s_pLevelInfo != nullptr) && ::IsWindow(s_pLevelInfo->GetSafeHwnd()))
{
- s_levelInfo.DestroyWindow();
+ s_pLevelInfo->DestroyWindow();
+ s_pLevelInfo = nullptr;
}
}
@@ -1440,14 +1573,14 @@ void CD2MainForm::OnOptionsCheckChar()
CharInfo.fillCharacterStats(cs);
// does a valid level range check
- if (cs.Level < 1 || cs.Level > d2ce::NUM_OF_LEVELS)
+ if (cs.Level < 1 || cs.Level > cs.MaxLevel)
{
bFoundIssue = true;
if (AfxMessageBox(_T("\"Level\" amount exceeds the recommended maximum limit.\n")
_T("Would you like the amount changed to the recommended maximum limit?"),
MB_ICONQUESTION | MB_YESNO) == IDYES)
{
- cs.Level = d2ce::NUM_OF_LEVELS;
+ cs.Level = cs.MaxLevel;
CharInfo.updateCharacterStats(cs);
UpdateCharInfo();
statChanged = true;
@@ -1461,14 +1594,7 @@ void CD2MainForm::OnOptionsCheckChar()
}
// does a level-experience check
- std::uint32_t expLevel = d2ce::NUM_OF_LEVELS;
- std::uint32_t value = (std::uint32_t)ToInt(&Experience);
- // find the correct level
- while ((expLevel > 1) && (value < d2ce::MinExpRequired[expLevel - 1]))
- {
- --expLevel;
- }
-
+ std::uint32_t expLevel = getCharacterLevelFromExperience((std::uint32_t)ToInt(&Experience));
if (expLevel > cs.Level)
{
bFoundIssue = true;
@@ -1489,7 +1615,7 @@ void CD2MainForm::OnOptionsCheckChar()
_T("Would you like experience changed to match your character's level?"),
MB_ICONQUESTION | MB_YESNO) == IDYES)
{
- cs.Experience = d2ce::MinExpRequired[cs.Level - 1];
+ cs.Experience = cs.MinExperienceLevel;
CharInfo.updateCharacterStats(cs);
UpdateCharInfo();
statChanged = true;
@@ -1497,28 +1623,28 @@ void CD2MainForm::OnOptionsCheckChar()
}
// does a level-gold check
- if (ToInt(&GoldInBelt) > cs.getMaxGoldInBelt())
+ if (ToInt(&GoldInBelt) > cs.MaxGoldInBelt)
{
bFoundIssue = true;
if (AfxMessageBox(_T("\"Gold In Belt\" amount exceeds the maximum limit.\n")
_T("Would you like the amount changed to match your character's level?"),
MB_ICONQUESTION | MB_YESNO) == IDYES)
{
- cs.GoldInBelt = cs.getMaxGoldInBelt();
+ cs.GoldInBelt = cs.MaxGoldInBelt;
CharInfo.updateCharacterStats(cs);
UpdateCharInfo();
statChanged = true;
}
}
- if (ToInt(&GoldInStash) > cs.getMaxGoldInStash())
+ if (ToInt(&GoldInStash) > cs.MaxGoldInBelt)
{
bFoundIssue = true;
if (AfxMessageBox(_T("\"Gold In Stash\" amount exceeds the maximum limit.\n")
_T("Would you like the amount changed to match your character's level?"),
MB_ICONQUESTION | MB_YESNO) == IDYES)
{
- cs.GoldInStash = cs.getMaxGoldInStash();
+ cs.GoldInStash = cs.MaxGoldInStash;
CharInfo.updateCharacterStats(cs);
UpdateCharInfo();
statChanged = true;
@@ -1656,7 +1782,7 @@ void CD2MainForm::OnOptionsCheckChar()
if (expLevel > cs.Level)
{
bFoundIssue = true;
- if (expLevel > d2ce::NUM_OF_LEVELS)
+ if (expLevel > cs.MaxLevel)
{
// stats do not make sense
if (AfxMessageBox(_T("\"Total Stat Points\" is higher then what can be achieved in the game.\n")
@@ -1725,7 +1851,7 @@ void CD2MainForm::OnOptionsCheckChar()
if (expLevel > cs.Level)
{
bFoundIssue = true;
- if (expLevel > d2ce::NUM_OF_LEVELS)
+ if (expLevel > cs.MaxLevel)
{
// stats do not make sense
if (AfxMessageBox(_T("\"Total Skill Points\" is higher then what can be achieved in the game.\n")
@@ -2076,7 +2202,10 @@ void CD2MainForm::DisplayCharInfo()
auto vesion = CharInfo.getVersion();
CharStatusLadder.EnableWindow(vesion >= d2ce::EnumCharVersion::v110 ? TRUE : FALSE);
CharStatusExpansion.EnableWindow((vesion < d2ce::EnumCharVersion::v107 || vesion == d2ce::EnumCharVersion::v108) ? FALSE : TRUE);
- s_levelInfo.ResetVersion(CharInfo.getVersion());
+ if (s_pLevelInfo != nullptr)
+ {
+ s_pLevelInfo->ResetView();
+ }
}
//---------------------------------------------------------------------------
void CD2MainForm::UpdateCharInfo()
@@ -2089,7 +2218,7 @@ void CD2MainForm::UpdateCharInfo()
auto strValue = ToStdString(&CharName);
if (_stricmp(strValue.c_str(), CharInfo.getName().data()) != 0)
{
- SetText(&CharName, CharInfo.getName().data());
+ SetUTF8Text(&CharName, CharInfo.getName().data());
if (_stricmp(CharInfo.getName().data(), Bs.Name.data()) == 0)
{
auto iter = CtrlEditted.find(CharName.GetDlgCtrlID());
@@ -2814,6 +2943,8 @@ void CD2MainForm::EnableCharInfoBox(BOOL bEnable)
GoldInStash.SetWindowText(_T(""));
CharTitle.SetCurSel(-1);
CharTitle.ResetContent();
+ Difficulty.SetCurSel(-1);
+ Difficulty.ResetContent();
// Update Static so we refresh properly
SetText(&NextExperience, _T(""));
@@ -2879,10 +3010,13 @@ void CD2MainForm::OpenFile(LPCTSTR filename)
return;
}
- CStringA newPathNameA(filename);
+ if (filename == nullptr)
+ {
+ return;
+ }
// return if open not successful
- if (!CharInfo.open(newPathNameA, false))
+ if (!CharInfo.open(filename, false))
{
CString errorMsg(CharInfo.getLastError().message().c_str());
if (errorMsg.IsEmpty())
@@ -2902,29 +3036,17 @@ void CD2MainForm::OpenFile(LPCTSTR filename)
if (AfxMessageBox(_T("Character File checksum is not valid.\nDo you wish to correct it now?"), MB_ICONERROR | MB_YESNO) == IDYES)
{
// The checksum was updated on load, so just save the file
+ CWaitCursor wait;
CharInfo.save();
}
}
CheckFileSize();
- CurPathName = filename;
+ CurPathName = CharInfo.getPath().wstring().c_str();
EnableCharInfoBox(TRUE);
- CString backupname;
- if (CharInfo.is_json())
- {
- backupname = CurPathName + _T(".bak");
- }
- else
- {
- backupname = ChangeFileExt(CurPathName, _T(".bak"));
- }
-
- if (FileExists(backupname))
- {
- hasBackupFile = true;
- }
+ hasBackupFile = HasBackupFile(CurPathName);
CharInfo.fillBasicStats(Bs);
CharInfo.fillCharacterStats(Cs);
@@ -2961,7 +3083,10 @@ int CD2MainForm::DoFileCloseAction()
}
CharInfo.close();
- s_levelInfo.ResetVersion(d2ce::EnumCharVersion::v110);
+ if (s_pLevelInfo != nullptr)
+ {
+ s_pLevelInfo->ResetView();
+ }
CurPathName.Empty();
Initialize();
@@ -2975,6 +3100,7 @@ int CD2MainForm::DoFileCloseAction()
//---------------------------------------------------------------------------
void CD2MainForm::OnFileSave()
{
+ CWaitCursor wait;
if (BackupChar)
{
WriteBackupFile();
@@ -2995,8 +3121,7 @@ void CD2MainForm::OnFileSave()
return;
}
- CurPathName = CharInfo.getPathName();
-
+ CurPathName = CharInfo.getPath().wstring().c_str();
if (static_cast(CharInfo.getLastError().value()) == d2ce::CharacterErrc::AuxFileRenameError)
{
CString errorMsg(CharInfo.getLastError().message().c_str());
@@ -3033,21 +3158,22 @@ void CD2MainForm::OnFileSaveAs()
return;
}
- CStringA jsonPathA(CurPathName);
if (BackupChar)
{
- std::filesystem::path p = CurPathName.GetString();
+ std::filesystem::path p(CurPathName.GetString());
p.replace_extension();
- p = p.replace_filename(CharInfo.getName().data()).string() + ".d2s";
+ p.replace_filename(std::filesystem::u8path(CharInfo.getName().data()));
+ p.replace_extension(".d2s");
if (std::filesystem::exists(p))
{
- CString newD2SPath(p.string().c_str());
+ CString newD2SPath(p.wstring().c_str());
CString backupname(ChangeFileExt(newD2SPath, _T(".bak")));
CopyFile(newD2SPath, backupname, false);
}
}
bool bSuccess = true;
+ CWaitCursor wait;
if (!CharInfo.saveAsD2s())
{
bSuccess = false;
@@ -3063,7 +3189,7 @@ void CD2MainForm::OnFileSaveAs()
OnFileClose();
// return if open not successful
- if (!CharInfo.open(jsonPathA, false))
+ if (!CharInfo.open(CurPathName.GetString(), false))
{
return;
}
@@ -3071,14 +3197,10 @@ void CD2MainForm::OnFileSaveAs()
CheckFileSize();
- CurPathName = CharInfo.getPathName();
+ CurPathName = CharInfo.getPath().wstring().c_str();
EnableCharInfoBox(TRUE);
- if (FileExists(ChangeFileExt(CurPathName, _T(".bak"))))
- {
- hasBackupFile = true;
- }
-
+ hasBackupFile = HasBackupFile(CurPathName);
CharInfo.fillBasicStats(Bs);
CharInfo.fillCharacterStats(Cs);
DisplayCharInfo();
@@ -3105,8 +3227,9 @@ void CD2MainForm::ExportAsJson(bool bSerializedFormat)
return;
}
- CString filename(CharInfo.getName().data());
- filename += ".json";
+ auto uName = utf8::utf8to16(CharInfo.getName().data());
+ CString filename(reinterpret_cast(uName.c_str()));
+ filename += _T(".json");
CFileDialog fileDialog(FALSE, _T("json"), filename,
OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,
@@ -3117,9 +3240,10 @@ void CD2MainForm::ExportAsJson(bool bSerializedFormat)
return;
}
- CStringA jsonFileName(fileDialog.GetPathName());
+ CWaitCursor wait;
+ auto jsonfilename = utf8::utf16to8(reinterpret_cast(fileDialog.GetPathName().GetString()));
std::FILE* jsonFile = NULL;
- fopen_s(&jsonFile, jsonFileName.GetString(), "wb");
+ _wfopen_s(&jsonFile, fileDialog.GetPathName(), L"wb");
std::rewind(jsonFile);
auto output = CharInfo.asJson(bSerializedFormat);
@@ -3201,10 +3325,10 @@ void CD2MainForm::OnOptionsMaxEverything()
d2ce::CharStats cs;
CharInfo.fillCharacterStats(cs);
- cs.Level = d2ce::NUM_OF_LEVELS;
- cs.Experience = d2ce::MAX_EXPERIENCE;
- cs.GoldInBelt = cs.getMaxGoldInBelt();
- cs.GoldInStash = cs.getMaxGoldInStash(CharInfo.getVersion());
+ cs.Level = cs.MaxLevel;
+ cs.Experience = cs.MaxExperience;
+ cs.GoldInBelt = cs.MaxGoldInBelt;
+ cs.GoldInStash = cs.MaxGoldInStash;
CharInfo.updateCharacterStats(cs);
UpdateCharInfo();
@@ -3287,7 +3411,7 @@ void CD2MainForm::SetupBasicStats()
CharInfo.fillBasicStats(bs);
// display character stats
- SetText(&CharName, &bs.Name[0]);
+ SetUTF8Text(&CharName, &bs.Name[0]);
if (bs.isHardcoreCharacter())
{
@@ -3314,7 +3438,7 @@ void CD2MainForm::SetupBasicStats()
UpdateTitleDisplay();
UpdateClassDisplay();
- Difficulty.SetCurSel(static_cast>(bs.DifficultyLastPlayed));
+ UpdateDifficultyDisplay();
UpdateStartingActDisplay();
}
//---------------------------------------------------------------------------
@@ -3401,8 +3525,11 @@ void CD2MainForm::UpdateAppTitle()
}
switch (CharInfo.getVersion())
{
+ case d2ce::EnumCharVersion::v115:
+ newAppTitle += _T(" (Version 1.0.x - 1.1.x)");
+ break;
case d2ce::EnumCharVersion::v110:
- newAppTitle += _T(" (Version 1.10-1.14d)");
+ newAppTitle += _T(" (Version 1.10 - 1.14d)");
break;
case d2ce::EnumCharVersion::v109:
newAppTitle += _T(" (Version 1.09)");
@@ -3424,35 +3551,16 @@ void CD2MainForm::UpdateAppTitle()
//---------------------------------------------------------------------------
void CD2MainForm::UpdateClassDisplay()
{
- // Check if we need to add before selected class
- if (CharInfo.isExpansionCharacter())
+ auto curClass = CharInfo.getClass();
+ CharClass.ResetContent();
+ std::u16string uText;
+ for (const auto& type : d2ce::LocalizationHelpers::GetCharacterTypes(CharInfo.isExpansionCharacter()))
{
- if (CharClass.GetCount() < d2ce::NUM_OF_CLASSES)
- {
- // add the expansion set characters to combo box component
- CharClass.AddString(_T("Druid"));
- CharClass.AddString(_T("Assassin"));
- }
+ uText = utf8::utf8to16(type);
+ CharClass.AddString(CString(reinterpret_cast(uText.c_str())));
}
- CharClass.SetCurSel(static_cast>(CharInfo.getClass()));
-
- // Check if we need to remove after selecting class
- if (!CharInfo.isExpansionCharacter())
- {
- // remove expansion set classes from combo box component
- auto pos = CharClass.FindStringExact(0, _T("Druid"));
- if (pos != CB_ERR)
- {
- CharClass.DeleteString(pos);
- }
-
- pos = CharClass.FindStringExact(0, _T("Assassin"));
- if (pos != CB_ERR)
- {
- CharClass.DeleteString(pos);
- }
- }
+ CharClass.SetCurSel(static_cast>(curClass));
}
//---------------------------------------------------------------------------
/*
@@ -3468,69 +3576,35 @@ void CD2MainForm::UpdateTitleDisplay()
}
CharTitle.ResetContent();
-
- const char** pValidTitles = nullptr;
- switch (static_cast(CharClass.GetCurSel()))
+
+ std::u16string uText;
+ for (const auto& str : d2ce::LocalizationHelpers::GetCharacterTitles(CharInfo.isFemaleCharacter(), CharInfo.isHardcoreCharacter(), CharInfo.isExpansionCharacter()))
{
- case d2ce::EnumCharClass::Amazon:
- case d2ce::EnumCharClass::Assassin:
- case d2ce::EnumCharClass::Sorceress: // add titles for female characters
- if (CharInfo.isExpansionCharacter())
- {
- if (CharInfo.isHardcoreCharacter())
- {
- pValidTitles = HardcoreExpansionTitle;
- }
- else
- {
- pValidTitles = FemaleExpansionTitle;
- }
- }
- else if (CharInfo.isHardcoreCharacter())
+ if (str.empty())
{
- pValidTitles = FemaleHardcoreTitle;
+ CharTitle.AddString(_T(""));
+ continue;
}
- else
- {
- pValidTitles = FemaleTitle;
- }
- break;
- case d2ce::EnumCharClass::Barbarian:
- case d2ce::EnumCharClass::Druid:
- case d2ce::EnumCharClass::Necromancer:
- case d2ce::EnumCharClass::Paladin: // add titles for male characters
- default:
- if (CharInfo.isExpansionCharacter())
- {
- if (CharInfo.isHardcoreCharacter())
- {
- pValidTitles = HardcoreExpansionTitle;
- }
- else
- {
- pValidTitles = MaleExpansionTitle;
- }
- }
- else if (CharInfo.isHardcoreCharacter())
- {
- pValidTitles = MaleHardcoreTitle;
- }
- else
- {
- pValidTitles = MaleTitle;
- }
- break;
+ uText = utf8::utf8to16(str);
+ CharTitle.AddString(CString(reinterpret_cast(uText.c_str())));
}
- if (pValidTitles != nullptr)
+ CharTitle.SetCurSel(curSel);
+}
+//---------------------------------------------------------------------------
+void CD2MainForm::UpdateDifficultyDisplay()
+{
+ std::string strValue;
+ static std::initializer_list all_diff = { d2ce::EnumDifficulty::Normal, d2ce::EnumDifficulty::Nightmare, d2ce::EnumDifficulty::Hell };
+ Difficulty.ResetContent();
+ for (auto diff : all_diff)
{
- for (std::uint32_t i = 0; i < NUM_OF_TITLES; i++)
- {
- CharTitle.AddString(CString(pValidTitles[i]));
- }
+ d2ce::LocalizationHelpers::GetDifficultyStringTxtValue(diff, strValue);
+ auto uName = utf8::utf8to16(strValue);
+ Difficulty.AddString(reinterpret_cast(uName.c_str()));
}
- CharTitle.SetCurSel(curSel);
+ Difficulty.SetCurSel(static_cast>(CharInfo.getDifficultyLastPlayed()));
}
//---------------------------------------------------------------------------
void CD2MainForm::UpdateStartingActDisplay()
@@ -3647,13 +3721,19 @@ void CD2MainForm::WriteBackupFile()
{
CStringA oldPathNameA(CurPathName);
CString backupname;
+
+ auto now = std::chrono::system_clock::now();
+ auto UTC = std::chrono::duration_cast(now.time_since_epoch()).count();
+ auto ext = "." + std::to_string(UTC) + ".bak";
+ CString backupExt(ext.c_str());
+
if (CharInfo.is_json())
{
- backupname = CurPathName + _T(".bak");
+ backupname = CurPathName + backupExt;
}
else
{
- backupname = ChangeFileExt(CurPathName, _T(".bak"));
+ backupname = ChangeFileExt(CurPathName, backupExt);
}
if (CopyFile(CurPathName, backupname, false))
@@ -3728,7 +3808,7 @@ void CD2MainForm::OnEnKillfocusCharName()
if (FileExists(newFileName) && Editted)
{
AfxMessageBox(_T("A file with that name already exists. Please select another name."), MB_OK | MB_ICONEXCLAMATION);
- SetText(&CharName, prev_name.c_str());
+ SetText(&CharName, prev_name);
return;
}
@@ -3830,7 +3910,7 @@ void CD2MainForm::OnEnKillfocusCharLevel()
{
d2ce::CharStats cs;
CharInfo.fillCharacterStats(cs);
- std::uint32_t level = std::min(std::max(ToInt(&CharLevel), std::uint32_t(1)), d2ce::NUM_OF_LEVELS);
+ std::uint32_t level = std::min(std::max(ToInt(&CharLevel), std::uint32_t(1)), cs.MaxLevel);
if (level != cs.Level)
{
cs.Level = level;
@@ -4029,7 +4109,7 @@ void CD2MainForm::OnEnKillfocusCharExperience()
{
d2ce::CharStats cs;
CharInfo.fillCharacterStats(cs);
- cs.Experience = std::min(ToInt(&Experience), d2ce::MAX_EXPERIENCE);
+ cs.Experience = std::min(ToInt(&Experience), CharInfo.getMaxExperience());
CharInfo.updateCharacterStats(cs);
UpdateCharInfo();
}
@@ -4070,7 +4150,8 @@ void CD2MainForm::OnEnKillfocusGoldInStash()
//---------------------------------------------------------------------------
std::string CD2MainForm::ToStdString(const CWnd* Sender) const
{
- return (LPCSTR)CStringA(ToText(Sender));
+ CStringW wValue(ToText(Sender));
+ return utf8::utf16to8(reinterpret_cast(wValue.GetString()));
}
//---------------------------------------------------------------------------
CString CD2MainForm::ToText(const CWnd* Sender) const
@@ -4097,6 +4178,17 @@ CStringA CD2MainForm::ToTextA(const CWnd* Sender) const
return CStringA(ToText(Sender));
}
//---------------------------------------------------------------------------
+void CD2MainForm::SetText(CWnd* Sender, const std::string& newValue)
+{
+ SetUTF8Text(Sender, newValue.c_str());
+}
+//---------------------------------------------------------------------------
+void CD2MainForm::SetUTF8Text(CWnd* Sender, const char* newValue)
+{
+ auto uText = utf8::utf8to16(newValue);
+ SetText(Sender, reinterpret_cast(uText.c_str()));
+}
+//---------------------------------------------------------------------------
void CD2MainForm::SetText(CWnd* Sender, const char* newValue)
{
if (Sender->IsKindOf(RUNTIME_CLASS(CEdit)) || Sender->IsKindOf(RUNTIME_CLASS(CStatic)))
@@ -4202,6 +4294,7 @@ void CD2MainForm::SetInt(CWnd* Sender, std::uint32_t newValue)
{
if (Sender->IsKindOf(RUNTIME_CLASS(CEdit)) || Sender->IsKindOf(RUNTIME_CLASS(CStatic)))
{
+ std::uint32_t value;
switch (Sender->GetDlgCtrlID())
{
case IDC_CUR_LIFE:
@@ -4213,13 +4306,14 @@ void CD2MainForm::SetInt(CWnd* Sender, std::uint32_t newValue)
newValue >>= 8; // shift right 8 bits for actual value
break;
case IDC_CHAR_LEVEL:
- if (newValue + 1 <= d2ce::NUM_OF_LEVELS)
+ value = getCharacterNextExperience(newValue);
+ if (value == MAXUINT32)
{
- SetInt(&NextExperience, d2ce::MinExpRequired[newValue]);
+ SetText(&NextExperience, _T("NONE"));
}
else
{
- SetText(&NextExperience, _T("NONE"));
+ SetInt(&NextExperience, value);
}
break;
}
@@ -4287,41 +4381,44 @@ void CD2MainForm::OnOptionsRestoreChar()
return;
}
- hasBackupFile = false;
- CStringA origNameA(ExtractFileName(RemoveFileExtFromPath(CurPathName)));
- CStringA oldPathNameA(CurPathName);
- CStringA backupname;
- if (CharInfo.is_json())
+ CString curPathName = CurPathName;
+ CString backupname = GetLastBackupFile(CurPathName);
+ if (backupname.IsEmpty())
{
- backupname = CurPathName + _T(".bak");
- }
- else
- {
- backupname = ChangeFileExt(CurPathName, _T(".bak"));
+ return;
}
Editted = false;
DoFileCloseAction();
- // rename temp file to character file
- if (std::remove((LPCSTR)oldPathNameA))
+ try
+ {
+ std::filesystem::remove(curPathName.GetString());
+
+ try
+ {
+ // rename temp file to character file
+ std::filesystem::rename(backupname.GetString(), curPathName.GetString());
+ }
+ catch (std::filesystem::filesystem_error const&)
+ {
+ CString msg;
+ msg.Format(_T("Failed to rename backup character file: %s"), backupname.GetString());
+ AfxMessageBox(CString(msg), MB_OK | MB_ICONERROR);
+ return;
+ }
+ }
+ catch (std::filesystem::filesystem_error const&)
{
CString msg;
- msg.Format(_T("Failed to delete existing character file: %s"), CurPathName.GetString());
+ msg.Format(_T("Failed to delete existing character file: %s"), curPathName.GetString());
AfxMessageBox(msg, MB_OK | MB_ICONERROR);
// just reopen it again
}
- else if (std::rename((LPCSTR)backupname, (LPCSTR)oldPathNameA))
- {
- CStringA msg;
- msg.Format("Failed to rename backup character file: %s", backupname.GetString());
- AfxMessageBox(CString(msg), MB_OK | MB_ICONERROR);
- return;
- }
// return if open not successful
- if (!CharInfo.open(oldPathNameA))
+ if (!CharInfo.open(curPathName.GetString()))
{
CString errorMsg(CharInfo.getLastError().message().c_str());
if (errorMsg.IsEmpty())
@@ -4336,8 +4433,12 @@ void CD2MainForm::OnOptionsRestoreChar()
}
Initialize();
+
+ CurPathName = CharInfo.getPath().wstring().c_str();
EnableCharInfoBox(TRUE);
+ hasBackupFile = HasBackupFile(CurPathName);
+
CharInfo.fillBasicStats(Bs);
CharInfo.fillCharacterStats(Cs);
DisplayCharInfo();
@@ -4355,7 +4456,13 @@ void CD2MainForm::OnUpdateOptionsRestoreChar(CCmdUI* pCmdUI)
//---------------------------------------------------------------------------
void CD2MainForm::OnViewLevelReq()
{
- s_levelInfo.Show(this);
+ static CD2LevelInfoForm levelInfo(*this);
+ if (s_pLevelInfo == nullptr)
+ {
+ s_pLevelInfo = &levelInfo;
+ }
+
+ s_pLevelInfo->Show();
}
//---------------------------------------------------------------------------
void CD2MainForm::OnOptionsGpsConvertor()
@@ -4613,6 +4720,36 @@ std::uint32_t CD2MainForm::getCharacterLevel() const
return CharInfo.getLevel();
}
//---------------------------------------------------------------------------
+std::uint32_t CD2MainForm::getCharacterMaxLevel() const
+{
+ return CharInfo.getMaxLevel();
+}
+//---------------------------------------------------------------------------
+std::uint32_t CD2MainForm::getCharacterMaxExperience() const
+{
+ return CharInfo.getMaxExperience();
+}
+//---------------------------------------------------------------------------
+std::uint32_t CD2MainForm::getCharacterMinExperience(std::uint32_t level) const
+{
+ return CharInfo.getMinExperience(level);
+}
+//---------------------------------------------------------------------------
+std::uint32_t CD2MainForm::getCharacterNextExperience(std::uint32_t level) const
+{
+ return CharInfo.getNextExperience(level);
+}
+//---------------------------------------------------------------------------
+std::uint32_t CD2MainForm::getCharacterLevelFromExperience() const
+{
+ return CharInfo.getLevelFromExperience();
+}
+//---------------------------------------------------------------------------
+std::uint32_t CD2MainForm::getCharacterLevelFromExperience(std::uint32_t experience) const
+{
+ return CharInfo.getLevelFromExperience(experience);
+}
+//---------------------------------------------------------------------------
std::uint32_t CD2MainForm::getWeaponSet() const
{
return CharInfo.getWeaponSet();
@@ -4623,6 +4760,11 @@ bool CD2MainForm::isExpansionCharacter() const
return CharInfo.isExpansionCharacter();
}
//---------------------------------------------------------------------------
+bool CD2MainForm::isFemaleCharacter() const
+{
+ return CharInfo.isFemaleCharacter();
+}
+//---------------------------------------------------------------------------
d2ce::EnumAct CD2MainForm::getLastAct() const
{
return CharInfo.getLastAct();
@@ -4630,13 +4772,13 @@ d2ce::EnumAct CD2MainForm::getLastAct() const
//---------------------------------------------------------------------------
std::uint32_t CD2MainForm::getSkillPointsEarned() const
{
- std::uint32_t curLevel = std::min(ToInt(&CharLevel), d2ce::NUM_OF_LEVELS);
+ std::uint32_t curLevel = std::min(ToInt(&CharLevel), CharInfo.getMaxLevel());
return CharInfo.getSkillPointsEarned(curLevel);
}
//---------------------------------------------------------------------------
std::uint32_t CD2MainForm::getStatPointsEarned() const
{
- std::uint32_t curLevel = std::min(ToInt(&CharLevel), d2ce::NUM_OF_LEVELS);
+ std::uint32_t curLevel = std::min(ToInt(&CharLevel), CharInfo.getMaxLevel());
return CharInfo.getStatPointsEarned(curLevel);
}
//---------------------------------------------------------------------------
@@ -4689,6 +4831,59 @@ void CD2MainForm::setWaypoints(d2ce::EnumDifficulty difficulty, std::uint64_t ne
StatsChanged();
}
//---------------------------------------------------------------------------
+bool CD2MainForm::getSkillBitmap(const d2ce::SkillType& skill, CBitmap& bitmap) const
+{
+ CStringA skillIcon;
+ if (skill.classInfo.has_value())
+ {
+ std::stringstream ss;
+ ss << d2ce::CharClassHelper::getClassCode(skill.classInfo.value().charClass);
+ ss << std::setw(2) << std::setfill('0') << skill.classInfo.value().iconIndex;
+ skillIcon = ss.str().c_str();
+ }
+ else
+ {
+ skillIcon = "UNK";
+ }
+
+ static const std::string& resourceHeader = GetResourceHeader();
+ if (resourceHeader.empty())
+ {
+ return false;
+ }
+
+ std::string regexStr;
+ {
+ std::stringstream ss;
+ ss << "^[\\s]*#define\\s+IDB_SKILL_";
+ ss << skillIcon.MakeUpper().GetString();
+ ss << "\\s+([0-9]+)\\s*$";
+ regexStr = ss.str();
+ }
+ std::regex regexDefine(regexStr, std::regex::ECMAScript | std::regex::icase);
+ std::cmatch m;
+ auto pStr = resourceHeader.c_str();
+ if (std::regex_search(pStr, m, regexDefine))
+ {
+ bitmap.Attach(::LoadBitmap(AfxGetResourceHandle(), MAKEINTRESOURCE(atoi(m[1].str().c_str()))));
+ return bitmap.GetSafeHandle() == NULL ? false : true;
+ }
+ else
+ {
+ std::stringstream ss;
+ ss << "^[\\s]*#define\\s+IDB_SKILL_UNK";
+ ss << "\\s+([0-9]+)\\s*$";
+ std::regex regexDefineBase(ss.str(), std::regex::ECMAScript | std::regex::icase);
+ if (std::regex_search(pStr, m, regexDefineBase))
+ {
+ bitmap.Attach(::LoadBitmap(AfxGetResourceHandle(), MAKEINTRESOURCE(atoi(m[1].str().c_str()))));
+ return bitmap.GetSafeHandle() == NULL ? false : true;
+ }
+ }
+
+ return false;
+}
+//---------------------------------------------------------------------------
std::array& CD2MainForm::getSkills()
{
return CharInfo.getSkills();
@@ -4712,6 +4907,11 @@ std::uint32_t CD2MainForm::getSkillChoices() const
return CharInfo.getSkillChoices();
}
//---------------------------------------------------------------------------
+bool CD2MainForm::getSkillBonusPoints(std::vector& points) const
+{
+ return CharInfo.getSkillBonusPoints(points);
+}
+//---------------------------------------------------------------------------
const std::vector>& CD2MainForm::getGPSs()
{
return CharInfo.getGPSs();
@@ -4912,42 +5112,10 @@ bool CD2MainForm::getItemBitmap(const d2ce::Item& item, CBitmap& bitmap) const
}
}
- static std::string resourceHeader;
+ static const std::string& resourceHeader = GetResourceHeader();
if (resourceHeader.empty())
{
- HINSTANCE hInstance = ::GetModuleHandle(NULL);
- HRSRC hres = ::FindResource(hInstance, MAKEINTRESOURCE(IDR_RESOURCE_HEADER), _T("TEXT"));
- if (hres == NULL)
- {
- return false;
- }
-
- DWORD dwSizeBytes = ::SizeofResource(hInstance, hres);
- if (dwSizeBytes == 0)
- {
- return false;
- }
-
- HGLOBAL hglobal = ::LoadResource(hInstance, hres);
- if (hglobal == NULL)
- {
- return false;
- }
-
- BYTE* pRes = (BYTE*)LockResource(hglobal);
- if (pRes == nullptr)
- {
- ::FreeResource(hglobal);
- return false;
- }
-
- resourceHeader.assign((char*)pRes, dwSizeBytes);
- ::FreeResource(hglobal);
-
- if (resourceHeader.empty())
- {
- return false;
- }
+ return false;
}
std::string regexStr;
diff --git a/source/D2MainForm.h b/source/D2MainForm.h
index 59719b57..d7afd615 100644
--- a/source/D2MainForm.h
+++ b/source/D2MainForm.h
@@ -20,7 +20,7 @@
#pragma once
-#include "d2ce\Character.h"
+#include "d2ce/Character.h"
#include "MainFormConstants.h"
#include "resource.h"
#include
@@ -34,6 +34,7 @@ class CCharNameEdit : public CEdit
public:
virtual ~CCharNameEdit();
+ void SetASCIIOnly(BOOL bFlag);
// Generated message map functions
protected:
@@ -48,6 +49,8 @@ class CCharNameEdit : public CEdit
protected:
CString GetValidText(LPCTSTR value) const;
+ BOOL m_bASCII = FALSE;
+ UINT surrugate = 0;
};
class CStatsLeftImage : public CStatic
@@ -319,6 +322,7 @@ class CD2MainForm : public CDialogEx
void UpdateAppTitle();
void UpdateClassDisplay();
void UpdateTitleDisplay();
+ void UpdateDifficultyDisplay();
void UpdateStartingActDisplay();
void WriteBackupFile();
void SetupBasicStats();
@@ -327,11 +331,13 @@ class CD2MainForm : public CDialogEx
bool CheckFileSize();
void CheckStatsLeft();
- std::string ToStdString(const CWnd* Sender) const;
- CString ToText(const CWnd* Sender) const;
- CStringA ToTextA(const CWnd* Sender) const;
- void SetText(CWnd* Sender, const char* newValue);
- void SetText(CWnd* Sender, const wchar_t* newValue);
+ std::string ToStdString(const CWnd* Sender) const; // UTF-8
+ CString ToText(const CWnd* Sender) const; // UTF-16
+ CStringA ToTextA(const CWnd* Sender) const; // ANSI
+ void SetText(CWnd* Sender, const std::string& newValue); // UTF-8
+ void SetUTF8Text(CWnd* Sender, const char* newValue); // UTF-8
+ void SetText(CWnd* Sender, const char* newValue); // ANSI
+ void SetText(CWnd* Sender, const wchar_t* newValue); // UTF-16
std::uint32_t ToInt(const CWnd* Sender) const;
void SetInt(CWnd* Sender, std::uint32_t newValue);
@@ -349,10 +355,17 @@ class CD2MainForm : public CDialogEx
d2ce::EnumDifficulty getDifficultyLastPlayed() const;
d2ce::EnumAct getStartingAct() const;
std::uint32_t getCharacterLevel() const;
+ std::uint32_t getCharacterMaxLevel() const;
+ std::uint32_t getCharacterMaxExperience() const;
+ std::uint32_t getCharacterMinExperience(std::uint32_t level) const;
+ std::uint32_t getCharacterNextExperience(std::uint32_t level) const;
+ std::uint32_t getCharacterLevelFromExperience() const;
+ std::uint32_t getCharacterLevelFromExperience(std::uint32_t experience) const;
std::uint32_t getWeaponSet() const;
- bool isExpansionCharacter() const;
+ bool isExpansionCharacter() const;
+ bool isFemaleCharacter() const;
d2ce::EnumAct getLastAct() const;
uint32_t getSkillPointsEarned() const;
@@ -378,12 +391,15 @@ class CD2MainForm : public CDialogEx
void setWaypoints(d2ce::EnumDifficulty difficulty, std::uint64_t newvalue);
// Skills
+ bool getSkillBitmap(const d2ce::SkillType& skill, CBitmap& bitmap) const;
std::array& getSkills();
void updateSkills(const std::array& updated_skills, std::uint32_t skillChoices);
std::uint32_t getSkillPointsUsed() const;
std::uint32_t getSkillChoices() const;
+ bool getSkillBonusPoints(std::vector& points) const;
+
// Items
const std::vector>& getGPSs();
size_t convertGPSs(const std::array& existingGem, const std::array& desiredGem, d2ce::ItemFilter filter = d2ce::ItemFilter());
diff --git a/source/D2MercenaryForm.cpp b/source/D2MercenaryForm.cpp
index 60832821..abbb45ef 100644
--- a/source/D2MercenaryForm.cpp
+++ b/source/D2MercenaryForm.cpp
@@ -1,4 +1,4 @@
-/*
+/*
Diablo II Character Editor
Copyright (C) 2021-2022 Walter Couto
@@ -20,6 +20,8 @@
#include "pch.h"
#include "D2Editor.h"
#include "D2MercenaryForm.h"
+#include "d2ce/helpers/ItemHelpers.h"
+#include
#include "afxdialogex.h"
#ifdef _DEBUG
@@ -378,7 +380,7 @@ void CD2MercenaryForm::DisplayMercInfo()
Merc.getDamage(damage);
if (damage.Max != 0)
{
- sDamage.Format(_T("%ld-%d"), damage.Min, damage.Max);
+ sDamage.Format(DamageFmt, damage.Min, damage.Max);
}
MercDamage.SetWindowText(sDamage);
@@ -464,37 +466,13 @@ void CD2MercenaryForm::UpdateMercNames()
return;
}
+ std::u16string uText;
MercName.ResetContent();
CurMercNameClass = curClass;
- switch (CurMercNameClass)
+ for (const auto& name : d2ce::Mercenary::getMercNames(CurMercNameClass))
{
- case d2ce::EnumMercenaryClass::RogueScout:
- for (const auto& name : d2ce::RogueMercNames)
- {
- MercName.AddString(CString(name.c_str()));
- }
- break;
-
- case d2ce::EnumMercenaryClass::DesertMercenary:
- for (const auto& name : d2ce::DesertMercenaryNames)
- {
- MercName.AddString(CString(name.c_str()));
- }
- break;
-
- case d2ce::EnumMercenaryClass::IronWolf:
- for (const auto& name : d2ce::IronWolfNames)
- {
- MercName.AddString(CString(name.c_str()));
- }
- break;
-
- case d2ce::EnumMercenaryClass::Barbarian:
- for (const auto& name : d2ce::BarbarianMercNames)
- {
- MercName.AddString(CString(name.c_str()));
- }
- break;
+ uText = utf8::utf8to16(name.c_str());
+ MercName.AddString(reinterpret_cast(uText.c_str()));
}
MercName.SetCurSel(Merc.getNameId());
@@ -520,33 +498,14 @@ void CD2MercenaryForm::UpdateAttributes()
return;
}
+ std::u16string uText;
Attribute.ResetContent();
CurAttributeClass = curClass;
- switch (CurAttributeClass)
+ const auto& attribs = d2ce::Mercenary::getMercAttributes(CurAttributeClass);
+ for (const auto& name : attribs)
{
- case d2ce::EnumMercenaryClass::RogueScout:
- for (const auto& name : d2ce::RogueMercAttributes)
- {
- Attribute.AddString(CString(name.c_str()));
- }
- break;
-
- case d2ce::EnumMercenaryClass::DesertMercenary:
- for (const auto& name : d2ce::DesertMercenaryAttributes)
- {
- Attribute.AddString(CString(name.c_str()));
- }
- break;
-
- case d2ce::EnumMercenaryClass::IronWolf:
- for (const auto& name : d2ce::IronWolfAttributes)
- {
- Attribute.AddString(CString(name.c_str()));
- }
- break;
-
- case d2ce::EnumMercenaryClass::Barbarian:
- break;
+ uText = utf8::utf8to16(name.c_str());
+ Attribute.AddString(reinterpret_cast(uText.c_str()));
}
if (Attribute.GetCount() <= 0)
@@ -772,6 +731,17 @@ CStringA CD2MercenaryForm::ToTextA(const CWnd* Sender) const
return CStringA(ToText(Sender));
}
//---------------------------------------------------------------------------
+void CD2MercenaryForm::SetText(CWnd* Sender, const std::string& newValue)
+{
+ SetUTF8Text(Sender, newValue.c_str());
+}
+//---------------------------------------------------------------------------
+void CD2MercenaryForm::SetUTF8Text(CWnd* Sender, const char* newValue)
+{
+ auto uText = utf8::utf8to16(newValue);
+ SetText(Sender, reinterpret_cast(uText.c_str()));
+}
+//---------------------------------------------------------------------------
void CD2MercenaryForm::SetText(CWnd* Sender, const char* newValue)
{
if (Sender->IsKindOf(RUNTIME_CLASS(CEdit)) || Sender->IsKindOf(RUNTIME_CLASS(CStatic)))
@@ -972,7 +942,168 @@ BOOL CD2MercenaryForm::OnInitDialog()
Experience.SetLimitText(10);
{
+ std::string strValue;
CWaitCursor wait;
+ d2ce::LocalizationHelpers::GetStringTxtValue("MiniPanelHire", strValue, "Mercenary");
+ auto uText = utf8::utf8to16(strValue);
+ SetWindowText(reinterpret_cast(uText.c_str()));
+
+ if(d2ce::LocalizationHelpers::GetStringTxtValue("ItemStats1l", strValue))
+ {
+ auto pos = strValue.find(":");
+ if (pos != strValue.npos)
+ {
+ strValue = strValue.substr(pos + 1);
+ uText = utf8::utf8to16(strValue);
+ DamageFmt = reinterpret_cast(uText.c_str());
+ }
+ }
+
+ if (d2ce::LocalizationHelpers::GetStringTxtValue("VerifyTransaction6", strValue))
+ {
+ uText = utf8::utf8to16(strValue);
+ GetDlgItem(IDC_MERC_HIRED)->SetWindowText(reinterpret_cast(uText.c_str()));
+ }
+
+ if (d2ce::LocalizationHelpers::GetStringTxtValue("sysmsg9", strValue))
+ {
+ uText = utf8::utf8to16(strValue);
+ CStringW tmp(reinterpret_cast(uText.c_str()));
+ tmp.Trim();
+ tmp.TrimRight(L".。");
+ GetDlgItem(IDC_RESURRECTED_CHECK)->SetWindowText(tmp);
+ }
+
+ if (d2ce::LocalizationHelpers::GetStringTxtValue("strName", strValue))
+ {
+ uText = utf8::utf8to16(strValue);
+ GetDlgItem(IDC_STATIC_CHAR_NAME)->SetWindowText(reinterpret_cast(uText.c_str()));
+ }
+
+ if (d2ce::LocalizationHelpers::GetStringTxtValue("strClass", strValue))
+ {
+ uText = utf8::utf8to16(strValue);
+ GetDlgItem(IDC_STATIC_CHAR_CLASS)->SetWindowText(reinterpret_cast(uText.c_str()));
+ }
+
+ if (d2ce::LocalizationHelpers::GetStringTxtValue("strchrlvl", strValue))
+ {
+ uText = utf8::utf8to16(strValue);
+ GetDlgItem(IDC_STATIC_CHAR_LEVEL)->SetWindowText(reinterpret_cast(uText.c_str()));
+ }
+
+ if (d2ce::LocalizationHelpers::GetStringTxtValue("strchrexp", strValue))
+ {
+ uText = utf8::utf8to16(strValue);
+ GetDlgItem(IDC_STATIC_CHAR_EXPERIENCE)->SetWindowText(reinterpret_cast(uText.c_str()));
+ }
+
+ if (d2ce::LocalizationHelpers::GetStringTxtValue("strchrlif", strValue))
+ {
+ uText = utf8::utf8to16(strValue);
+ GetDlgItem(IDC_STATIC_CUR_LIFE)->SetWindowText(reinterpret_cast(uText.c_str()));
+ }
+
+ if (d2ce::LocalizationHelpers::GetStringTxtValue("strchrstr", strValue))
+ {
+ uText = utf8::utf8to16(strValue);
+ GetDlgItem(IDC_STATIC_CHAR_STRENGTH)->SetWindowText(reinterpret_cast(uText.c_str()));
+ }
+
+ if (d2ce::LocalizationHelpers::GetStringTxtValue("strchrdex", strValue))
+ {
+ uText = utf8::utf8to16(strValue);
+ GetDlgItem(IDC_STATIC_CHAR_DEXTERITY)->SetWindowText(reinterpret_cast(uText.c_str()));
+ }
+
+ if (d2ce::LocalizationHelpers::GetStringTxtValue("strchrskm", strValue))
+ {
+ uText = utf8::utf8to16(strValue);
+ GetDlgItem(IDC_STATIC_CHAR_DAMAGE)->SetWindowText(reinterpret_cast(uText.c_str()));
+ }
+
+ if (d2ce::LocalizationHelpers::GetStringTxtValue("strchrdef", strValue))
+ {
+ uText = utf8::utf8to16(strValue);
+ GetDlgItem(IDC_STATIC_CHAR_DEFENSE)->SetWindowText(reinterpret_cast(uText.c_str()));
+ }
+
+ if (d2ce::LocalizationHelpers::GetStringTxtValue("strchrfir", strValue))
+ {
+ uText = utf8::utf8to16(strValue);
+ GetDlgItem(IDC_STATIC_CHAR_RESIST_FIRE)->SetWindowText(reinterpret_cast(uText.c_str()));
+ }
+
+ if (d2ce::LocalizationHelpers::GetStringTxtValue("strchrcol", strValue))
+ {
+ uText = utf8::utf8to16(strValue);
+ GetDlgItem(IDC_STATIC_CHAR_RESIST_COLD)->SetWindowText(reinterpret_cast(uText.c_str()));
+ }
+
+ if (d2ce::LocalizationHelpers::GetStringTxtValue("strchrlit", strValue))
+ {
+ uText = utf8::utf8to16(strValue);
+ GetDlgItem(IDC_STATIC_CHAR_RESIST_LIGHTNING)->SetWindowText(reinterpret_cast(uText.c_str()));
+ }
+
+ if (d2ce::LocalizationHelpers::GetStringTxtValue("strchrpos", strValue))
+ {
+ uText = utf8::utf8to16(strValue);
+ GetDlgItem(IDC_STATIC_CHAR_RESIST_POISON)->SetWindowText(reinterpret_cast(uText.c_str()));
+ }
+
+ CString text;
+ CStringA textA;
+ CWnd* pWnd = nullptr;
+ if (d2ce::LocalizationHelpers::GetStringTxtValue("ok", strValue))
+ {
+ pWnd = GetDlgItem(IDOK);
+ if (pWnd != nullptr)
+ {
+ pWnd->GetWindowText(text);
+ textA = text;
+ if (textA.CompareNoCase(strValue.c_str()) != 0)
+ {
+ uText = utf8::utf8to16(strValue);
+ pWnd->SetWindowText(reinterpret_cast(uText.c_str()));
+ }
+ }
+ }
+
+ if (d2ce::LocalizationHelpers::GetStringTxtValue("cancel", strValue))
+ {
+ pWnd = GetDlgItem(IDCANCEL);
+ if (pWnd != nullptr)
+ {
+ pWnd->GetWindowText(text);
+ textA = text;
+ if (textA.CompareNoCase(strValue.c_str()) != 0)
+ {
+ uText = utf8::utf8to16(strValue);
+ pWnd->SetWindowText(reinterpret_cast(uText.c_str()));
+ }
+ }
+ }
+
+ MercClass.ResetContent();
+ for (const auto& name : d2ce::Mercenary::getMercClassNames())
+ {
+ if (!name.empty()) // skip none
+ {
+ uText = utf8::utf8to16(name.c_str());
+ MercClass.AddString(reinterpret_cast(uText.c_str()));
+ }
+ }
+
+ static std::initializer_list all_diff = { d2ce::EnumDifficulty::Normal, d2ce::EnumDifficulty::Nightmare, d2ce::EnumDifficulty::Hell };
+ Difficulty.ResetContent();
+ for (auto diff : all_diff)
+ {
+ d2ce::LocalizationHelpers::GetDifficultyStringTxtValue(diff, strValue);
+ uText = utf8::utf8to16(strValue);
+ Difficulty.AddString(reinterpret_cast(uText.c_str()));
+ }
+
DisplayMercInfo();
LoadMercItemImages();
}
@@ -987,7 +1118,7 @@ void CD2MercenaryForm::OnEnChangeMercLevel()
}
void CD2MercenaryForm::OnEnKillfocusMercLevel()
{
- std::uint32_t level = std::min(std::max(ToInt(&MercLevel), std::uint32_t(1)), std::min(MainForm.getCharacterLevel(), d2ce::NUM_OF_LEVELS - 1));
+ std::uint32_t level = std::min(std::max(ToInt(&MercLevel), std::uint32_t(1)), std::min(MainForm.getCharacterLevel(), d2ce::MERC_NUM_OF_LEVELS));
if (level != Merc.getLevel())
{
Merc.setLevel(level);
@@ -1002,7 +1133,7 @@ void CD2MercenaryForm::OnEnChangeMercExperience()
}
void CD2MercenaryForm::OnEnKillfocusMercExperience()
{
- std::uint32_t experience = std::min(std::max(ToInt(&Experience), std::uint32_t(1)), d2ce::MAX_EXPERIENCE);
+ std::uint32_t experience = std::min(std::max(ToInt(&Experience), std::uint32_t(1)), d2ce::MERC_MAX_EXPERIENCE);
if (experience != Merc.getExperience())
{
Merc.setExperience(experience);
@@ -1264,6 +1395,8 @@ void CD2MercenaryForm::OnContextMenu(CWnd* /*pWnd*/, CPoint point)
bool isStackable = CurrItem->isStackable();
bool isArmor = !isStackable && CurrItem->isArmor();
bool isWeapon = !isArmor && CurrItem->isWeapon();
+ bool canHaveSockets = CurrItem->canHaveSockets();
+ bool canPersonalize = CurrItem->canPersonalize();
if (isArmor || isWeapon || isStackable)
{
CMenu menu;
@@ -1289,48 +1422,36 @@ void CD2MercenaryForm::OnContextMenu(CWnd* /*pWnd*/, CPoint point)
pPopup->DeleteMenu(ID_ITEM_CONTEXT_LOAD, MF_BYCOMMAND);
}
- if (!isArmor && !isWeapon)
+ if (!canHaveSockets || (CurrItem->isSocketed() && (CurrItem->getMaxSocketCount() <= CurrItem->socketCount())))
{
- pPopup->DeleteMenu(ID_ITEM_CONTEXT_FIX, MF_BYCOMMAND);
- pPopup->DeleteMenu(ID_ITEM_CONTEXT_MAXDURABILITY, MF_BYCOMMAND);
- pPopup->DeleteMenu(ID_ITEM_CONTEXT_INDESTRUCTIBLE, MF_BYCOMMAND);
pPopup->DeleteMenu(ID_ITEM_CONTEXT_ADDSOCKET, MF_BYCOMMAND);
pPopup->DeleteMenu(ID_ITEM_CONTEXT_MAXSOCKETS, MF_BYCOMMAND);
+ }
+
+ if (!canPersonalize)
+ {
pPopup->DeleteMenu(ID_ITEM_CONTEXT_PERSONALIZE, MF_BYCOMMAND);
pPopup->DeleteMenu(ID_ITEM_CONTEXT_REMOVE_PERSONALIZATION, MF_BYCOMMAND);
}
else
{
- if (CurrItem->isIndestructible())
- {
- pPopup->DeleteMenu(ID_ITEM_CONTEXT_FIX, MF_BYCOMMAND);
- pPopup->DeleteMenu(ID_ITEM_CONTEXT_MAXDURABILITY, MF_BYCOMMAND);
- pPopup->DeleteMenu(ID_ITEM_CONTEXT_INDESTRUCTIBLE, MF_BYCOMMAND);
- }
-
- if (!CurrItem->canHaveSockets() || (CurrItem->isSocketed() && (CurrItem->getMaxSocketCount() <= CurrItem->socketCount())))
- {
- pPopup->DeleteMenu(ID_ITEM_CONTEXT_ADDSOCKET, MF_BYCOMMAND);
- pPopup->DeleteMenu(ID_ITEM_CONTEXT_MAXSOCKETS, MF_BYCOMMAND);
- }
-
- if (MainForm.getCharacterInfo().getVersion() < d2ce::EnumCharVersion::v109)
+ if (CurrItem->isPersonalized())
{
pPopup->DeleteMenu(ID_ITEM_CONTEXT_PERSONALIZE, MF_BYCOMMAND);
- pPopup->DeleteMenu(ID_ITEM_CONTEXT_REMOVE_PERSONALIZATION, MF_BYCOMMAND);
}
else
{
- if (CurrItem->isPersonalized())
- {
- pPopup->DeleteMenu(ID_ITEM_CONTEXT_PERSONALIZE, MF_BYCOMMAND);
- }
- else
- {
- pPopup->DeleteMenu(ID_ITEM_CONTEXT_REMOVE_PERSONALIZATION, MF_BYCOMMAND);
- }
+ pPopup->DeleteMenu(ID_ITEM_CONTEXT_REMOVE_PERSONALIZATION, MF_BYCOMMAND);
}
}
+
+ if ((!isArmor && !isWeapon) || CurrItem->isIndestructible())
+ {
+ pPopup->DeleteMenu(ID_ITEM_CONTEXT_FIX, MF_BYCOMMAND);
+ pPopup->DeleteMenu(ID_ITEM_CONTEXT_MAXDURABILITY, MF_BYCOMMAND);
+ pPopup->DeleteMenu(ID_ITEM_CONTEXT_INDESTRUCTIBLE, MF_BYCOMMAND);
+ }
+
pPopup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, this);
}
}
diff --git a/source/D2MercenaryForm.h b/source/D2MercenaryForm.h
index 2ae0b3ac..19d730bf 100644
--- a/source/D2MercenaryForm.h
+++ b/source/D2MercenaryForm.h
@@ -110,6 +110,8 @@ class CD2MercenaryForm : public CDialogEx, CD2ItemToolTipCtrlCallback, public CD
d2ce::MercInfo OrigMerc;
d2ce::Item* CurrItem = nullptr;
+ CString DamageFmt = _T("%d to %d");
+
void DisplayMercInfo();
void EnableMercInfoBox();
void UpdateMercNames();
@@ -118,11 +120,13 @@ class CD2MercenaryForm : public CDialogEx, CD2ItemToolTipCtrlCallback, public CD
void LoadMercItemImages();
void refreshEquipped(const d2ce::Item& item);
- std::string ToStdString(const CWnd* Sender) const;
- CString ToText(const CWnd* Sender) const;
- CStringA ToTextA(const CWnd* Sender) const;
- void SetText(CWnd* Sender, const char* newValue);
- void SetText(CWnd* Sender, const wchar_t* newValue);
+ std::string ToStdString(const CWnd* Sender) const; // UTF-8
+ CString ToText(const CWnd* Sender) const; // UTF-16
+ CStringA ToTextA(const CWnd* Sender) const; // ANSI
+ void SetText(CWnd* Sender, const std::string& newValue); // UTF-8
+ void SetUTF8Text(CWnd* Sender, const char* newValue); // UTF-8
+ void SetText(CWnd* Sender, const char* newValue); // ANSI
+ void SetText(CWnd* Sender, const wchar_t* newValue); // UTF-16
std::uint32_t ToInt(const CWnd* Sender) const;
void SetInt(CWnd* Sender, std::uint32_t newValue);
diff --git a/source/D2QuestsForm.cpp b/source/D2QuestsForm.cpp
index 887354c3..db50f0b1 100644
--- a/source/D2QuestsForm.cpp
+++ b/source/D2QuestsForm.cpp
@@ -21,6 +21,8 @@
#include "pch.h"
#include "D2Editor.h"
#include "D2QuestsForm.h"
+#include "d2ce/helpers/ItemHelpers.h"
+#include
#include "afxdialogex.h"
#ifdef _DEBUG
@@ -169,13 +171,86 @@ BOOL CD2QuestsForm::OnInitDialog()
EnableToolTips(TRUE);
+ std::string strValue;
+ d2ce::LocalizationHelpers::GetStringTxtValue("strpanel2", strValue, "Quests");
+ auto uText = utf8::utf8to16(strValue);
+ SetWindowText(reinterpret_cast(uText.c_str()));
+
CWnd* pWnd = nullptr;
- for (auto i = MaxItemIndex + 1; i <= (int)static_cast>(d2ce::EnumDifficulty::Hell); ++i)
+ for (auto i = 0; i <= (int)static_cast>(d2ce::EnumDifficulty::Hell); ++i)
{
pWnd = GetDlgItem(IDC_RADIO_DIFFICULTY_NORMAL + i);
if (pWnd != nullptr)
{
- pWnd->EnableWindow(FALSE);
+ d2ce::LocalizationHelpers::GetDifficultyStringTxtValue(static_cast(i), strValue);
+ uText = utf8::utf8to16(strValue);
+ pWnd->SetWindowText(reinterpret_cast(uText.c_str()));
+ if (i >= MaxItemIndex + 1)
+ {
+ pWnd->EnableWindow(FALSE);
+ }
+ }
+ }
+
+ if (d2ce::LocalizationHelpers::GetStringTxtValue("respec", strValue))
+ {
+ uText = utf8::utf8to16(strValue);
+ GetDlgItem(IDC_CHECK_ACTI_RESET_STATS)->SetWindowText(reinterpret_cast(uText.c_str()));
+ }
+
+ if (d2ce::LocalizationHelpers::GetStringTxtValue("Moo Moo Farm", strValue))
+ {
+ uText = utf8::utf8to16(strValue);
+ GetDlgItem(IDC_CHECK_QUEST_FARM)->SetWindowText(reinterpret_cast(uText.c_str()));
+ }
+
+ if (d2ce::LocalizationHelpers::GetStringTxtValue("act1", strValue))
+ {
+ uText = utf8::utf8to16(strValue);
+ GetDlgItem(IDC_STATIC_ACTI)->SetWindowText(reinterpret_cast(uText.c_str()));
+ }
+
+ if (d2ce::LocalizationHelpers::GetStringTxtValue("act2", strValue))
+ {
+ uText = utf8::utf8to16(strValue);
+ GetDlgItem(IDC_STATIC_ACTII)->SetWindowText(reinterpret_cast(uText.c_str()));
+ }
+
+ if (d2ce::LocalizationHelpers::GetStringTxtValue("act3", strValue))
+ {
+ uText = utf8::utf8to16(strValue);
+ GetDlgItem(IDC_STATIC_ACTIII)->SetWindowText(reinterpret_cast(uText.c_str()));
+ }
+
+ if (d2ce::LocalizationHelpers::GetStringTxtValue("act4", strValue))
+ {
+ uText = utf8::utf8to16(strValue);
+ GetDlgItem(IDC_STATIC_ACTIV)->SetWindowText(reinterpret_cast(uText.c_str()));
+ }
+
+ if (d2ce::LocalizationHelpers::GetStringTxtValue("act5", strValue))
+ {
+ uText = utf8::utf8to16(strValue);
+ GetDlgItem(IDC_STATIC_ACTV)->SetWindowText(reinterpret_cast(uText.c_str()));
+ }
+
+ // update non-Expansion Quests
+ std::uint32_t numQuests = IDC_CHECK_ACTIV_QUEST_3 - IDC_CHECK_ACTI_QUEST_1 + 1;
+ std::uint16_t actNumber = 0;
+ std::uint16_t questNumber = 0;
+ for (std::uint32_t i = 0, nIDC = IDC_CHECK_ACTI_QUEST_1; i < numQuests; ++i, ++nIDC)
+ {
+ actNumber = std::uint16_t(i / d2ce::NUM_OF_QUESTS) + 1;
+ questNumber = std::uint16_t(i % d2ce::NUM_OF_QUESTS) + 1;
+ std::stringstream ss;
+ ss << "qstsa";
+ ss << actNumber;
+ ss << "q";
+ ss << questNumber;
+ if (d2ce::LocalizationHelpers::GetStringTxtValue(ss.str(), strValue))
+ {
+ uText = utf8::utf8to16(strValue);
+ GetDlgItem(nIDC)->SetWindowText(reinterpret_cast(uText.c_str()));
}
}
@@ -199,6 +274,54 @@ BOOL CD2QuestsForm::OnInitDialog()
}
}
}
+ else
+ {
+ // update Expansion Quests
+ questNumber = 1;
+ for (std::uint32_t i = 0, nIDC = IDC_CHECK_ACTV_QUEST_1; i < d2ce::NUM_OF_QUESTS; ++i, ++nIDC)
+ {
+ std::stringstream ss;
+ ss << "qstsa5q";
+ ss << (i + 1);
+ if (d2ce::LocalizationHelpers::GetStringTxtValue(ss.str(), strValue))
+ {
+ uText = utf8::utf8to16(strValue);
+ GetDlgItem(nIDC)->SetWindowText(reinterpret_cast(uText.c_str()));
+ }
+ }
+ }
+
+ CString text;
+ CStringA textA;
+ if (d2ce::LocalizationHelpers::GetStringTxtValue("ok", strValue))
+ {
+ pWnd = GetDlgItem(IDOK);
+ if (pWnd != nullptr)
+ {
+ pWnd->GetWindowText(text);
+ textA = text;
+ if (textA.CompareNoCase(strValue.c_str()) != 0)
+ {
+ uText = utf8::utf8to16(strValue);
+ pWnd->SetWindowText(reinterpret_cast(uText.c_str()));
+ }
+ }
+ }
+
+ if (d2ce::LocalizationHelpers::GetStringTxtValue("cancel", strValue))
+ {
+ pWnd = GetDlgItem(IDCANCEL);
+ if (pWnd != nullptr)
+ {
+ pWnd->GetWindowText(text);
+ textA = text;
+ if (textA.CompareNoCase(strValue.c_str()) != 0)
+ {
+ uText = utf8::utf8to16(strValue);
+ pWnd->SetWindowText(reinterpret_cast(uText.c_str()));
+ }
+ }
+ }
return TRUE; // return TRUE unless you set the focus to a control
// EXCEPTION: OCX Property Pages should return FALSE
diff --git a/source/D2QuestsForm.h b/source/D2QuestsForm.h
index d89fdb49..3f04b653 100644
--- a/source/D2QuestsForm.h
+++ b/source/D2QuestsForm.h
@@ -22,7 +22,7 @@
//---------------------------------------------------------------------------
#include "D2MainForm.h"
-#include "d2ce\ActsInfo.h"
+#include "d2ce/ActsInfo.h"
//---------------------------------------------------------------------------
class CD2QuestsForm : public CDialogEx
diff --git a/source/D2SharedStashForm.cpp b/source/D2SharedStashForm.cpp
index d97bd0d8..40182a7e 100644
--- a/source/D2SharedStashForm.cpp
+++ b/source/D2SharedStashForm.cpp
@@ -26,6 +26,7 @@
#include "D2AddGemsForm.h"
#include "D2MercenaryForm.h"
#include
+#include
#ifdef _DEBUG
#define new DEBUG_NEW
@@ -1492,8 +1493,8 @@ void CD2SharedStashForm::OnBnClickedOk()
CWaitCursor wait;
if (!Stash.save())
{
- CStringA msg;
- msg.Format("Failed to savefile: %s", Stash.getPathName());
+ CStringW msg;
+ msg.Format(L"Failed to savefile: %s", Stash.getPath().wstring().c_str());
AfxMessageBox(CString(msg), MB_OK | MB_ICONERROR);
return;
}
@@ -1514,7 +1515,8 @@ void CD2SharedStashForm::OnBnClickedCancel()
//---------------------------------------------------------------------------
std::string CD2SharedStashForm::ToStdString(const CWnd* Sender) const
{
- return (LPCSTR)CStringA(ToText(Sender));
+ CStringW wValue(ToText(Sender));
+ return utf8::utf16to8(reinterpret_cast(wValue.GetString()));
}
//---------------------------------------------------------------------------
CString CD2SharedStashForm::ToText(const CWnd* Sender) const
diff --git a/source/D2SkillTreeForm.cpp b/source/D2SkillTreeForm.cpp
index c6d25625..572b2c9d 100644
--- a/source/D2SkillTreeForm.cpp
+++ b/source/D2SkillTreeForm.cpp
@@ -21,13 +21,369 @@
#include "pch.h"
#include "D2Editor.h"
#include "D2SkillTreeForm.h"
-#include "d2ce\SkillConstants.h"
+#include "d2ce/SkillConstants.h"
+#include "d2ce/CharacterConstants.h"
+#include "d2ce/CharacterStats.h"
+#include "d2ce/helpers/ItemHelpers.h"
+#include
#include "afxdialogex.h"
+#include
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
+namespace
+{
+ void DrawDependencyLine(Gdiplus::Graphics& graphics, Gdiplus::Pen& pen, CRect& rect, CRect& depRect)
+ {
+ long y = 0;
+ long x = 0;
+ long depY = 0;
+ long depX = 0;
+ if (depRect.left < rect.left)
+ {
+ // dependency to the left
+ x = rect.left;
+ depX = depRect.right;
+
+ if (depRect.bottom == rect.bottom)
+ {
+ y = rect.top + rect.Height() / 3;
+ depY = depRect.top + depRect.Height() / 3;
+ }
+ else if (depRect.bottom < rect.bottom)
+ {
+ // dependency to the top-left
+ y = rect.top + rect.Height() / 2;
+ depX = depRect.left + depRect.Width() / 2;
+ depY = depRect.bottom;
+ }
+ else
+ {
+ // dependency to the bottom-left
+ y = rect.top + rect.Height() / 2;
+ depY = depRect.top + depRect.Height() / 3;
+ }
+ }
+ else if (depRect.left > rect.right)
+ {
+ // dependency to the right
+ x = rect.right;
+ depX = depRect.left;
+
+ if (depRect.bottom == rect.bottom)
+ {
+ y = rect.top + rect.Height() / 3;
+ depY = depRect.top + depRect.Height() / 3;
+ }
+ else if (depRect.bottom < rect.bottom)
+ {
+ // dependency to the top-right
+ y = rect.top + rect.Height() / 3;
+ depX = depRect.left + depRect.Width() / 2;
+ depY = depRect.bottom;
+ }
+ else
+ {
+ // dependency to the bottom-right
+ y = rect.bottom;
+ depY = depRect.top + depRect.Height() / 2;
+ }
+ }
+ else if (depRect.top < rect.top)
+ {
+ // dependency is above
+ x = rect.left + rect.Width() / 2;
+ depX = depRect.left + depRect.Width() / 2;
+ y = rect.top;
+ depY = depRect.bottom;
+ }
+ else
+ {
+ // dependency is below
+ x = rect.left + rect.Width() / 2;
+ depX = depRect.left + depRect.Width() / 2;
+ y = rect.bottom;
+ depY = depRect.top;
+ }
+
+ graphics.DrawLine(&pen, depX, depY, x, y);
+ }
+
+ void ScaleImage(CDC* pDC, CBitmap& image, const CRect& rect)
+ {
+ BITMAP bmp;
+ image.GetBitmap(&bmp);
+
+ // select the source CBitmap in a memory DC;
+ CDC memSrcDc;
+ memSrcDc.CreateCompatibleDC(pDC);
+ memSrcDc.SelectObject(&image); //now bitmap is an instance of CBitmap class
+
+ // Create your new CBitmap with the new desired size and select it into a destination memory DC
+ CDC memDestDC;
+ CBitmap image2;
+ memDestDC.CreateCompatibleDC(pDC);
+ image2.CreateCompatibleBitmap(&memSrcDc, rect.Width(), rect.Height());
+ memDestDC.SelectObject(&image2);
+
+ // StretchBlt from src to dest
+ memDestDC.StretchBlt(0, 0, rect.Width(), rect.Height(), &memSrcDc, 0, 0, bmp.bmWidth - 1, bmp.bmHeight - 1, SRCCOPY);
+
+ HGDIOBJ hbitmap_detach = image.Detach();
+ if (hbitmap_detach)
+ {
+ DeleteObject(hbitmap_detach);
+ }
+
+ image.Attach(image2.Detach());
+ }
+
+ void SetScaledButtonImage(CDC* pDC, CButton& button, CBitmap& image)
+ {
+ CRect buttonRect;
+ button.GetClientRect(&buttonRect);
+ ScaleImage(pDC, image, buttonRect);
+ button.SetBitmap(image);
+ }
+
+ CSize CalcTextSize(CDC* pDC, CString& strText, CRect& rect, BOOL bCalcOnly)
+ {
+ CSize sizeText(0, 0);
+
+ strText.Replace(_T("\t"), _T(" "));
+ if (strText.Find(_T('\n')) >= 0) // Multi-line text
+ {
+ UINT nFormat = DT_CENTER | DT_NOPREFIX;
+ if (bCalcOnly)
+ {
+ nFormat |= DT_CALCRECT;
+ }
+
+ int nHeight = pDC->DrawText(strText, rect, nFormat);
+ rect.top += nHeight;
+ rect.bottom += nHeight;
+ sizeText = CSize(rect.Width(), nHeight);
+ }
+ else
+ {
+ if (bCalcOnly)
+ {
+ sizeText = pDC->GetTextExtent(strText);
+ }
+ else
+ {
+ UINT nFormat = DT_CENTER | DT_NOCLIP | DT_SINGLELINE;
+ sizeText.cy = pDC->DrawText(strText, rect, nFormat);
+ rect.top += sizeText.cy;
+ rect.bottom += sizeText.cy;
+ sizeText.cx = (LONG)rect.Width();
+ }
+ }
+
+ return sizeText;
+ }
+}
+
+//---------------------------------------------------------------------------
+// CD2ItemToolTipCtrl
+class CD2SkillToolTipCtrl : public CMFCToolTipCtrl
+{
+ DECLARE_DYNCREATE(CD2SkillToolTipCtrl)
+
+ // Construction
+public:
+ CD2SkillToolTipCtrl(CMFCToolTipInfo* pParams = NULL) : CMFCToolTipCtrl(pParams)
+ {
+ }
+
+ void SetCallback(const CD2SkillToolTipCtrlCallback* callback = nullptr)
+ {
+ Callback = callback;
+ }
+ // Overrides
+public:
+ CSize OnDrawLabel(CDC* pDC, CRect rect, BOOL bCalcOnly) override
+ {
+ if (CurrSkillId == nullptr)
+ {
+ return __super::OnDrawLabel(pDC, rect, bCalcOnly);
+ }
+
+ // color codes as described in the statdesc file
+ static const COLORREF colors[] = { RGB(255,255,255), RGB(255, 0, 0), RGB(0,255,0), RGB(94,94,255), RGB(148,128,100), RGB(117, 117, 117), RGB(255,255,255), RGB(255,255,255), RGB(255,128,0), RGB(255,255,0) };
+ enum { WHITE = 0, RED = 1, GREEN = 2, BLUE = 3, GOLD = 4, GRAY = 5, ORANGE = 8, YELLOW = 9 };
+
+ const auto& skillInfo = d2ce::CharClassHelper::getSkillById(*CurrSkillId);
+
+ // Get color of top text
+ COLORREF color = colors[GREEN];
+ pDC->SetTextColor(color);
+
+ std::u16string uText = utf8::utf8to16(skillInfo.name);
+ CString strText(reinterpret_cast(uText.c_str()));
+ CSize sizeText(CalcTextSize(pDC, strText, rect, bCalcOnly));
+ auto skipLineHeight = sizeText.cy;
+
+ // Get color of description
+ auto colorDesc = (Enabled && (BasePoints > 0)) ? WHITE : RED;
+ color = colors[colorDesc];
+ pDC->SetTextColor(color);
+
+ // long description
+ uText = utf8::utf8to16(skillInfo.longName);
+ strText = reinterpret_cast(uText.c_str());
+ if (!strText.IsEmpty())
+ {
+ CSize prevSizeText = sizeText;
+ sizeText = CalcTextSize(pDC, strText, rect, bCalcOnly);
+ sizeText.cy += prevSizeText.cy;
+ sizeText.cx = std::max(prevSizeText.cx, sizeText.cx);
+ }
+
+ std::string strValue;
+ if (skillInfo.reqLevel > 0)
+ {
+ d2ce::LocalizationHelpers::GetStringTxtValue("skilldesc3", strValue, "Required Level: %d");
+ uText = utf8::utf8to16(d2ce::LocalizationHelpers::string_format(strValue, skillInfo.reqLevel));
+ strText = reinterpret_cast(uText.c_str());
+ if (!strText.IsEmpty())
+ {
+ CSize prevSizeText = sizeText;
+ sizeText = CalcTextSize(pDC, strText, rect, bCalcOnly);
+ sizeText.cy += prevSizeText.cy;
+ sizeText.cx = std::max(prevSizeText.cx, sizeText.cx);
+ }
+ }
+
+ if (BasePoints > 0)
+ {
+ auto value = std::min(std::uint16_t(BonusPoints + BasePoints), std::uint16_t(MAXUINT8));
+ if (value != BasePoints)
+ {
+ d2ce::LocalizationHelpers::GetStringTxtValue("TooltipSkillLevelBonus", strValue, "Current Skill Level: %d (Base: %d)");
+ uText = utf8::utf8to16(d2ce::LocalizationHelpers::string_format(strValue, value, std::uint16_t(BasePoints)));
+ }
+ else
+ {
+ d2ce::LocalizationHelpers::GetStringTxtValue("StrSkill2", strValue, "Current Skill Level: %d");
+ uText = utf8::utf8to16(d2ce::LocalizationHelpers::string_format(strValue, value));
+ }
+
+ strText = reinterpret_cast(uText.c_str());
+ if (!strText.IsEmpty())
+ {
+ // skip line
+ rect.top += skipLineHeight;
+ rect.bottom += skipLineHeight;
+ sizeText.cy += skipLineHeight;
+
+ CSize prevSizeText = sizeText;
+ sizeText = CalcTextSize(pDC, strText, rect, bCalcOnly);
+ sizeText.cy += prevSizeText.cy;
+ sizeText.cx = std::max(prevSizeText.cx, sizeText.cx);
+ }
+
+ /*if (BasePoints < d2ce::MAX_SKILL_VALUE)
+ {
+ d2ce::LocalizationHelpers::GetStringTxtValue("StrSkill1", strValue, "Next Level");
+ uText = utf8::utf8to16(strValue);
+ strText = reinterpret_cast(uText.c_str());
+ if (!strText.IsEmpty())
+ {
+ // skip line
+ rect.top += skipLineHeight;
+ rect.bottom += skipLineHeight;
+ sizeText.cy += skipLineHeight;
+
+ CSize prevSizeText = sizeText;
+ sizeText = CalcTextSize(pDC, strText, rect, bCalcOnly);
+ sizeText.cy += prevSizeText.cy;
+ sizeText.cx = std::max(prevSizeText.cx, sizeText.cx);
+ }
+ }*/
+ }
+ else
+ {
+ /*d2ce::LocalizationHelpers::GetStringTxtValue("StrSkill17", strValue, "First Level");
+ uText = utf8::utf8to16(strValue);
+ strText = reinterpret_cast(uText.c_str());
+ if (!strText.IsEmpty())
+ {
+ // skip line
+ rect.top += skipLineHeight;
+ rect.bottom += skipLineHeight;
+ sizeText.cy += skipLineHeight;
+
+ CSize prevSizeText = sizeText;
+ sizeText = CalcTextSize(pDC, strText, rect, bCalcOnly);
+ sizeText.cy += prevSizeText.cy;
+ sizeText.cx = std::max(prevSizeText.cx, sizeText.cx);
+ }*/
+ }
+
+ if (BasePoints < d2ce::MAX_SKILL_VALUE)
+ {
+ // get next level stats
+ }
+
+ return sizeText;
+ }
+
+ void OnFillBackground(CDC* pDC, CRect rect, COLORREF& clrText, COLORREF& clrLine) override
+ {
+ if (CurrSkillId == nullptr)
+ {
+ return __super::OnFillBackground(pDC, rect, clrText, clrLine);
+ }
+
+ CBrush br(RGB(0, 0, 0));
+ pDC->FillRect(rect, &br);
+ }
+
+ // Implementation
+public:
+ virtual ~CD2SkillToolTipCtrl()
+ {
+ }
+
+protected:
+ afx_msg void OnShow(NMHDR* pNMHDR, LRESULT* pResult)
+ {
+ CurrUID = UINT(CWnd::FromHandle((HWND)pNMHDR->idFrom)->GetDlgCtrlID());
+ CurrSkillId = nullptr;
+ BasePoints = 0;
+ BonusPoints = 0;
+ Enabled = false;
+ if (Callback != nullptr)
+ {
+ CPoint point;
+ ::GetCursorPos(&point);
+ CurrSkillId = Callback->InvHitTest(CurrUID, point, BasePoints, BonusPoints, Enabled);
+ }
+
+ __super::OnShow(pNMHDR, pResult);
+ }
+ DECLARE_MESSAGE_MAP()
+
+protected:
+ UINT CurrUID = 0;
+ const std::uint16_t* CurrSkillId = nullptr;
+ std::uint8_t BasePoints = 0;
+ std::uint16_t BonusPoints = 0;
+ bool Enabled = false;
+ const CD2SkillToolTipCtrlCallback* Callback = nullptr;
+};
+
+IMPLEMENT_DYNAMIC(CD2SkillToolTipCtrl, CMFCToolTipCtrl)
+//---------------------------------------------------------------------------
+
+//---------------------------------------------------------------------------
+BEGIN_MESSAGE_MAP(CD2SkillToolTipCtrl, CMFCToolTipCtrl)
+ ON_NOTIFY_REFLECT(TTN_SHOW, &CD2SkillToolTipCtrl::OnShow)
+END_MESSAGE_MAP()
+
//---------------------------------------------------------------------------
// CD2SkillTreeForm dialog
@@ -42,6 +398,170 @@ CD2SkillTreeForm::CD2SkillTreeForm(CD2MainForm& form)
SkillsUsed = MainForm.getSkillPointsUsed();
SkillChoices = MainForm.getSkillChoices();
Skills = MainForm.getSkills();
+ MainForm.getSkillBonusPoints(BonusSkillPoints);
+ Level = std::uint16_t(MainForm.getCharacterLevel());
+
+ {
+ const auto& tab1Skills = d2ce::CharClassHelper::getSklTreeTab(0ui16, Class);
+ for (const auto& skillRows : tab1Skills)
+ {
+ if ((skillRows.first == 0) || (skillRows.first > 6))
+ {
+ // should not happend
+ continue;
+ }
+
+ UINT id = IDC_BUTTON_TREE_1_ROW_1_COL_1 + 3 * (skillRows.first - 1);
+ UINT idEdit = IDC_EDIT_TREE_1_ROW_1_COL_1 + 3 * (skillRows.first - 1);
+ for (const auto& skillCols : skillRows.second)
+ {
+ if ((skillCols.first == 0) || (skillCols.first > 3))
+ {
+ // should not happend
+ continue;
+ }
+
+ const auto& skillInfo = d2ce::CharClassHelper::getSkillById(skillCols.second);
+ if (!skillInfo.classInfo.has_value())
+ {
+ // should not happend
+ continue;
+ }
+
+ auto& tabSkill = Tab1SkillMap[id + (skillCols.first - 1)];
+ tabSkill.skillId = skillCols.second;
+ tabSkill.buttonId = id + (skillCols.first - 1);
+ tabSkill.editId = idEdit + (skillCols.first - 1);
+ tabSkill.skillIdx = skillInfo.classInfo.value().index;
+ SkillBnMap[tabSkill.editId] = tabSkill.buttonId;
+ SkillIdxBnMap[tabSkill.skillIdx] = tabSkill.buttonId;
+ if (skillInfo.reqLevel > Level)
+ {
+ tabSkill.levelReqMet = false;
+ }
+
+ for (const auto& reqSkill : skillInfo.reqSkills)
+ {
+ const auto& reqSkillInfo = d2ce::CharClassHelper::getSkillByIndex(reqSkill);
+ if (!skillInfo.classInfo.has_value())
+ {
+ // should not happend
+ continue;
+ }
+
+ tabSkill.reqSkills.push_back(reqSkillInfo.classInfo.value().index);
+ }
+ }
+ }
+ }
+
+ {
+ const auto& tab2Skills = d2ce::CharClassHelper::getSklTreeTab(1ui16, Class);
+ for (const auto& skillRows : tab2Skills)
+ {
+ if ((skillRows.first == 0) || (skillRows.first > 6))
+ {
+ // should not happend
+ continue;
+ }
+
+ UINT id = IDC_BUTTON_TREE_2_ROW_1_COL_1 + 3 * (skillRows.first - 1);
+ UINT idEdit = IDC_EDIT_TREE_2_ROW_1_COL_1 + 3 * (skillRows.first - 1);
+ for (const auto& skillCols : skillRows.second)
+ {
+ if ((skillCols.first == 0) || (skillCols.first > 3))
+ {
+ // should not happend
+ continue;
+ }
+
+ const auto& skillInfo = d2ce::CharClassHelper::getSkillById(skillCols.second);
+ if (!skillInfo.classInfo.has_value())
+ {
+ // should not happend
+ continue;
+ }
+
+ auto& tabSkill = Tab2SkillMap[id + (skillCols.first - 1)];
+ tabSkill.skillId = skillCols.second;
+ tabSkill.buttonId = id + (skillCols.first - 1);
+ tabSkill.editId = idEdit + (skillCols.first - 1);
+ tabSkill.skillIdx = skillInfo.classInfo.value().index;
+ SkillBnMap[tabSkill.editId] = tabSkill.buttonId;
+ SkillIdxBnMap[tabSkill.skillIdx] = tabSkill.buttonId;
+ if (skillInfo.reqLevel > Level)
+ {
+ tabSkill.levelReqMet = false;
+ }
+
+ for (const auto& reqSkill : skillInfo.reqSkills)
+ {
+ const auto& reqSkillInfo = d2ce::CharClassHelper::getSkillByIndex(reqSkill);
+ if (!skillInfo.classInfo.has_value())
+ {
+ // should not happend
+ continue;
+ }
+
+ tabSkill.reqSkills.push_back(reqSkillInfo.classInfo.value().index);
+ }
+ }
+ }
+ }
+
+ {
+ const auto& tab3Skills = d2ce::CharClassHelper::getSklTreeTab(2ui16, Class);
+ for (const auto& skillRows : tab3Skills)
+ {
+ if ((skillRows.first == 0) || (skillRows.first > 6))
+ {
+ // should not happend
+ continue;
+ }
+
+ UINT id = IDC_BUTTON_TREE_3_ROW_1_COL_1 + 3 * (skillRows.first - 1);
+ UINT idEdit = IDC_EDIT_TREE_3_ROW_1_COL_1 + 3 * (skillRows.first - 1);
+ for (const auto& skillCols : skillRows.second)
+ {
+ if ((skillCols.first == 0) || (skillCols.first > 3))
+ {
+ // should not happend
+ continue;
+ }
+
+ const auto& skillInfo = d2ce::CharClassHelper::getSkillById(skillCols.second);
+ if (!skillInfo.classInfo.has_value())
+ {
+ // should not happend
+ continue;
+ }
+
+ auto& tabSkill = Tab3SkillMap[id + (skillCols.first - 1)];
+ tabSkill.skillId = skillCols.second;
+ tabSkill.buttonId = id + (skillCols.first - 1);
+ tabSkill.editId = idEdit + (skillCols.first - 1);
+ tabSkill.skillIdx = skillInfo.classInfo.value().index;
+ SkillBnMap[tabSkill.editId] = tabSkill.buttonId;
+ SkillIdxBnMap[tabSkill.skillIdx] = tabSkill.buttonId;
+ if (skillInfo.reqLevel > Level)
+ {
+ tabSkill.levelReqMet = false;
+ }
+
+ for (const auto& reqSkill : skillInfo.reqSkills)
+ {
+ const auto& reqSkillInfo = d2ce::CharClassHelper::getSkillByIndex(reqSkill);
+ if (!skillInfo.classInfo.has_value())
+ {
+ // should not happend
+ continue;
+ }
+
+ tabSkill.reqSkills.push_back(reqSkillInfo.classInfo.value().index);
+ }
+ }
+ }
+ }
// Fix up Skill Choices
EarnedSkillPoints = MainForm.getSkillPointsEarned();
@@ -59,29 +579,73 @@ CD2SkillTreeForm::~CD2SkillTreeForm()
{
}
//---------------------------------------------------------------------------
-void CD2SkillTreeForm::DoDataExchange(CDataExchange* pDX)
+const std::uint16_t* CD2SkillTreeForm::InvHitTest(CPoint point, TOOLINFO* pTI) const
+{
+ INT_PTR nHit = __super::OnToolHitTest(point, pTI);
+ if (nHit == -1)
+ {
+ return nullptr;
+ }
+
+ // Make sure we have hit an item
+ ClientToScreen(&point);
+ std::uint8_t base = 0;
+ std::uint16_t bonus = 0;
+ bool enabled = false;
+ return InvHitTest((UINT)nHit, point, base, bonus, enabled, pTI);
+}
+//---------------------------------------------------------------------------
+INT_PTR CD2SkillTreeForm::OnToolHitTest(CPoint point, TOOLINFO* pTI) const
{
- __super::DoDataExchange(pDX);
+ TOOLINFO ti = { 0 };
+ ti.cbSize = sizeof(TOOLINFO);
+
+ TOOLINFO* pTi = (pTI == nullptr) ? &ti : pTI;
+ InvHitTest(point, pTi);
+
+ UINT_PTR nHit = pTi->uId;
+ if (pTi->uFlags & TTF_IDISHWND)
+ {
+ nHit = UINT_PTR(::GetDlgCtrlID(HWND(pTi->uId)));
+ }
+ else if(nHit != 0)
+ {
+ nHit = pTi->uId;
+ }
+
+ return (INT_PTR)nHit;
}
//---------------------------------------------------------------------------
BOOL CD2SkillTreeForm::PreTranslateMessage(MSG* pMsg)
{
- if (pMsg->message == WM_KEYDOWN)
+ CWnd* pWndFocus = GetFocus();
+ if (pWndFocus != NULL && IsChild(pWndFocus))
{
- if (pMsg->wParam == VK_RETURN)
+ UINT message = pMsg->message;
+ if ((message == WM_MOUSEMOVE || message == WM_NCMOUSEMOVE ||
+ message == WM_LBUTTONUP || message == WM_RBUTTONUP ||
+ message == WM_MBUTTONUP) &&
+ (GetKeyState(VK_LBUTTON) >= 0 && GetKeyState(VK_RBUTTON) >= 0 &&
+ GetKeyState(VK_MBUTTON) >= 0))
+ {
+ CheckToolTipCtrl();
+ }
+
+ if (pMsg->message == WM_KEYDOWN)
{
- TCHAR szClass[10];
- CWnd* pWndFocus = GetFocus();
- if (((pWndFocus = GetFocus()) != NULL) &&
- IsChild(pWndFocus) &&
- GetClassName(pWndFocus->m_hWnd, szClass, 10) &&
- (lstrcmpi(szClass, _T("EDIT")) == 0))
+ if (pMsg->wParam == VK_RETURN)
{
- // pressing the ENTER key will take the focus to the next control
- pMsg->wParam = VK_TAB;
+ TCHAR szClass[10];
+ if (GetClassName(pWndFocus->m_hWnd, szClass, 10) &&
+ (lstrcmpi(szClass, _T("EDIT")) == 0))
+ {
+ // pressing the ENTER key will take the focus to the next control
+ pMsg->wParam = VK_TAB;
+ }
}
}
}
+
return __super::PreTranslateMessage(pMsg);
}
//---------------------------------------------------------------------------
@@ -110,7 +674,8 @@ CString CD2SkillTreeForm::ToText(CWnd* Sender)
//---------------------------------------------------------------------------
std::string CD2SkillTreeForm::ToStdString(CWnd* Sender)
{
- return (LPCSTR)CStringA(ToText(Sender));
+ CStringW wValue(ToText(Sender));
+ return utf8::utf16to8(reinterpret_cast(wValue.GetString()));
}
//---------------------------------------------------------------------------
std::uint32_t CD2SkillTreeForm::ToInt(UINT nID)
@@ -169,10 +734,18 @@ void CD2SkillTreeForm::SetInt(UINT nID, std::uint32_t newValue)
//---------------------------------------------------------------------------
BEGIN_MESSAGE_MAP(CD2SkillTreeForm, CDialogEx)
- ON_CONTROL_RANGE(EN_KILLFOCUS, IDC_EDIT_TREE_1_SKILL_1, IDC_EDIT_TREE_3_SKILL_10, OnSkillKillFocus)
+ ON_CONTROL_RANGE(EN_KILLFOCUS, IDC_EDIT_TREE_1_ROW_1_COL_1, IDC_EDIT_TREE_1_ROW_6_COL_3, &CD2SkillTreeForm::OnTab1SkillKillFocus)
+ ON_CONTROL_RANGE(EN_KILLFOCUS, IDC_EDIT_TREE_2_ROW_1_COL_1, IDC_EDIT_TREE_2_ROW_6_COL_3, &CD2SkillTreeForm::OnTab2SkillKillFocus)
+ ON_CONTROL_RANGE(EN_KILLFOCUS, IDC_EDIT_TREE_3_ROW_1_COL_1, IDC_EDIT_TREE_3_ROW_6_COL_3, &CD2SkillTreeForm::OnTab3SkillKillFocus)
+ ON_CONTROL_RANGE(BN_CLICKED, IDC_BUTTON_TREE_1_ROW_1_COL_1, IDC_BUTTON_TREE_1_ROW_6_COL_3, &CD2SkillTreeForm::OnTab1SkillBnClicked)
+ ON_CONTROL_RANGE(BN_CLICKED, IDC_BUTTON_TREE_2_ROW_1_COL_1, IDC_BUTTON_TREE_2_ROW_6_COL_3, &CD2SkillTreeForm::OnTab2SkillBnClicked)
+ ON_CONTROL_RANGE(BN_CLICKED, IDC_BUTTON_TREE_3_ROW_1_COL_1, IDC_BUTTON_TREE_3_ROW_6_COL_3, &CD2SkillTreeForm::OnTab3SkillBnClicked)
+ ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTW, 0, 0xFFFF, &CD2SkillTreeForm::OnToolTipText)
+ ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTA, 0, 0xFFFF, &CD2SkillTreeForm::OnToolTipText)
ON_BN_CLICKED(IDOK, &CD2SkillTreeForm::OnBnClickedOk)
ON_BN_CLICKED(IDCANCEL, &CD2SkillTreeForm::OnBnClickedCancel)
ON_BN_CLICKED(IDC_SET_ALL_SKILLS, &CD2SkillTreeForm::OnBnClickedSetAll)
+ ON_WM_PAINT()
END_MESSAGE_MAP()
//---------------------------------------------------------------------------
@@ -182,74 +755,227 @@ END_MESSAGE_MAP()
BOOL CD2SkillTreeForm::OnInitDialog()
{
__super::OnInitDialog();
- GetWindowText(OrigCaption);
- UpdateCaption();
- // Fill out tab names.
- auto classNumber = static_cast>(Class);
- const auto& tabNames = d2ce::TabNames[classNumber];
- for (UINT i = 0, nIDC = IDC_STATIC_TREE_1; i < 3; ++i, ++nIDC)
+ EnableToolTips(TRUE);
+ CheckToolTipCtrl();
+
+ std::string strValue;
+ d2ce::LocalizationHelpers::GetStringTxtValue("minipaneltree", strValue, "Skill Tree");
+ auto uText = utf8::utf8to16(strValue);
+ OrigCaption = reinterpret_cast(uText.c_str());
+ if (OrigCaption.IsEmpty())
{
- GetDlgItem(nIDC)->SetWindowText(CString(tabNames[i].c_str()));
+ GetWindowText(OrigCaption);
}
+ UpdateCaption();
- // Fill out skill names and values;
- const auto& tabSkillPos = d2ce::TabSkillPos[classNumber];
- const auto& skillsNames = d2ce::SkillsNames[classNumber];
- std::uint8_t Pos = 0;
- for (UINT i = 0, nIDC = IDC_STATIC_TREE_1_SKILL_1, nEditIDC = IDC_EDIT_TREE_1_SKILL_1; i < d2ce::NUM_OF_SKILLS; ++i, ++nIDC, ++nEditIDC)
+ // Fill out tab names.
+ for (UINT i = 0, nIDC = IDC_STATIC_TREE_1; i < d2ce::NUM_OF_SKILL_TABS; ++i, ++nIDC)
{
- Pos = tabSkillPos[i];
- GetDlgItem(nIDC)->SetWindowText(CString(skillsNames[Pos].c_str()));
- ((CEdit*)GetDlgItem(nEditIDC))->LimitText(2);
- SetInt(nEditIDC, Skills[Pos]);
+ uText = utf8::utf8to16(d2ce::CharClassHelper::getSkillTabName(std::uint16_t(i), Class));
+ GetDlgItem(nIDC)->SetWindowText(reinterpret_cast(uText.c_str()));
}
- ((CEdit*)GetDlgItem(IDC_EDIT_SET_ALL_SKILLS))->LimitText(2);
- SetInt(IDC_EDIT_SET_ALL_SKILLS, 0);
- return TRUE; // return TRUE unless you set the focus to a control
- // EXCEPTION: OCX Property Pages should return FALSE
-}
-//---------------------------------------------------------------------------
-void CD2SkillTreeForm::OnSkillKillFocus(UINT nID)
-{
- // Fill out skill names
- const auto& tabSkillPos = d2ce::TabSkillPos[static_cast>(Class)];
- auto Pos = tabSkillPos[nID - IDC_EDIT_TREE_1_SKILL_1];
- std::uint8_t skillValue = (std::uint8_t)ToInt(nID);
- if (skillValue > d2ce::MAX_SKILL_VALUE)
+ CString text;
+ CStringA textA;
+ CWnd* pWnd = nullptr;
+ if (d2ce::LocalizationHelpers::GetStringTxtValue("ok", strValue))
{
- skillValue = d2ce::MAX_SKILL_VALUE;
- SetInt(nID, skillValue);
+ pWnd = GetDlgItem(IDOK);
+ if (pWnd != nullptr)
+ {
+ pWnd->GetWindowText(text);
+ textA = text;
+ if (textA.CompareNoCase(strValue.c_str()) != 0)
+ {
+ uText = utf8::utf8to16(strValue);
+ pWnd->SetWindowText(reinterpret_cast(uText.c_str()));
+ }
+ }
}
- // value actually changed?
- if (skillValue != Skills[Pos])
+ if (d2ce::LocalizationHelpers::GetStringTxtValue("cancel", strValue))
{
- auto oldValue = Skills[Pos];
- Skills[Pos] = skillValue;
- if (oldValue > skillValue)
+ pWnd = GetDlgItem(IDCANCEL);
+ if (pWnd != nullptr)
{
- SkillsUsed -= (oldValue - skillValue);
- if (SkillsUsed + SkillChoices < EarnedSkillPoints)
+ pWnd->GetWindowText(text);
+ textA = text;
+ if (textA.CompareNoCase(strValue.c_str()) != 0)
{
- SkillChoices = std::min(EarnedSkillPoints - SkillsUsed, d2ce::MAX_SKILL_CHOICES);
- UpdateCaption();
+ uText = utf8::utf8to16(strValue);
+ pWnd->SetWindowText(reinterpret_cast(uText.c_str()));
}
}
- else
+ }
+
+ CDC* pDC = GetDC();
+ for (auto& skill : Tab1SkillMap)
+ {
+ BOOL bEnable = TRUE;
+ if (!skill.second.reqSkills.empty())
{
- std::uint8_t diff = (std::uint8_t)(skillValue - oldValue);
- SkillsUsed += diff;
- if (SkillChoices > 0)
+ bEnable = FALSE;
+ for (const auto& reqSkillIdx : skill.second.reqSkills)
{
- if (SkillChoices > diff)
- {
- SkillChoices -= diff;
- }
- else
+ if (GetSkillPoints(reqSkillIdx, true) != 0)
{
- SkillChoices = 0;
+ bEnable = TRUE;
+ break;
+ }
+ }
+ }
+
+ if (bEnable && !skill.second.levelReqMet)
+ {
+ bEnable = FALSE;
+ }
+
+ CButton* pButton = ((CButton*)GetDlgItem(skill.second.buttonId));
+ pButton->EnableWindow(bEnable);
+ pButton->ShowWindow(SW_SHOW);
+
+ const auto& skillInfo = d2ce::CharClassHelper::getSkillById(skill.second.skillId);
+ MainForm.getSkillBitmap(skillInfo, skill.second.bitmap);
+ SetScaledButtonImage(pDC, *pButton, skill.second.bitmap);
+
+ CEdit* pEdit = ((CEdit*)GetDlgItem(skill.second.editId));
+ pEdit->EnableWindow(bEnable);
+ pEdit->ShowWindow(SW_SHOW);
+ pEdit->LimitText(2);
+ SetInt(skill.second.editId, GetSkillPoints(skill.second.skillIdx));
+ }
+
+ for (auto& skill : Tab2SkillMap)
+ {
+ BOOL bEnable = TRUE;
+ if (!skill.second.reqSkills.empty())
+ {
+ bEnable = FALSE;
+ for (const auto& reqSkillIdx : skill.second.reqSkills)
+ {
+ if (GetSkillPoints(reqSkillIdx, true) != 0)
+ {
+ bEnable = TRUE;
+ break;
+ }
+ }
+ }
+
+ if (bEnable && !skill.second.levelReqMet)
+ {
+ bEnable = FALSE;
+ }
+
+ CButton* pButton = ((CButton*)GetDlgItem(skill.second.buttonId));
+ pButton->EnableWindow(bEnable);
+ pButton->ShowWindow(SW_SHOW);
+
+ const auto& skillInfo = d2ce::CharClassHelper::getSkillById(skill.second.skillId);
+ MainForm.getSkillBitmap(skillInfo, skill.second.bitmap);
+ SetScaledButtonImage(pDC, *pButton, skill.second.bitmap);
+
+ CEdit* pEdit = ((CEdit*)GetDlgItem(skill.second.editId));
+ pEdit->EnableWindow(bEnable);
+ pEdit->ShowWindow(SW_SHOW);
+ pEdit->LimitText(2);
+ SetInt(skill.second.editId, GetSkillPoints(skill.second.skillIdx));
+ }
+
+ for (auto& skill : Tab3SkillMap)
+ {
+ BOOL bEnable = TRUE;
+ if (!skill.second.reqSkills.empty())
+ {
+ bEnable = FALSE;
+ for (const auto& reqSkillIdx : skill.second.reqSkills)
+ {
+ if (GetSkillPoints(reqSkillIdx, true) != 0)
+ {
+ bEnable = TRUE;
+ break;
+ }
+ }
+ }
+
+ if (bEnable && !skill.second.levelReqMet)
+ {
+ bEnable = FALSE;
+ }
+
+ CButton* pButton = ((CButton*)GetDlgItem(skill.second.buttonId));
+ pButton->EnableWindow(bEnable);
+ pButton->ShowWindow(SW_SHOW);
+
+ const auto& skillInfo = d2ce::CharClassHelper::getSkillById(skill.second.skillId);
+ MainForm.getSkillBitmap(skillInfo, skill.second.bitmap);
+ SetScaledButtonImage(pDC, *pButton, skill.second.bitmap);
+
+ CEdit* pEdit = ((CEdit*)GetDlgItem(skill.second.editId));
+ pEdit->EnableWindow(bEnable);
+ pEdit->ShowWindow(SW_SHOW);
+ pEdit->LimitText(2);
+ SetInt(skill.second.editId, GetSkillPoints(skill.second.skillIdx));
+ }
+
+ ((CEdit*)GetDlgItem(IDC_EDIT_SET_ALL_SKILLS))->LimitText(2);
+ SetInt(IDC_EDIT_SET_ALL_SKILLS, 0);
+ //InvalidateRect(NULL);
+
+ return TRUE; // return TRUE unless you set the focus to a control
+ // EXCEPTION: OCX Property Pages should return FALSE
+}
+//---------------------------------------------------------------------------
+void CD2SkillTreeForm::OnTab1SkillKillFocus(UINT nID)
+{
+ auto iterBn = SkillBnMap.find(nID);
+ if (iterBn == SkillBnMap.end())
+ {
+ return;
+ }
+
+ auto iter = Tab1SkillMap.find(iterBn->second);
+ if (iter == Tab1SkillMap.end())
+ {
+ return;
+ }
+
+ const auto& skillInfo = iter->second;
+ auto pos = skillInfo.skillIdx;
+ std::uint8_t skillValue = (std::uint8_t)ToInt(nID);
+ if (skillValue > d2ce::MAX_SKILL_VALUE)
+ {
+ skillValue = d2ce::MAX_SKILL_VALUE;
+ SetInt(nID, skillValue);
+ }
+
+ // value actually changed?
+ auto oldValue = GetSkillPoints(pos);
+ if (skillValue != oldValue)
+ {
+ SetSkillPoints(pos, skillValue);
+ if (oldValue > skillValue)
+ {
+ SkillsUsed -= (oldValue - skillValue);
+ if (SkillsUsed + SkillChoices < EarnedSkillPoints)
+ {
+ SkillChoices = std::min(EarnedSkillPoints - SkillsUsed, d2ce::MAX_SKILL_CHOICES);
+ UpdateCaption();
+ }
+ }
+ else
+ {
+ std::uint8_t diff = (std::uint8_t)(skillValue - oldValue);
+ SkillsUsed += diff;
+ if (SkillChoices > 0)
+ {
+ if (SkillChoices > diff)
+ {
+ SkillChoices -= diff;
+ }
+ else
+ {
+ SkillChoices = 0;
}
if (SkillsUsed + SkillChoices < EarnedSkillPoints)
@@ -261,9 +987,200 @@ void CD2SkillTreeForm::OnSkillKillFocus(UINT nID)
}
SkillsChanged = true;
+ CheckTab1Skills();
}
}
//---------------------------------------------------------------------------
+void CD2SkillTreeForm::OnTab2SkillKillFocus(UINT nID)
+{
+ auto iterBn = SkillBnMap.find(nID);
+ if (iterBn == SkillBnMap.end())
+ {
+ return;
+ }
+
+ auto iter = Tab2SkillMap.find(iterBn->second);
+ if (iter == Tab2SkillMap.end())
+ {
+ return;
+ }
+
+ const auto& skillInfo = iter->second;
+ auto pos = skillInfo.skillIdx;
+ std::uint8_t skillValue = (std::uint8_t)ToInt(nID);
+ if (skillValue > d2ce::MAX_SKILL_VALUE)
+ {
+ skillValue = d2ce::MAX_SKILL_VALUE;
+ SetInt(nID, skillValue);
+ }
+
+ // value actually changed?
+ auto oldValue = GetSkillPoints(pos);
+ if (skillValue != oldValue)
+ {
+ SetSkillPoints(pos, skillValue);
+ if (oldValue > skillValue)
+ {
+ SkillsUsed -= (oldValue - skillValue);
+ if (SkillsUsed + SkillChoices < EarnedSkillPoints)
+ {
+ SkillChoices = std::min(EarnedSkillPoints - SkillsUsed, d2ce::MAX_SKILL_CHOICES);
+ UpdateCaption();
+ }
+ }
+ else
+ {
+ std::uint8_t diff = (std::uint8_t)(skillValue - oldValue);
+ SkillsUsed += diff;
+ if (SkillChoices > 0)
+ {
+ if (SkillChoices > diff)
+ {
+ SkillChoices -= diff;
+ }
+ else
+ {
+ SkillChoices = 0;
+ }
+
+ if (SkillsUsed + SkillChoices < EarnedSkillPoints)
+ {
+ SkillChoices = std::min(EarnedSkillPoints - SkillsUsed, d2ce::MAX_SKILL_CHOICES);
+ }
+ UpdateCaption();
+ }
+ }
+
+ SkillsChanged = true;
+ CheckTab2Skills();
+ }
+}
+//---------------------------------------------------------------------------
+void CD2SkillTreeForm::OnTab3SkillKillFocus(UINT nID)
+{
+ auto iterBn = SkillBnMap.find(nID);
+ if (iterBn == SkillBnMap.end())
+ {
+ return;
+ }
+
+ auto iter = Tab3SkillMap.find(iterBn->second);
+ if (iter == Tab3SkillMap.end())
+ {
+ return;
+ }
+
+ const auto& skillInfo = iter->second;
+ auto pos = skillInfo.skillIdx;
+ std::uint8_t skillValue = (std::uint8_t)ToInt(nID);
+ if (skillValue > d2ce::MAX_SKILL_VALUE)
+ {
+ skillValue = d2ce::MAX_SKILL_VALUE;
+ SetInt(nID, skillValue);
+ }
+
+ // value actually changed?
+ auto oldValue = GetSkillPoints(pos);
+ if (skillValue != oldValue)
+ {
+ SetSkillPoints(pos, skillValue);
+ if (oldValue > skillValue)
+ {
+ SkillsUsed -= (oldValue - skillValue);
+ if (SkillsUsed + SkillChoices < EarnedSkillPoints)
+ {
+ SkillChoices = std::min(EarnedSkillPoints - SkillsUsed, d2ce::MAX_SKILL_CHOICES);
+ UpdateCaption();
+ }
+ }
+ else
+ {
+ std::uint8_t diff = (std::uint8_t)(skillValue - oldValue);
+ SkillsUsed += diff;
+ if (SkillChoices > 0)
+ {
+ if (SkillChoices > diff)
+ {
+ SkillChoices -= diff;
+ }
+ else
+ {
+ SkillChoices = 0;
+ }
+
+ if (SkillsUsed + SkillChoices < EarnedSkillPoints)
+ {
+ SkillChoices = std::min(EarnedSkillPoints - SkillsUsed, d2ce::MAX_SKILL_CHOICES);
+ }
+ UpdateCaption();
+ }
+ }
+
+ SkillsChanged = true;
+ CheckTab3Skills();
+ }
+}
+//---------------------------------------------------------------------------
+void CD2SkillTreeForm::OnTab1SkillBnClicked(UINT nID)
+{
+ auto iter = Tab1SkillMap.find(nID);
+ if (iter == Tab1SkillMap.end())
+ {
+ return;
+ }
+
+ const auto& skillInfo = iter->second;
+ std::uint8_t skillValue = (std::uint8_t)ToInt(skillInfo.editId);
+ if (skillValue >= d2ce::MAX_SKILL_VALUE)
+ {
+ return;
+ }
+
+ ++skillValue;
+ SetInt(skillInfo.editId, skillValue);
+ OnTab1SkillKillFocus(skillInfo.editId);
+}
+//---------------------------------------------------------------------------
+void CD2SkillTreeForm::OnTab2SkillBnClicked(UINT nID)
+{
+ auto iter = Tab2SkillMap.find(nID);
+ if (iter == Tab2SkillMap.end())
+ {
+ return;
+ }
+
+ const auto& skillInfo = iter->second;
+ std::uint8_t skillValue = (std::uint8_t)ToInt(skillInfo.editId);
+ if (skillValue >= d2ce::MAX_SKILL_VALUE)
+ {
+ return;
+ }
+
+ ++skillValue;
+ SetInt(skillInfo.editId, skillValue);
+ OnTab2SkillKillFocus(skillInfo.editId);
+}
+//---------------------------------------------------------------------------
+void CD2SkillTreeForm::OnTab3SkillBnClicked(UINT nID)
+{
+ auto iter = Tab3SkillMap.find(nID);
+ if (iter == Tab3SkillMap.end())
+ {
+ return;
+ }
+
+ const auto& skillInfo = iter->second;
+ std::uint8_t skillValue = (std::uint8_t)ToInt(skillInfo.editId);
+ if (skillValue >= d2ce::MAX_SKILL_VALUE)
+ {
+ return;
+ }
+
+ ++skillValue;
+ SetInt(skillInfo.editId, skillValue);
+ OnTab3SkillKillFocus(skillInfo.editId);
+}
+//---------------------------------------------------------------------------
void CD2SkillTreeForm::SaveSkills()
{
if (SkillsChanged)
@@ -289,11 +1206,137 @@ void CD2SkillTreeForm::UpdateCaption()
}
}
//---------------------------------------------------------------------------
+std::uint16_t CD2SkillTreeForm::GetBonusSkillPoints(std::uint16_t idx) const
+{
+ if (idx < BonusSkillPoints.size())
+ {
+ return BonusSkillPoints[idx];
+ }
+
+ return 0;
+}
+//---------------------------------------------------------------------------
+std::uint8_t CD2SkillTreeForm::GetSkillPoints(std::uint16_t idx, bool addBonusPts) const
+{
+ std::uint8_t points = 0;
+ if (idx < d2ce::NUM_OF_SKILLS)
+ {
+ points += Skills[idx];
+ }
+
+ if (addBonusPts)
+ {
+ points = std::uint8_t(std::min(std::uint16_t(GetBonusSkillPoints(idx) + points), std::uint16_t(MAXUINT8)));
+ }
+
+ return points;
+}
+//---------------------------------------------------------------------------
+bool CD2SkillTreeForm::SetSkillPoints(std::uint16_t idx, std::uint8_t value)
+{
+ if (idx < d2ce::NUM_OF_SKILLS)
+ {
+ Skills[idx] = value;
+ return true;
+ }
+
+ return false;
+}
+//---------------------------------------------------------------------------
+const std::uint16_t* CD2SkillTreeForm::InvHitTest(UINT id, CPoint point, std::uint8_t& base, std::uint16_t& bonus, bool& enabled, TOOLINFO* pTI) const
+{
+ base = 0;
+ bonus = 0;
+ auto* pWnd = GetDlgItem(id);
+ if ((pWnd == nullptr) || !::IsWindow(pWnd->GetSafeHwnd()))
+ {
+ enabled = false;
+ return nullptr;
+ }
+
+ enabled = pWnd->IsWindowEnabled() ? true : false;
+
+ ScreenToClient(&point);
+
+ auto iter = Tab1SkillMap.find(id);
+ if (iter != Tab1SkillMap.end())
+ {
+ CRect buttonRect;
+ pWnd->GetWindowRect(buttonRect);
+ ScreenToClient(&buttonRect);
+ if (!PtInRect(&buttonRect, point))
+ {
+ return nullptr;
+ }
+
+ if (pTI != nullptr)
+ {
+ pTI->hwnd = GetSafeHwnd();
+ pTI->uId = (UINT_PTR)pWnd->GetSafeHwnd();
+ pTI->lpszText = LPSTR_TEXTCALLBACK;
+ pTI->uFlags |= TTF_IDISHWND;
+ }
+
+ base = GetSkillPoints(iter->second.skillIdx);
+ bonus = GetBonusSkillPoints(iter->second.skillIdx);
+ return &iter->second.skillId;
+ }
+
+ iter = Tab2SkillMap.find(id);
+ if (iter != Tab2SkillMap.end())
+ {
+ CRect buttonRect;
+ pWnd->GetWindowRect(buttonRect);
+ ScreenToClient(&buttonRect);
+ if (!PtInRect(&buttonRect, point))
+ {
+ return nullptr;
+ }
+
+ if (pTI != nullptr)
+ {
+ pTI->hwnd = GetSafeHwnd();
+ pTI->uId = (UINT_PTR)pWnd->GetSafeHwnd();
+ pTI->lpszText = LPSTR_TEXTCALLBACK;
+ pTI->uFlags |= TTF_IDISHWND;
+ }
+
+ base = GetSkillPoints(iter->second.skillIdx);
+ bonus = GetBonusSkillPoints(iter->second.skillIdx);
+ return &iter->second.skillId;
+ }
+
+ iter = Tab3SkillMap.find(id);
+ if (iter != Tab3SkillMap.end())
+ {
+ CRect buttonRect;
+ pWnd->GetWindowRect(buttonRect);
+ ScreenToClient(&buttonRect);
+ if (!PtInRect(&buttonRect, point))
+ {
+ return nullptr;
+ }
+
+ if (pTI != nullptr)
+ {
+ pTI->hwnd = GetSafeHwnd();
+ pTI->uId = (UINT_PTR)pWnd->GetSafeHwnd();
+ pTI->lpszText = LPSTR_TEXTCALLBACK;
+ pTI->uFlags |= TTF_IDISHWND;
+ }
+
+ base = GetSkillPoints(iter->second.skillIdx);
+ bonus = GetBonusSkillPoints(iter->second.skillIdx);
+ return &iter->second.skillId;
+ }
+
+ return nullptr;
+}
+//---------------------------------------------------------------------------
void CD2SkillTreeForm::OnBnClickedOk()
{
SaveSkills();
__super::OnOK();
-
}
//---------------------------------------------------------------------------
void CD2SkillTreeForm::OnBnClickedCancel()
@@ -313,13 +1356,52 @@ void CD2SkillTreeForm::OnBnClickedSetAll()
// Save Values
SkillsChanged = true;
- Skills.fill(skillValue);
- for (std::uint32_t nEditIDC = IDC_EDIT_TREE_1_SKILL_1; nEditIDC <= IDC_EDIT_TREE_3_SKILL_10; ++nEditIDC)
+ SkillsUsed = 0;
+ for (auto& skill : Tab1SkillMap)
+ {
+ if (!skill.second.levelReqMet)
+ {
+ // skip
+ SkillsUsed += GetSkillPoints(skill.second.skillIdx);
+ continue;
+ }
+
+ SetSkillPoints(skill.second.skillIdx, skillValue);
+ SetInt(skill.second.editId, skillValue);
+ SkillsUsed += skillValue;
+ }
+ CheckTab1Skills();
+
+ for (auto& skill : Tab2SkillMap)
{
- SetInt(nEditIDC, skillValue);
+ if (!skill.second.levelReqMet)
+ {
+ // skip
+ SkillsUsed += GetSkillPoints(skill.second.skillIdx);
+ continue;
+ }
+
+ SetSkillPoints(skill.second.skillIdx, skillValue);
+ SetInt(skill.second.editId, skillValue);
+ SkillsUsed += skillValue;
}
+ CheckTab2Skills();
+
+ for (auto& skill : Tab3SkillMap)
+ {
+ if (!skill.second.levelReqMet)
+ {
+ // skip
+ SkillsUsed += GetSkillPoints(skill.second.skillIdx);
+ continue;
+ }
+
+ SetSkillPoints(skill.second.skillIdx, skillValue);
+ SetInt(skill.second.editId, skillValue);
+ SkillsUsed += skillValue;
+ }
+ CheckTab3Skills();
- SkillsUsed = d2ce::NUM_OF_SKILLS * skillValue;
if (SkillsUsed >= EarnedSkillPoints)
{
SkillChoices = 0;
@@ -331,9 +1413,276 @@ void CD2SkillTreeForm::OnBnClickedSetAll()
UpdateCaption();
}
//---------------------------------------------------------------------------
+BOOL CD2SkillTreeForm::OnToolTipText(UINT, NMHDR* pNMHDR, LRESULT* pResult)
+{
+ ASSERT(pNMHDR->code == TTN_NEEDTEXTA || pNMHDR->code == TTN_NEEDTEXTW);
+
+ TOOLTIPTEXTA* pTTTA = (TOOLTIPTEXTA*)pNMHDR;
+ TOOLTIPTEXTW* pTTTW = (TOOLTIPTEXTW*)pNMHDR;
+ CString strTipText;
+ UINT_PTR nID = pNMHDR->idFrom;
+
+ if (pNMHDR->code == TTN_NEEDTEXTA && (pTTTA->uFlags & TTF_IDISHWND) ||
+ pNMHDR->code == TTN_NEEDTEXTW && (pTTTW->uFlags & TTF_IDISHWND))
+ {
+ nID = ((UINT_PTR)(DWORD)::GetDlgCtrlID((HWND)nID));
+ }
+
+ if ((nID != 0) && (nID != ID_VIEW_STATUS_BAR))
+ {
+ for (const auto& uid : SkillBnMap)
+ {
+ if (uid.second == nID)
+ {
+ strTipText = _T("N/A");
+ break;
+ }
+ }
+ }
+#ifndef _UNICODE
+ if (pNMHDR->code == TTN_NEEDTEXTA)
+ lstrcpyn(pTTTA->szText, strTipText, (sizeof(pTTTA->szText) / sizeof(pTTTA->szText[0])));
+ else
+ _mbstowcsz(pTTTW->szText, strTipText, (sizeof(pTTTW->szText) / sizeof(pTTTW->szText[0])));
+#else
+ if (pNMHDR->code == TTN_NEEDTEXTA)
+ _wcstombsz(pTTTA->szText, strTipText, (sizeof(pTTTA->szText) / sizeof(pTTTA->szText[0])));
+ else
+ lstrcpyn(pTTTW->szText, strTipText, (sizeof(pTTTW->szText) / sizeof(pTTTW->szText[0])));
+#endif
+ * pResult = 0;
+
+ ::SetWindowPos(pNMHDR->hwndFrom, HWND_TOP, 0, 0, 0, 0, SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOMOVE);
+
+ return TRUE;
+}
+//---------------------------------------------------------------------------
+void CD2SkillTreeForm::OnPaint()
+{
+ if (IsIconic())
+ {
+ __super::OnPaint();
+ return;
+ }
+
+ CPaintDC dc(this); // device context for painting
+ auto color = dc.GetTextColor();
+ Gdiplus::Graphics graphics(dc.GetSafeHdc());
+ Gdiplus::Pen pen(Gdiplus::Color(GetRValue(color), GetGValue(color), GetBValue(color)), 3.0);
+ pen.SetAlignment(Gdiplus::PenAlignmentCenter);
+ pen.SetEndCap(Gdiplus::LineCapArrowAnchor);
+
+ CWnd* pWnd = nullptr;
+ CRect rect;
+ CRect depRect;
+ for (auto& skill : Tab1SkillMap)
+ {
+ pWnd = GetDlgItem(skill.second.buttonId);
+ if (pWnd == nullptr || !::IsWindow(pWnd->GetSafeHwnd()))
+ {
+ continue;
+ }
+ pWnd->GetWindowRect(rect);
+ ScreenToClient(rect);
+
+ for (const auto& reqSkillIdx : skill.second.reqSkills)
+ {
+ pWnd = GetDlgItem(SkillIdxBnMap[reqSkillIdx]);
+ if (pWnd == nullptr || !::IsWindow(pWnd->GetSafeHwnd()))
+ {
+ continue;
+ }
+ pWnd->GetWindowRect(depRect);
+ ScreenToClient(depRect);
+ DrawDependencyLine(graphics, pen, rect, depRect);
+ }
+ }
+
+ for (auto& skill : Tab2SkillMap)
+ {
+ pWnd = GetDlgItem(skill.second.buttonId);
+ if (pWnd == nullptr || !::IsWindow(pWnd->GetSafeHwnd()))
+ {
+ continue;
+ }
+ pWnd->GetWindowRect(rect);
+ ScreenToClient(rect);
+
+ for (const auto& reqSkillIdx : skill.second.reqSkills)
+ {
+ pWnd = GetDlgItem(SkillIdxBnMap[reqSkillIdx]);
+ if (pWnd == nullptr || !::IsWindow(pWnd->GetSafeHwnd()))
+ {
+ continue;
+ }
+ pWnd->GetWindowRect(depRect);
+ ScreenToClient(depRect);
+ DrawDependencyLine(graphics, pen, rect, depRect);
+ }
+ }
+
+ for (auto& skill : Tab3SkillMap)
+ {
+ pWnd = GetDlgItem(skill.second.buttonId);
+ if (pWnd == nullptr || !::IsWindow(pWnd->GetSafeHwnd()))
+ {
+ continue;
+ }
+ pWnd->GetWindowRect(rect);
+ ScreenToClient(rect);
+
+ for (const auto& reqSkillIdx : skill.second.reqSkills)
+ {
+ pWnd = GetDlgItem(SkillIdxBnMap[reqSkillIdx]);
+ if (pWnd == nullptr || !::IsWindow(pWnd->GetSafeHwnd()))
+ {
+ continue;
+ }
+ pWnd->GetWindowRect(depRect);
+ ScreenToClient(depRect);
+ DrawDependencyLine(graphics, pen, rect, depRect);
+ }
+ }
+}
+//---------------------------------------------------------------------------
+void CD2SkillTreeForm::CheckTab1Skills()
+{
+ for (auto& skill : Tab1SkillMap)
+ {
+ if (!skill.second.levelReqMet)
+ {
+ // skip
+ continue;
+ }
+
+ if (skill.second.reqSkills.empty())
+ {
+ // no dependencies
+ continue;
+ }
+
+ BOOL bEnable = FALSE;
+ for (const auto& reqSkillIdx : skill.second.reqSkills)
+ {
+ if (GetSkillPoints(reqSkillIdx, true) != 0)
+ {
+ bEnable = TRUE;
+ break;
+ }
+ }
+
+ GetDlgItem(skill.second.buttonId)->EnableWindow(bEnable);
+ GetDlgItem(skill.second.editId)->EnableWindow(bEnable);
+ }
+}
+//---------------------------------------------------------------------------
+void CD2SkillTreeForm::CheckTab2Skills()
+{
+ for (auto& skill : Tab2SkillMap)
+ {
+ if (!skill.second.levelReqMet)
+ {
+ // skip
+ continue;
+ }
+
+ if (skill.second.reqSkills.empty())
+ {
+ // no dependencies
+ continue;
+ }
+
+ BOOL bEnable = FALSE;
+ for (const auto& reqSkillIdx : skill.second.reqSkills)
+ {
+ if (GetSkillPoints(reqSkillIdx, true) != 0)
+ {
+ bEnable = TRUE;
+ break;
+ }
+ }
+
+ GetDlgItem(skill.second.buttonId)->EnableWindow(bEnable);
+ GetDlgItem(skill.second.editId)->EnableWindow(bEnable);
+ }
+}
+//---------------------------------------------------------------------------
+void CD2SkillTreeForm::CheckTab3Skills()
+{
+ for (auto& skill : Tab3SkillMap)
+ {
+ if (!skill.second.levelReqMet)
+ {
+ // skip
+ continue;
+ }
+
+ if (skill.second.reqSkills.empty())
+ {
+ // no dependencies
+ continue;
+ }
+
+ BOOL bEnable = FALSE;
+ for (const auto& reqSkillIdx : skill.second.reqSkills)
+ {
+ if (GetSkillPoints(reqSkillIdx, true) != 0)
+ {
+ bEnable = TRUE;
+ break;
+ }
+ }
+
+ GetDlgItem(skill.second.buttonId)->EnableWindow(bEnable);
+ GetDlgItem(skill.second.editId)->EnableWindow(bEnable);
+ }
+}
+//---------------------------------------------------------------------------
+void CD2SkillTreeForm::CheckToolTipCtrl()
+{
+ auto pToolTip = AfxGetModuleThreadState()->m_pToolTip;
+ if (pToolTip != NULL && (pToolTip->GetOwner() != this || DYNAMIC_DOWNCAST(CD2SkillToolTipCtrl, pToolTip) == NULL))
+ {
+ pToolTip->DestroyWindow();
+ delete pToolTip;
+ AfxGetModuleThreadState()->m_pToolTip = NULL;
+ pToolTip = NULL;
+ }
+
+ if (pToolTip == NULL)
+ {
+ CMFCToolTipInfo ttParams;
+ ttParams.m_bVislManagerTheme = TRUE;
+ auto pD2SkillToolTip = new CD2SkillToolTipCtrl(&ttParams);
+ pToolTip = pD2SkillToolTip;
+ if (pToolTip->Create(this, TTS_ALWAYSTIP))
+ {
+ pD2SkillToolTip->SetCallback(this);
+
+ CRect rect;
+ CWnd* pWnd = nullptr;
+ for (const auto& uid : SkillBnMap)
+ {
+ pWnd = GetDlgItem(uid.second);
+ if (pWnd == nullptr)
+ {
+ continue;
+ }
+
+ pWnd->GetWindowRect(&rect);
+ ScreenToClient(&rect);
+ pToolTip->AddTool(this, LPSTR_TEXTCALLBACK, rect, uid.second);
+ }
+
+ pToolTip->SetDelayTime(TTDT_AUTOPOP, 0x7FFF);
+ pToolTip->SendMessage(TTM_ACTIVATE, FALSE);
+ AfxGetModuleThreadState()->m_pToolTip = pToolTip;
+ }
+ }
+}
+//---------------------------------------------------------------------------
bool CD2SkillTreeForm::isSkillChoicesChanged() const
{
return SkillsChanged;
}
//---------------------------------------------------------------------------
-
diff --git a/source/D2SkillTreeForm.h b/source/D2SkillTreeForm.h
index 2c652864..bed5973f 100644
--- a/source/D2SkillTreeForm.h
+++ b/source/D2SkillTreeForm.h
@@ -23,8 +23,17 @@
//---------------------------------------------------------------------------
#include "D2MainForm.h"
+
+//---------------------------------------------------------------------------
+class CD2SkillToolTipCtrlCallback
+{
+public:
+ virtual ~CD2SkillToolTipCtrlCallback() = default;
+ virtual const std::uint16_t* InvHitTest(UINT id, CPoint point, std::uint8_t& base, std::uint16_t& bonus, bool& enabled, TOOLINFO* pTI = nullptr) const = 0;
+};
+
//---------------------------------------------------------------------------
-class CD2SkillTreeForm : public CDialogEx
+class CD2SkillTreeForm : public CDialogEx, public CD2SkillToolTipCtrlCallback
{
DECLARE_DYNAMIC(CD2SkillTreeForm)
@@ -32,18 +41,32 @@ class CD2SkillTreeForm : public CDialogEx
CD2SkillTreeForm(CD2MainForm& form);
virtual ~CD2SkillTreeForm();
+ const std::uint16_t* InvHitTest(CPoint point, TOOLINFO* pTI = nullptr) const; // get skill at point
+ virtual INT_PTR OnToolHitTest(CPoint point, TOOLINFO* pTI) const; // get tool at point
+
// Dialog Data
enum { IDD = IDD_SKILLS_DIALOG };
protected:
- virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
virtual BOOL PreTranslateMessage(MSG* pMsg);
- void OnSkillKillFocus(UINT nID);
+ void OnTab1SkillKillFocus(UINT nID);
+ void OnTab2SkillKillFocus(UINT nID);
+ void OnTab3SkillKillFocus(UINT nID);
+ void OnTab1SkillBnClicked(UINT nID);
+ void OnTab2SkillBnClicked(UINT nID);
+ void OnTab3SkillBnClicked(UINT nID);
afx_msg void OnBnClickedOk();
afx_msg void OnBnClickedCancel();
afx_msg void OnBnClickedSetAll();
DECLARE_MESSAGE_MAP()
+ BOOL OnToolTipText(UINT, NMHDR* pNMHDR, LRESULT* pResult);
+
+ void CheckTab1Skills();
+ void CheckTab2Skills();
+ void CheckTab3Skills();
+
+ void CheckToolTipCtrl();
public:
virtual BOOL OnInitDialog();
@@ -56,15 +79,41 @@ class CD2SkillTreeForm : public CDialogEx
void SetInt(UINT nID, std::uint32_t newValue);
void SaveSkills();
void UpdateCaption();
+ std::uint16_t GetBonusSkillPoints(std::uint16_t idx) const;
+ std::uint8_t GetSkillPoints(std::uint16_t idx, bool addBonusPts = false) const;
+ bool SetSkillPoints(std::uint16_t idx, std::uint8_t value);
private:
std::array Skills;
+ std::vector BonusSkillPoints;
bool SkillsChanged = false;
CD2MainForm& MainForm;
d2ce::EnumCharClass Class = d2ce::EnumCharClass::Amazon;
+ std::uint16_t Level = 1;
std::uint32_t SkillChoices = 0;
std::uint32_t SkillsUsed = 0;
std::uint32_t EarnedSkillPoints = 0;
CString OrigCaption;
+
+ struct SkillCtrlsType
+ {
+ CBitmap bitmap;
+ UINT buttonId = 0;
+ UINT editId = 0;
+ std::uint16_t skillId = MAXUINT16;
+ std::uint16_t skillIdx = MAXUINT16;
+ std::vector reqSkills; // map to index in Skill array
+ bool levelReqMet = true;
+ };
+ std::map Tab1SkillMap;
+ std::map Tab2SkillMap;
+ std::map Tab3SkillMap;
+ std::map SkillBnMap;
+ std::map SkillIdxBnMap;
+
+ // Inherited via CD2SkillToolTipCtrlCallback
+ virtual const std::uint16_t* InvHitTest(UINT id, CPoint point, std::uint8_t& base, std::uint16_t& bonus, bool& enabled, TOOLINFO* pTI = nullptr) const override;
+public:
+ afx_msg void OnPaint();
};
//---------------------------------------------------------------------------
diff --git a/source/D2WaypointsForm.cpp b/source/D2WaypointsForm.cpp
index b4bd92af..46d09884 100644
--- a/source/D2WaypointsForm.cpp
+++ b/source/D2WaypointsForm.cpp
@@ -22,6 +22,8 @@
#include "D2Editor.h"
#include "D2WaypointsForm.h"
#include "MainFormConstants.h"
+#include "d2ce/helpers/ItemHelpers.h"
+#include
#include "afxdialogex.h"
#ifdef _DEBUG
@@ -146,13 +148,100 @@ BOOL CD2WaypointsForm::OnInitDialog()
{
__super::OnInitDialog();
+ std::string strValue;
+ std::u16string uText;
CWnd* pWnd = nullptr;
- for (auto i = MaxItemIndex + 1; i <= (int)static_cast>(d2ce::EnumDifficulty::Hell); ++i)
+ for (auto i = 0; i <= (int)static_cast>(d2ce::EnumDifficulty::Hell); ++i)
{
pWnd = GetDlgItem(IDC_RADIO_DIFFICULTY_NORMAL + i);
if (pWnd != nullptr)
{
- pWnd->EnableWindow(FALSE);
+ d2ce::LocalizationHelpers::GetDifficultyStringTxtValue(static_cast(i), strValue);
+ uText = utf8::utf8to16(strValue);
+ pWnd->SetWindowText(reinterpret_cast(uText.c_str()));
+ if (i >= MaxItemIndex + 1)
+ {
+ pWnd->EnableWindow(FALSE);
+ }
+ }
+ }
+
+ CString text;
+ CStringA textA;
+ for (UINT i = 0, nIDC = IDC_CHECK_ACTI_1; i < TotalNumWaypoints; ++i, ++nIDC)
+ {
+ pWnd = GetDlgItem(nIDC);
+ if (pWnd == nullptr)
+ {
+ continue;
+ }
+
+ pWnd->GetWindowText(text);
+ textA = text;
+ if (d2ce::LocalizationHelpers::GetStringTxtValue(textA.GetString(), strValue))
+ {
+ uText = utf8::utf8to16(strValue);
+ pWnd->SetWindowText(reinterpret_cast(uText.c_str()));
+ }
+ }
+
+ if (d2ce::LocalizationHelpers::GetStringTxtValue("act1", strValue))
+ {
+ uText = utf8::utf8to16(strValue);
+ GetDlgItem(IDC_STATIC_ACTI)->SetWindowText(reinterpret_cast(uText.c_str()));
+ }
+
+ if (d2ce::LocalizationHelpers::GetStringTxtValue("act2", strValue))
+ {
+ uText = utf8::utf8to16(strValue);
+ GetDlgItem(IDC_STATIC_ACTII)->SetWindowText(reinterpret_cast(uText.c_str()));
+ }
+
+ if (d2ce::LocalizationHelpers::GetStringTxtValue("act3", strValue))
+ {
+ uText = utf8::utf8to16(strValue);
+ GetDlgItem(IDC_STATIC_ACTIII)->SetWindowText(reinterpret_cast(uText.c_str()));
+ }
+
+ if (d2ce::LocalizationHelpers::GetStringTxtValue("act4", strValue))
+ {
+ uText = utf8::utf8to16(strValue);
+ GetDlgItem(IDC_STATIC_ACTIV)->SetWindowText(reinterpret_cast(uText.c_str()));
+ }
+
+ if (d2ce::LocalizationHelpers::GetStringTxtValue("act5", strValue))
+ {
+ uText = utf8::utf8to16(strValue);
+ GetDlgItem(IDC_STATIC_ACTV)->SetWindowText(reinterpret_cast(uText.c_str()));
+ }
+
+ if (d2ce::LocalizationHelpers::GetStringTxtValue("ok", strValue))
+ {
+ pWnd = GetDlgItem(IDOK);
+ if (pWnd != nullptr)
+ {
+ pWnd->GetWindowText(text);
+ textA = text;
+ if (textA.CompareNoCase(strValue.c_str()) != 0)
+ {
+ uText = utf8::utf8to16(strValue);
+ pWnd->SetWindowText(reinterpret_cast(uText.c_str()));
+ }
+ }
+ }
+
+ if (d2ce::LocalizationHelpers::GetStringTxtValue("cancel", strValue))
+ {
+ pWnd = GetDlgItem(IDCANCEL);
+ if (pWnd != nullptr)
+ {
+ pWnd->GetWindowText(text);
+ textA = text;
+ if (textA.CompareNoCase(strValue.c_str()) != 0)
+ {
+ uText = utf8::utf8to16(strValue);
+ pWnd->SetWindowText(reinterpret_cast(uText.c_str()));
+ }
}
}
diff --git a/source/D2WaypointsForm.h b/source/D2WaypointsForm.h
index d533c8a3..3cd52441 100644
--- a/source/D2WaypointsForm.h
+++ b/source/D2WaypointsForm.h
@@ -21,8 +21,8 @@
#pragma once
//---------------------------------------------------------------------------
-#include "d2ce\Constants.h"
-#include "d2ce\WaypointConstants.h"
+#include "d2ce/Constants.h"
+#include "d2ce/WaypointConstants.h"
#include "D2MainForm.h"
//---------------------------------------------------------------------------
diff --git a/source/MainFormConstants.h b/source/MainFormConstants.h
index 0ea17390..927dab2a 100644
--- a/source/MainFormConstants.h
+++ b/source/MainFormConstants.h
@@ -19,33 +19,17 @@
#pragma once
-#include "d2ce\Constants.h"
-#include "d2ce\ExperienceConstants.h"
-#include "d2ce\CharacterStatsConstants.h"
+#include "d2ce/Constants.h"
+#include "d2ce/ExperienceConstants.h"
+#include "d2ce/CharacterStatsConstants.h"
constexpr COLORREF EDITED_COLOUR = 0x00FFFF00;
const COLORREF NORMAL_COLOUR = GetSysColor(COLOR_WINDOW);
-constexpr std::uint32_t NUM_OF_TITLES = 4;
constexpr std::uint32_t NORMAL = static_cast>(d2ce::EnumDifficulty::Normal);
constexpr std::uint32_t NIGHTMARE = static_cast>(d2ce::EnumDifficulty::Nightmare);
constexpr std::uint32_t HELL = static_cast>(d2ce::EnumDifficulty::Hell);
-// titles for regular non-expansion set characters
-static const char *FemaleTitle[NUM_OF_TITLES] = {"", "Dame", "Lady", "Baroness"},
- *MaleTitle[NUM_OF_TITLES] = {"", "Sir", "Lord", "Baron"};
-
-// titles for regular expansion set characters
-static const char *FemaleExpansionTitle[NUM_OF_TITLES] = {"", "Slayer", "Champion", "Matriarch"},
- *MaleExpansionTitle[NUM_OF_TITLES] = {"", "Slayer", "Champion", "Patriarch"};
-
-// titles for hardcore non-expansion set characters
-static const char *FemaleHardcoreTitle[NUM_OF_TITLES] = {"", "Countess", "Duchess", "Queen"},
- *MaleHardcoreTitle[NUM_OF_TITLES] = {"", "Count", "Duke", "King"};
-
-// titles for hardcore expansion set characters
-static const char *HardcoreExpansionTitle[NUM_OF_TITLES] = {"", "Destroyer", "Conqueror", "Guardian"};
-
static const CString SettingsSection("Settings");
static const CString BackupCharacterOption("Backup Character");
//---------------------------------------------------------------------------
diff --git a/source/d2ce/Character.cpp b/source/d2ce/Character.cpp
index efc54ea1..583cf1a5 100644
--- a/source/d2ce/Character.cpp
+++ b/source/d2ce/Character.cpp
@@ -19,20 +19,25 @@
//---------------------------------------------------------------------------
#include "pch.h"
-#include
#include "Character.h"
#include "CharacterConstants.h"
#include "ExperienceConstants.h"
#include "ItemConstants.h"
#include "SkillConstants.h"
#include "SharedStash.h"
+#include "helpers/DefaultTxtReader.h"
+#include "helpers/ItemHelpers.h"
#include
+#include
//---------------------------------------------------------------------------
namespace d2ce
{
constexpr std::array HEADER = { 0x55, 0xAA, 0x55, 0xAA };
+ constexpr std::array UNKNOWN_014_v116 = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ };
constexpr std::array UNKNOWN_01C_v100 = { 0xDD, 0x00, 0x10, 0x00, 0x82, 0x00 };
constexpr std::array UNKNOWN_01C_v107 = { 0x3F, 0x01, 0x10, 0x00, 0x82, 0x00 };
constexpr std::array UNKNOWN_026 = { 0x00, 0x00 };
@@ -59,8 +64,25 @@ namespace d2ce
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
- constexpr std::array UNKOWN_14B_v109 = { 0x01, 0x00, 0x00, 0x00 };
- constexpr std::array UNKOWN_14B_v115 = { 0x00, 0x00, 0x00, 0x00 };
+ constexpr std::array UNKNOWN_0BF_v116 = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ };
+ constexpr std::array UNKNOWN_11B_v116 = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ };
+ constexpr std::array UNKNOWN_14B_v109 = { 0x01, 0x00, 0x00, 0x00 };
+ constexpr std::array UNKNOWN_14B_v115 = { 0x00, 0x00, 0x00, 0x00 };
void ApplyJsonAppearnces(const Json::Value& appearances, std::array &appearancesValue)
{
@@ -163,11 +185,11 @@ namespace d2ce
}
}
- std::uint32_t ApplyJsonSkill(const Json::Value& skill)
+ std::uint16_t ApplyJsonSkill(const Json::Value& skill)
{
if (skill.isNull())
{
- return 0xFFFF;
+ return MAXUINT16;
}
if (skill.isObject())
@@ -175,13 +197,14 @@ namespace d2ce
Json::Value value = skill["Id"];
if (value.isNull())
{
- return 0xFFFF;
+ return MAXUINT16;
}
- return std::min(std::uint32_t(value.asInt()), std::uint32_t(0xFFFF));
+ return std::uint16_t(value.asInt());
}
- return std::min(CharacterStats::getSkillIdByName(skill.asString()), std::uint32_t(0xFFFF));
+ const auto& skillInfo = CharClassHelper::getSkillByIndex(skill.asString());
+ return skillInfo.id;
}
void ApplyJsonAssignedSkills(const Json::Value& assignedSkills, std::array& assignedSkillsValue)
@@ -189,16 +212,11 @@ namespace d2ce
Json::Value value;
if (!assignedSkills.isNull())
{
- std::uint32_t id = 0xFFFF;
size_t idx = 0;
auto iter_end = assignedSkills.end();
for (auto iter = assignedSkills.begin(); iter != iter_end && idx < NUM_OF_SKILL_HOTKEYS; ++iter, ++idx)
{
- id = ApplyJsonSkill(*iter);
- if (id < 0xFFFF)
- {
- assignedSkillsValue[idx] = id;
- }
+ assignedSkillsValue[idx] = ApplyJsonSkill(*iter);
}
}
}
@@ -275,6 +293,10 @@ namespace d2ce
}
//---------------------------------------------------------------------------
+
+//---------------------------------------------------------------------------
+
+
//---------------------------------------------------------------------------
const char* d2ce::CharacterErrCategory::name() const noexcept
{
@@ -329,7 +351,7 @@ namespace std
//---------------------------------------------------------------------------
//---------------------------------------------------------------------------
-d2ce::Character::Character() : Merc(*this), Acts(*this)
+d2ce::Character::Character() : Cs(*this), Merc(*this), Acts(*this)
{
initialize();
}
@@ -378,6 +400,7 @@ void d2ce::Character::initialize()
m_filesize_location = 0;
m_checksum_location = 0;
m_name_location = 0;
+ m_status_location = 0;
m_class_location = 0;
m_level_location = 0;
m_starting_location = 0;
@@ -394,12 +417,17 @@ void d2ce::Character::initialize()
{
std::rewind(m_charfile);
}
+
+ if (!ItemHelpers::isTxtReaderInitialized())
+ {
+ setDefaultTxtReader();
+ }
}
//---------------------------------------------------------------------------
/*
Returns false if file was not opened or there was an error.
*/
-bool d2ce::Character::openD2S(const char* szfilename, bool validateChecksum)
+bool d2ce::Character::openD2S(const std::filesystem::path& path, bool validateChecksum)
{
if (is_open())
{
@@ -407,10 +435,11 @@ bool d2ce::Character::openD2S(const char* szfilename, bool validateChecksum)
}
m_error_code.clear();
+
#ifdef _MSC_VER
- m_charfile = _fsopen(szfilename, "rb+", _SH_DENYNO);
+ m_charfile = _wfsopen(path.wstring().c_str(), L"rb+", _SH_DENYNO);
#else
- errno_t err = fopen_s(&m_charfile, szfilename, "rb+");
+ errno_t err = _wfopen_s(&m_charfile, path.wstring().c_str(), L"rb+");
if (err != 0)
{
m_error_code = std::make_error_code(CharacterErrc::CannotOpenFile);
@@ -418,13 +447,14 @@ bool d2ce::Character::openD2S(const char* szfilename, bool validateChecksum)
}
#endif
- m_d2sfilename = szfilename;
if (m_charfile == nullptr)
{
m_error_code = std::make_error_code(CharacterErrc::CannotOpenFile);
return false;
}
+ m_d2sfilename = path;
+
readHeader();
if (!isValidHeader())
{
@@ -459,7 +489,7 @@ bool d2ce::Character::openD2S(const char* szfilename, bool validateChecksum)
/*
Returns false if file was not opened or there was an error.
*/
-bool d2ce::Character::openJson(const char* szjsonfilename)
+bool d2ce::Character::openJson(const std::filesystem::path& path)
{
if (is_open())
{
@@ -467,13 +497,13 @@ bool d2ce::Character::openJson(const char* szjsonfilename)
}
std::ifstream ifs;
- ifs.open(szjsonfilename);
+ ifs.open(path.wstring().c_str());
if (!ifs.is_open())
{
m_error_code = std::make_error_code(CharacterErrc::CannotOpenFile);
return false;
}
- m_jsonfilename = szjsonfilename;
+ m_jsonfilename = path;
Json::Value root;
Json::CharReaderBuilder builder;
@@ -494,8 +524,8 @@ bool d2ce::Character::openJson(const char* szjsonfilename)
}
m_d2sfilename.clear();
- char name1[L_tmpnam_s];
- errno_t err = tmpnam_s(name1, L_tmpnam_s);
+ wchar_t name1[L_tmpnam_s];
+ errno_t err = _wtmpnam_s(name1, L_tmpnam_s);
if (err != 0)
{
m_error_code = std::make_error_code(CharacterErrc::CannotOpenFile);
@@ -503,14 +533,15 @@ bool d2ce::Character::openJson(const char* szjsonfilename)
}
m_charfile = NULL;
- fopen_s(&m_charfile, name1, "wb+");
+ std::wstring utempfilename = name1;
+ _wfopen_s(&m_charfile, utempfilename.c_str(), L"wb+");
if (m_charfile == nullptr)
{
m_error_code = std::make_error_code(CharacterErrc::CannotOpenFile);
return false;
}
- m_d2sfilename = name1;
+ m_d2sfilename = utempfilename;
std::fwrite(Header.data(), Header.size(), 1, m_charfile);
if (!refresh(root))
@@ -525,7 +556,7 @@ bool d2ce::Character::openJson(const char* szjsonfilename)
/*
Returns false if file was not opened or there was an error.
*/
-bool d2ce::Character::open(const char* szfilename, bool validateChecksum)
+bool d2ce::Character::open(const std::filesystem::path& path, bool validateChecksum)
{
if (is_open())
{
@@ -533,18 +564,13 @@ bool d2ce::Character::open(const char* szfilename, bool validateChecksum)
}
m_error_code.clear();
-
- if (szfilename != nullptr)
+ std::wstring ext = path.extension().wstring();
+ if (_wcsicmp(ext.c_str(), L".json") == 0)
{
- std::filesystem::path p = szfilename;
- std::string ext = p.extension().string();
- if (_stricmp(ext.c_str(), ".json") == 0)
- {
- return openJson(szfilename);
- }
+ return openJson(path);
}
- return openD2S(szfilename, validateChecksum);
+ return openD2S(path, validateChecksum);
}
//---------------------------------------------------------------------------
/*
@@ -775,7 +801,6 @@ void d2ce::Character::readBasicInfo()
std::fread(&Version, sizeof(Version), 1, m_charfile);
Bs.Version = getVersion();
- Cs.Version = Bs.Version;
m_filesize_location = 0;
m_checksum_location = 0;
@@ -788,9 +813,19 @@ void d2ce::Character::readBasicInfo()
std::fread(&WeaponSet, sizeof(WeaponSet), 1, m_charfile);
}
- m_name_location = std::ftell(m_charfile);
- std::fread(Bs.Name.data(), Bs.Name.size(), 1, m_charfile);
- Bs.Name[15] = 0; // must be zero
+ if (Bs.Version <= EnumCharVersion::v115)
+ {
+ m_name_location = std::ftell(m_charfile);
+ std::fread(Bs.Name.data(), Bs.Name.size(), 1, m_charfile);
+ Bs.Name[15] = 0; // must be zero
+ }
+ else
+ {
+ // skip old name, should be all zero now
+ std::fseek(m_charfile, std::ftell(m_charfile) + (long)UNKNOWN_014_v116.size(), SEEK_SET);
+ }
+
+ m_status_location = std::ftell(m_charfile);
std::uint8_t value = 0;
std::fread(&value, sizeof(value), 1, m_charfile);
Bs.Status = static_cast(value);
@@ -922,6 +957,13 @@ void d2ce::Character::readBasicInfo()
std::fread(&MapID, sizeof(MapID), 1, m_charfile);
Merc.readInfo(Bs.Version, m_charfile);
+ if (Bs.Version > EnumCharVersion::v115)
+ {
+ std::fseek(m_charfile, std::ftell(m_charfile) + (long)UNKNOWN_0BF_v116.size(), SEEK_SET);
+ m_name_location = std::ftell(m_charfile);
+ std::fread(Bs.Name.data(), Bs.Name.size(), 1, m_charfile);
+ Bs.Name[15] = 0; // must be zero
+ }
}
if (Bs.getStartingActTitle() > Bs.Title)
@@ -954,7 +996,6 @@ bool d2ce::Character::readBasicInfo(const Json::Value& root)
std::fwrite(&Version, sizeof(Version), 1, m_charfile);
Bs.Version = getVersion();
- Cs.Version = Bs.Version;
m_filesize_location = 0;
m_checksum_location = 0;
@@ -969,7 +1010,7 @@ bool d2ce::Character::readBasicInfo(const Json::Value& root)
std::fwrite(&FileSize, sizeof(FileSize), 1, m_charfile);
m_checksum_location = std::ftell(m_charfile);
- jsonValue = header[m_bJsonSerializedFormat ? "Checksum": "checksum"];
+ jsonValue = header[m_bJsonSerializedFormat ? "Checksum" : "checksum"];
if (!jsonValue.isNull())
{
Checksum = m_bJsonSerializedFormat ? long(jsonValue.asInt64()) : long(std::stoul(jsonValue.asString(), nullptr, 16));
@@ -984,89 +1025,70 @@ bool d2ce::Character::readBasicInfo(const Json::Value& root)
std::fwrite(&WeaponSet, sizeof(WeaponSet), 1, m_charfile);
}
- m_name_location = std::ftell(m_charfile);
- jsonValue = m_bJsonSerializedFormat ? root["Name"] : header["name"];
- if (jsonValue.isNull())
- {
- return false;
- }
-
+ if (Bs.Version <= EnumCharVersion::v115)
{
- // Check Name
- // Remove any invalid characters from the number
- std::string curName(jsonValue.asString());
- std::string strNewText;
- for (size_t iPos = 0, numberOfUnderscores = 0, nLen = curName.size(); iPos < nLen; ++iPos)
- {
- char c = curName[iPos];
- if (std::isalpha(c))
- {
- strNewText += c;
- }
- else if ((c == '_' || c == '-') && strNewText.size() != 0 && numberOfUnderscores < 1)
- {
- strNewText += c;
- ++numberOfUnderscores;
- }
- }
-
- // trim bad characters
- if (strNewText.size() > 15)
- {
- strNewText.resize(15);
- }
- strNewText.erase(strNewText.find_last_not_of("_-") + 1);
- if (strNewText.size() < 2)
+ m_name_location = std::ftell(m_charfile);
+ jsonValue = m_bJsonSerializedFormat ? root["Name"] : header["name"];
+ if (jsonValue.isNull())
{
return false;
}
+ // Check Name
+ // Remove any invalid characters from the name
+ std::string curName(jsonValue.asString());
+ LocalizationHelpers::CheckCharName(curName, true);
Bs.Name.fill(0);
- strcpy_s(Bs.Name.data(), strNewText.length() + 1, strNewText.c_str());
+ strcpy_s(Bs.Name.data(), curName.length() + 1, curName.c_str());
Bs.Name[15] = 0; // must be zero
std::fwrite(Bs.Name.data(), Bs.Name.size(), 1, m_charfile);
}
+ else
+ {
+ std::fwrite(UNKNOWN_014_v116.data(), UNKNOWN_014_v116.size(), 1, m_charfile);
+ }
std::uint8_t value = 0;
- jsonValue = m_bJsonSerializedFormat ? root["ClassId"] : header["class"];
- if (!jsonValue.isNull())
+ jsonValue = m_bJsonSerializedFormat ? root["ClassId"] : header["class_id"];
+ if (jsonValue.isNull())
{
if (m_bJsonSerializedFormat)
{
- value = std::uint8_t(jsonValue.asInt());
- if (value > std::uint8_t(NUM_OF_CLASSES))
- {
- return false;
- }
+ return false;
}
- else
+
+ jsonValue = header["class"];
+ if (jsonValue.isNull())
{
- bool bFound = false;
- std::string className = jsonValue.asString();
- for (std::uint8_t idx = 0; idx < std::uint8_t(NUM_OF_CLASSES); ++idx)
- {
- if (ClassNames[idx].compare(className) == 0)
- {
- bFound = true;
- value = idx;
- break;
- }
- }
+ return false;
+ }
- if (!bFound)
+ std::string className = jsonValue.asString();
+ if (!CharClassHelper::getEnumCharClassByName(className, Bs.Class))
+ {
+ if (!CharClassHelper::getEnumCharClassByIndex(className, Bs.Class))
{
return false;
}
}
}
- Bs.Class = static_cast(value);
+ else
+ {
+ value = std::uint8_t(jsonValue.asInt());
+ if (value > std::uint8_t(NUM_OF_CLASSES))
+ {
+ return false;
+ }
+ Bs.Class = static_cast(value);
+ }
+
Bs.Status = EnumCharStatus::NoDeaths;
switch (Bs.Class)
{
case EnumCharClass::Druid:
case EnumCharClass::Assassin:
Bs.Status |= EnumCharStatus::Expansion;
- return false;
+ break;
}
jsonValue = m_bJsonSerializedFormat ? root["Location"] : header["difficulty"];
@@ -1151,6 +1173,7 @@ bool d2ce::Character::readBasicInfo(const Json::Value& root)
}
}
+ m_status_location = std::ftell(m_charfile);
value = Bs.Status.bits();
std::fwrite(&value, sizeof(value), 1, m_charfile);
@@ -1318,15 +1341,43 @@ bool d2ce::Character::readBasicInfo(const Json::Value& root)
return false;
}
- // Realm data
- std::fwrite(UNKNOWN_0BF.data(), UNKNOWN_0BF.size(), 1, m_charfile);
- if (Bs.Version < EnumCharVersion::v115)
+ if (Bs.Version > EnumCharVersion::v115)
{
- std::fwrite(UNKOWN_14B_v109.data(), UNKOWN_14B_v109.size(), 1, m_charfile);
+ // Realm data?
+ std::fwrite(UNKNOWN_0BF_v116.data(), UNKNOWN_0BF_v116.size(), 1, m_charfile);
+
+ m_name_location = std::ftell(m_charfile);
+ jsonValue = m_bJsonSerializedFormat ? root["Name"] : header["name"];
+ if (jsonValue.isNull())
+ {
+ return false;
+ }
+
+ // Check Name
+ // Remove any invalid characters from the name
+ std::string curName(jsonValue.asString());
+ LocalizationHelpers::CheckCharName(curName);
+ Bs.Name.fill(0);
+ strcpy_s(Bs.Name.data(), curName.length() + 1, curName.c_str());
+ Bs.Name[15] = 0; // must be zero
+ std::fwrite(Bs.Name.data(), Bs.Name.size(), 1, m_charfile);
+
+ // Realm data?
+ std::fwrite(UNKNOWN_11B_v116.data(), UNKNOWN_11B_v116.size(), 1, m_charfile);
+ std::fwrite(UNKNOWN_14B_v115.data(), UNKNOWN_14B_v115.size(), 1, m_charfile);
}
else
{
- std::fwrite(UNKOWN_14B_v115.data(), UNKOWN_14B_v115.size(), 1, m_charfile);
+ // Realm data?
+ std::fwrite(UNKNOWN_0BF.data(), UNKNOWN_0BF.size(), 1, m_charfile);
+ if (Bs.Version < EnumCharVersion::v115)
+ {
+ std::fwrite(UNKNOWN_14B_v109.data(), UNKNOWN_14B_v109.size(), 1, m_charfile);
+ }
+ else
+ {
+ std::fwrite(UNKNOWN_14B_v115.data(), UNKNOWN_14B_v115.size(), 1, m_charfile);
+ }
}
}
@@ -1362,7 +1413,7 @@ bool d2ce::Character::readActs(const Json::Value& root)
//---------------------------------------------------------------------------
bool d2ce::Character::readStats()
{
- if (Cs.readStats(Bs.Version, Bs.Class, m_charfile))
+ if (Cs.readStats(m_charfile))
{
m_stats_header_location = Cs.getHeaderLocation();
DisplayLevel = (std::uint8_t)Cs.Cs.Level; // updates character's display level
@@ -1375,7 +1426,7 @@ bool d2ce::Character::readStats()
//---------------------------------------------------------------------------
bool d2ce::Character::readStats(const Json::Value& root)
{
- if (Cs.readStats(root, m_bJsonSerializedFormat, Bs.Version, Bs.Class, m_charfile))
+ if (Cs.readStats(root, m_bJsonSerializedFormat, m_charfile))
{
m_stats_header_location = Cs.getHeaderLocation();
DisplayLevel = (std::uint8_t)Cs.Cs.Level; // updates character's display level
@@ -1388,12 +1439,12 @@ bool d2ce::Character::readStats(const Json::Value& root)
//---------------------------------------------------------------------------
bool d2ce::Character::readItems()
{
- return m_items.readItems(Bs.Version, m_charfile, isExpansionCharacter());
+ return m_items.readItems(*this, m_charfile);
}
//---------------------------------------------------------------------------
bool d2ce::Character::readItems(const Json::Value& root)
{
- return m_items.readItems(root, m_bJsonSerializedFormat, Bs.Version, m_charfile, isExpansionCharacter());
+ return m_items.readItems(root, m_bJsonSerializedFormat, *this, m_charfile);
}
//---------------------------------------------------------------------------
bool d2ce::Character::save()
@@ -1420,7 +1471,13 @@ bool d2ce::Character::save()
// prepare to update the character file
std::fclose(m_charfile);
m_charfile = nullptr;
- std::remove(m_d2sfilename.c_str());
+ try
+ {
+ std::filesystem::remove(m_d2sfilename);
+ }
+ catch (std::filesystem::filesystem_error const&)
+ {
+ }
// Don't modify the name of a temporary file
if (m_jsonfilename.empty())
@@ -1429,23 +1486,28 @@ bool d2ce::Character::save()
// to match the character's name
std::filesystem::path p = m_d2sfilename;
p.replace_extension();
- std::string tempname = p.filename().string();
+ std::string tempname = utf8::utf16to8(p.filename().wstring());
// compare m_d2sfilename (w/o extension) to character's name
if (tempname.compare(0, tempname.length(), Bs.Name.data()) != 0)
{
- m_d2sfilename = p.replace_filename(Bs.Name.data()).string();
+ p.replace_filename(std::filesystem::u8path(Bs.Name.data()));
+ m_d2sfilename = p;
}
}
- // rename temp file to character file
- if (std::rename(m_tempfilename.c_str(), m_d2sfilename.c_str()))
+ try
+ {
+ // rename temp file to character file
+ std::filesystem::rename(m_tempfilename, m_d2sfilename);
+ }
+ catch (std::filesystem::filesystem_error const&)
{
m_error_code = std::make_error_code(CharacterErrc::FileRenameError);
return false;
}
- if (!open(m_d2sfilename.c_str(), false)) // checksum is calulated and written below
+ if (!open(m_d2sfilename, false)) // checksum is calulated and written below
{
return false;
}
@@ -1457,85 +1519,133 @@ bool d2ce::Character::save()
m_tempfilename = m_d2sfilename;
std::filesystem::path p = m_d2sfilename;
p.replace_extension();
- std::string origFileNameBase = p.string();
- std::string tempname = p.filename().string();
+ std::filesystem::path origFileNameBase = p;
+ std::string tempname = utf8::utf16to8(p.filename().wstring());
// compare m_d2sfilename (w/o extension) to character's name
if (_stricmp(tempname.c_str(), Bs.Name.data()) != 0)
{
- std::string fileNameBase = p.replace_filename(Bs.Name.data()).string();
- m_d2sfilename = fileNameBase + ".d2s";
+ std::filesystem::path fileNameBase = p.replace_filename(std::filesystem::u8path(Bs.Name.data()));
+ m_d2sfilename = fileNameBase;
+ m_d2sfilename.replace_extension(".d2s");
std::fclose(m_charfile);
m_charfile = nullptr;
- if (std::rename(m_tempfilename.c_str(), m_d2sfilename.c_str()))
+ try
+ {
+ // rename temp file to character file
+ std::filesystem::rename(m_tempfilename, m_d2sfilename);
+ }
+ catch (std::filesystem::filesystem_error const&)
{
m_error_code = std::make_error_code(CharacterErrc::FileRenameError);
return false;
}
- if (!open(m_d2sfilename.c_str(), false)) // checksum is calulated and written below
+ if (!open(m_d2sfilename, false)) // checksum is calulated and written below
{
return false;
}
// rename other files (don't error out if it fails)
- m_tempfilename = origFileNameBase + ".key";
+ m_tempfilename = origFileNameBase;
+ m_tempfilename.replace_extension(".key");
if (std::filesystem::exists(m_tempfilename))
{
- tempname = fileNameBase + ".key";
- if (std::rename(m_tempfilename.c_str(), tempname.c_str()))
+ std::filesystem::path tempPath = fileNameBase;
+ tempPath.replace_extension(".key");
+ try
+ {
+ std::filesystem::rename(m_tempfilename, tempname);
+ }
+ catch (std::filesystem::filesystem_error const&)
{
m_error_code = std::make_error_code(CharacterErrc::AuxFileRenameError);
+ return false;
}
}
- m_tempfilename = origFileNameBase + ".ma0";
+ m_tempfilename = origFileNameBase;
+ m_tempfilename.replace_extension(".ma0");
if (std::filesystem::exists(m_tempfilename))
{
- tempname = fileNameBase + ".ma0";
- if (std::rename(m_tempfilename.c_str(), tempname.c_str()))
+ std::filesystem::path tempPath = fileNameBase;
+ tempPath.replace_extension(".ma0");
+ try
+ {
+ std::filesystem::rename(m_tempfilename, tempname);
+ }
+ catch (std::filesystem::filesystem_error const&)
{
m_error_code = std::make_error_code(CharacterErrc::AuxFileRenameError);
+ return false;
}
}
- m_tempfilename = origFileNameBase + ".ma1";
+ m_tempfilename = origFileNameBase;
+ m_tempfilename.replace_extension(".ma1");
if (std::filesystem::exists(m_tempfilename))
{
- tempname = fileNameBase + ".ma1";
- if (std::rename(m_tempfilename.c_str(), tempname.c_str()))
+ std::filesystem::path tempPath = fileNameBase;
+ tempPath.replace_extension(".ma1");
+ try
+ {
+ std::filesystem::rename(m_tempfilename, tempname);
+ }
+ catch (std::filesystem::filesystem_error const&)
{
m_error_code = std::make_error_code(CharacterErrc::AuxFileRenameError);
+ return false;
}
}
- m_tempfilename = origFileNameBase + ".ma2";
+ m_tempfilename = origFileNameBase;
+ m_tempfilename.replace_extension(".ma2");
if (std::filesystem::exists(m_tempfilename))
{
- tempname = fileNameBase + ".ma2";
- if (std::rename(m_tempfilename.c_str(), tempname.c_str()))
+ std::filesystem::path tempPath = fileNameBase;
+ tempPath.replace_extension(".ma2");
+ try
+ {
+ std::filesystem::rename(m_tempfilename, tempname);
+ }
+ catch (std::filesystem::filesystem_error const&)
{
m_error_code = std::make_error_code(CharacterErrc::AuxFileRenameError);
+ return false;
}
}
- m_tempfilename = origFileNameBase + ".ma3";
+ m_tempfilename = origFileNameBase;
+ m_tempfilename.replace_extension(".ma3");
if (std::filesystem::exists(m_tempfilename))
{
- tempname = fileNameBase + ".ma3";
- if (std::rename(m_tempfilename.c_str(), tempname.c_str()))
+ std::filesystem::path tempPath = fileNameBase;
+ tempPath.replace_extension(".ma3");
+ try
+ {
+ std::filesystem::rename(m_tempfilename, tempname);
+ }
+ catch (std::filesystem::filesystem_error const&)
{
m_error_code = std::make_error_code(CharacterErrc::AuxFileRenameError);
+ return false;
}
}
- m_tempfilename = origFileNameBase + ".map";
+ m_tempfilename = origFileNameBase;
+ m_tempfilename.replace_extension(".map");
if (std::filesystem::exists(m_tempfilename))
{
- tempname = fileNameBase + ".map";
- if (std::rename(m_tempfilename.c_str(), tempname.c_str()))
+ std::filesystem::path tempPath = fileNameBase;
+ tempPath.replace_extension(".map");
+ try
+ {
+ std::filesystem::rename(m_tempfilename, tempname);
+ }
+ catch (std::filesystem::filesystem_error const&)
{
m_error_code = std::make_error_code(CharacterErrc::AuxFileRenameError);
+ return false;
}
}
}
@@ -1575,7 +1685,7 @@ bool d2ce::Character::save()
if (!json.empty())
{
std::FILE* jsonFile = NULL;
- fopen_s(&jsonFile, m_jsonfilename.c_str(), "wb");
+ _wfopen_s(&jsonFile, m_jsonfilename.wstring().c_str(), L"wb");
std::rewind(jsonFile);
std::fwrite(json.c_str(), json.size(), 1, jsonFile);
std::fclose(jsonFile);
@@ -1607,19 +1717,36 @@ bool d2ce::Character::saveAsD2s()
m_jsonfilename.clear();
p.replace_extension();
- m_d2sfilename = p.replace_filename(Bs.Name.data()).string() + ".d2s";
+ m_d2sfilename = p.replace_filename(std::filesystem::u8path(Bs.Name.data()));
+ m_d2sfilename.replace_extension(".d2s");
p = m_d2sfilename;
if (std::filesystem::exists(p))
{
// remove exist file, back would be down prior to call this
- std::remove(m_d2sfilename.c_str());
+ try
+ {
+ std::filesystem::remove(m_d2sfilename);
+ }
+ catch (std::filesystem::filesystem_error const&)
+ {
+ }
}
- // rename temp file to character file
- if (std::rename(oldFileName.c_str(), m_d2sfilename.c_str()))
+ try
+ {
+ // rename temp file to character file
+ std::filesystem::rename(oldFileName, m_d2sfilename);
+ }
+ catch (std::filesystem::filesystem_error const&)
{
// this is a temporary d2s file created from a json input file
- std::remove(oldFileName.c_str());
+ try
+ {
+ std::filesystem::remove(oldFileName);
+ }
+ catch (std::filesystem::filesystem_error const&)
+ {
+ }
m_d2sfilename.clear();
initialize();
@@ -1628,7 +1755,7 @@ bool d2ce::Character::saveAsD2s()
}
initialize();
- if (!open(m_d2sfilename.c_str(), false)) // checksum is calulated and written below
+ if (!open(m_d2sfilename, false)) // checksum is calulated and written below
{
return false;
}
@@ -1652,6 +1779,7 @@ void d2ce::Character::writeBasicInfo()
Bs.Status &= ~EnumCharStatus::Died; // can't be resurrected
}
+ std::fseek(m_charfile, m_status_location, SEEK_SET);
std::uint8_t value = Bs.Status.bits();
std::fwrite(&value, sizeof(value), 1, m_charfile);
@@ -1737,15 +1865,17 @@ bool d2ce::Character::writeItems()
void d2ce::Character::writeTempFile()
{
m_tempfilename.clear();
- char name1[L_tmpnam_s];
- errno_t err = tmpnam_s(name1, L_tmpnam_s);
+ wchar_t name1[L_tmpnam_s];
+ std::wstring utempfilename;
+ errno_t err = _wtmpnam_s(name1, L_tmpnam_s);
if (err == 0)
{
- m_tempfilename = name1;
+ utempfilename = name1;
+ m_tempfilename = utempfilename;
}
std::FILE* tempfile = NULL;
- fopen_s(&tempfile, m_tempfilename.c_str(), "wb");
+ _wfopen_s(&tempfile, utempfilename.c_str(), L"wb");
std::rewind(m_charfile);
@@ -1922,6 +2052,7 @@ void d2ce::Character::headerAsJson(Json::Value& parent, bool bSerializedFormat)
header["progression"] = std::uint16_t(getTitle());
header["active_arms"] = WeaponSet;
header["class"] = getClassName();
+ header["class_id"] = std::uint16_t(getClass());
header["level"] = std::uint16_t(DisplayLevel);
if (Bs.Version >= EnumCharVersion::v109)
{
@@ -1935,7 +2066,7 @@ void d2ce::Character::headerAsJson(Json::Value& parent, bool bSerializedFormat)
Json::Value assignedSkills(Json::arrayValue);
for (auto& skillId : AssignedSkills)
{
- if (skillId == 0xFFFF)
+ if (skillId >= MAXUINT16)
{
++nullCount;
continue;
@@ -1947,15 +2078,15 @@ void d2ce::Character::headerAsJson(Json::Value& parent, bool bSerializedFormat)
--nullCount;
}
- assignedSkills.append(Cs.getSkillNameById(skillId));
+ assignedSkills.append(CharClassHelper::getSkillIndexById(std::uint16_t(skillId)));
}
header["assigned_skills"] = assignedSkills;
- header["left_skill"] = Cs.getSkillNameById(LeftSkill);
- header["right_skill"] = Cs.getSkillNameById(RightSkill);
+ header["left_skill"] = CharClassHelper::getSkillIndexById(std::uint16_t(LeftSkill));
+ header["right_skill"] = CharClassHelper::getSkillIndexById(std::uint16_t(RightSkill));
if (Bs.Version >= EnumCharVersion::v109)
{
- header["left_swap_skill"] = Cs.getSkillNameById(LeftSwapSkill);
- header["right_swap_skill"] = Cs.getSkillNameById(RightSwapSkill);
+ header["left_swap_skill"] = CharClassHelper::getSkillIndexById(std::uint16_t(LeftSwapSkill));
+ header["right_swap_skill"] = CharClassHelper::getSkillIndexById(std::uint16_t(RightSwapSkill));
}
// Appearances
@@ -2046,7 +2177,13 @@ void d2ce::Character::close()
if (!m_jsonfilename.empty())
{
// this is a temporary d2s file created from a json input file
- std::remove(m_d2sfilename.c_str());
+ try
+ {
+ std::filesystem::remove(m_d2sfilename);
+ }
+ catch (std::filesystem::filesystem_error const&)
+ {
+ }
}
m_d2sfilename.clear();
@@ -2055,9 +2192,9 @@ void d2ce::Character::close()
initialize();
}
//---------------------------------------------------------------------------
-const char* d2ce::Character::getPathName() const
+const std::filesystem::path& d2ce::Character::getPath() const
{
- return m_jsonfilename.empty() ? m_d2sfilename.c_str() : m_jsonfilename.c_str();
+ return m_jsonfilename.empty() ? m_d2sfilename : m_jsonfilename;
}
//---------------------------------------------------------------------------
std::string d2ce::Character::asJson(bool bSerializedFormat) const
@@ -2073,6 +2210,36 @@ std::string d2ce::Character::asJson(bool bSerializedFormat) const
return Json::writeString(builder, root);
}
//---------------------------------------------------------------------------
+void d2ce::Character::setDefaultTxtReader()
+{
+ setTxtReader(getDefaultTxtReader());
+}
+//---------------------------------------------------------------------------
+void d2ce::Character::setTxtReader(const d2ce::ITxtReader& txtReader)
+{
+ if (!ItemHelpers::isTxtReaderInitialized() || (&txtReader != &ItemHelpers::getTxtReader()))
+ {
+ ItemHelpers::setTxtReader(txtReader);
+ Cs.setTxtReader();
+ Merc.setTxtReader();
+ }
+}
+//---------------------------------------------------------------------------
+const d2ce::ITxtReader& d2ce::Character::getTxtReader() const
+{
+ return ItemHelpers::getTxtReader();
+}
+//---------------------------------------------------------------------------
+const std::string& d2ce::Character::getLanguage() const
+{
+ return ItemHelpers::getLanguage();
+}
+//---------------------------------------------------------------------------
+const std::string& d2ce::Character::setLanguage(const std::string& lang) const
+{
+ return ItemHelpers::setLanguage(lang);
+}
+//---------------------------------------------------------------------------
bool d2ce::Character::is_open() const
{
return m_charfile == nullptr ? false : true;
@@ -2208,30 +2375,9 @@ void d2ce::Character::updateBasicStats(BasicStats& bs)
// Remove any invalid characters from the name
bs.Name[15] = 0; // must be zero
std::string curName(bs.Name.data());
- std::string strNewText;
- for (size_t iPos = 0, numberOfUnderscores = 0, nLen = curName.size(); iPos < nLen; ++iPos)
- {
- char c = curName[iPos];
- if (std::isalpha(c))
- {
- strNewText += c;
- }
- else if ((c == '_' || c == '-') && !strNewText.empty() && numberOfUnderscores < 1)
- {
- strNewText += c;
- ++numberOfUnderscores;
- }
- }
-
- // trim bad characters
- strNewText.erase(strNewText.find_last_not_of("_-") + 1);
- if (strNewText.size() < 2)
- {
- strNewText = std::string(Bs.Name.data());
- }
-
+ LocalizationHelpers::CheckCharName(curName, ((bs.Version <= EnumCharVersion::v115) ? true : false));
bs.Name.fill(0);
- strcpy_s(bs.Name.data(), strNewText.length() + 1, strNewText.c_str());
+ strcpy_s(bs.Name.data(), curName.length() + 1, curName.c_str());
bs.Name[15] = 0; // must be zero
// Check Title
@@ -2274,7 +2420,7 @@ void d2ce::Character::updateBasicStats(BasicStats& bs)
if (oldClass != Bs.Class)
{
// classed changed
- Cs.updateClass(Bs.Class);
+ Cs.updateClass();
}
Acts.validateActs();
@@ -2322,7 +2468,12 @@ d2ce::EnumCharVersion d2ce::Character::getVersion() const
return EnumCharVersion::v110;
}
- return EnumCharVersion::v115;
+ if (Version < static_cast>(EnumCharVersion::v116))
+ {
+ return EnumCharVersion::v115;
+ }
+
+ return EnumCharVersion::v116;
}
//---------------------------------------------------------------------------
const std::array& d2ce::Character::getName() const
@@ -2374,6 +2525,10 @@ void d2ce::Character::ensureTitleAct(d2ce::EnumAct act)
{
if (!Acts.getActYetToStart(progression, act))
{
+ if (progression < EnumDifficulty::Hell && (act == EnumAct::V) && Acts.getActCompleted(progression, act))
+ {
+ progression = static_cast(static_cast>(progression) + 1);
+ }
break;
}
@@ -2400,15 +2555,14 @@ d2ce::EnumCharClass d2ce::Character::getClass() const
return Bs.Class;
}
//---------------------------------------------------------------------------
-std::string d2ce::Character::getClassName() const
+const std::string& d2ce::Character::getClassName() const
{
- auto idx = (std::uint8_t)getClass();
- if (idx < NUM_OF_CLASSES)
- {
- return ClassNames[idx];
- }
-
- return "";
+ return CharClassHelper::getClassName(getClass());
+}
+//---------------------------------------------------------------------------
+const std::string& d2ce::Character::getClassCode() const
+{
+ return CharClassHelper::getClassCode(getClass());
}
//---------------------------------------------------------------------------
/*
@@ -2492,16 +2646,51 @@ void d2ce::Character::setIsDeadCharacter(bool flag)
Bs.setIsDeadCharacter(flag);
}
//---------------------------------------------------------------------------
+bool d2ce::Character::isFemaleCharacter() const
+{
+ return Bs.isFemaleCharacter();
+}
+//---------------------------------------------------------------------------
std::uint32_t d2ce::Character::getLevel() const
{
return Cs.getLevel();
}
//---------------------------------------------------------------------------
+std::uint32_t d2ce::Character::getMaxLevel() const
+{
+ return Cs.getMaxLevel();
+}
+//---------------------------------------------------------------------------
std::uint32_t d2ce::Character::getExperience() const
{
return Cs.getExperience();
}
//---------------------------------------------------------------------------
+std::uint32_t d2ce::Character::getMaxExperience() const
+{
+ return Cs.getMaxExperience();
+}
+//---------------------------------------------------------------------------
+std::uint32_t d2ce::Character::getMinExperience(std::uint32_t level) const
+{
+ return Cs.getMinExperience(level);
+}
+//---------------------------------------------------------------------------
+std::uint32_t d2ce::Character::getMinExperienceLevel() const
+{
+ return Cs.getMinExperienceLevel();
+}
+//---------------------------------------------------------------------------
+std::uint32_t d2ce::Character::getNextExperience(std::uint32_t level) const
+{
+ return Cs.getNextExperience(level);
+}
+//---------------------------------------------------------------------------
+std::uint32_t d2ce::Character::getNextExperienceLevel() const
+{
+ return Cs.getNextExperienceLevel();
+}
+//---------------------------------------------------------------------------
std::uint32_t d2ce::Character::getMaxGoldInBelt() const
{
return Cs.getMaxGoldInBelt();
@@ -2543,6 +2732,8 @@ void d2ce::Character::setDifficultyComplete(d2ce::EnumDifficulty diff)
{
Bs.DifficultyLastPlayed = Bs.getTitleDifficulty();
Bs.StartingAct = Bs.getTitleAct();
+ StartingAct.fill(0);
+ StartingAct[static_cast>(Bs.DifficultyLastPlayed)] = 0x80 | static_cast>(Bs.StartingAct);
}
Acts.validateActs();
@@ -2564,6 +2755,8 @@ void d2ce::Character::setNoDifficultyComplete()
{
Bs.DifficultyLastPlayed = Bs.getTitleDifficulty();
Bs.StartingAct = Bs.getTitleAct();
+ StartingAct.fill(0);
+ StartingAct[static_cast>(Bs.DifficultyLastPlayed)] = 0x80 | static_cast>(Bs.StartingAct);
}
Acts.validateActs();
@@ -2611,7 +2804,7 @@ std::uint32_t d2ce::Character::getSkillPointsEarned() const
//---------------------------------------------------------------------------
std::uint32_t d2ce::Character::getSkillPointsEarned(std::uint32_t level) const
{
- return (std::min(d2ce::NUM_OF_LEVELS, level) - 1) + Acts.getSkillPointsEarned();
+ return (std::min(Cs.getMaxLevel(), level) - 1) + Acts.getSkillPointsEarned();
}
//---------------------------------------------------------------------------
std::uint32_t d2ce::Character::getLevelFromTotalSkillPoints() const
@@ -2621,7 +2814,7 @@ std::uint32_t d2ce::Character::getLevelFromTotalSkillPoints() const
//---------------------------------------------------------------------------
std::uint32_t d2ce::Character::getLevelFromSkillPointsEarned(std::uint32_t earned) const
{
- return std::min(d2ce::NUM_OF_LEVELS + 1, earned - Acts.getSkillPointsEarned() + 1);
+ return std::min(Cs.getMaxLevel() + 1, earned - Acts.getSkillPointsEarned() + 1);
}
//---------------------------------------------------------------------------
std::uint32_t d2ce::Character::getTotalStartStatPoints() const
@@ -2646,7 +2839,7 @@ std::uint32_t d2ce::Character::getStatPointsEarned() const
//---------------------------------------------------------------------------
std::uint32_t d2ce::Character::getStatPointsEarned(std::uint32_t level) const
{
- return std::uint16_t(std::min(d2ce::NUM_OF_LEVELS, level) - 1) * 5 + Acts.getStatPointsEarned();
+ return std::uint16_t(std::min(Cs.getMaxLevel(), level) - 1) * Cs.getStatPointsPerLevel() + Acts.getStatPointsEarned();
}
//---------------------------------------------------------------------------
std::uint32_t d2ce::Character::getLevelFromTotalStatPoints() const
@@ -2661,7 +2854,17 @@ std::uint32_t d2ce::Character::getLevelFromTotalStatPoints() const
//---------------------------------------------------------------------------
std::uint32_t d2ce::Character::getLevelFromStatPointsEarned(std::uint32_t earned) const
{
- return std::min(d2ce::NUM_OF_LEVELS + 1, (earned - Acts.getStatPointsEarned()) / 5 + 1);
+ return std::min(Cs.getMaxLevel() + 1, (earned - Acts.getStatPointsEarned()) / Cs.getStatPointsPerLevel() + 1);
+}
+//---------------------------------------------------------------------------
+std::uint32_t d2ce::Character::getLevelFromExperience() const
+{
+ return Cs.getLevelFromExperience();
+}
+//---------------------------------------------------------------------------
+std::uint32_t d2ce::Character::getLevelFromExperience(std::uint32_t experience) const
+{
+ return Cs.getLevelFromExperience(experience);
}
//---------------------------------------------------------------------------
const d2ce::ActsInfo& d2ce::Character::getQuests()
@@ -2730,6 +2933,11 @@ void d2ce::Character::clearSkillChoices()
Cs.clearSkillChoices();
}
//---------------------------------------------------------------------------
+bool d2ce::Character::getSkillBonusPoints(std::vector& points) const
+{
+ return Cs.getSkillBonusPoints(points);
+}
+//---------------------------------------------------------------------------
/*
Returns the number of m_items belonging to the character
Value returned excludes socketed gems/jewels/runes.
diff --git a/source/d2ce/Character.h b/source/d2ce/Character.h
index 735ded9f..6823f590 100644
--- a/source/d2ce/Character.h
+++ b/source/d2ce/Character.h
@@ -30,6 +30,7 @@
#include "Item.h"
#include "SharedStash.h"
#include
+#include
namespace d2ce
{
@@ -45,7 +46,7 @@ namespace d2ce
std::uint32_t FileSize = 0; // pos 8 (1.09+ only), file's size
long Checksum = 0; // pos 12 (1.09+ only), stores (possible) checksum
std::uint32_t WeaponSet = 0; // pos 16 (1.09+, otherwise pos 26 uint16_t)
- BasicStats Bs; // Name: pos 20 (1.09+, otherwise pos 8),
+ BasicStats Bs; // Name: pos 267 (D2R 1.2+, pos 20 for 1.09 - 1.14d, otherwise pos 8),
// name includes terminating NULL
// Status: pos 36 (1.09+, otherwise, pos 24), determines character status
// Title: pos 37 (1.09+, otherwise pos 25), character's title
@@ -87,11 +88,12 @@ namespace d2ce
// the following variables are not part of the character file format
std::FILE* m_charfile = nullptr;
- std::string m_d2sfilename, m_jsonfilename, m_tempfilename;
+ std::filesystem::path m_d2sfilename, m_jsonfilename, m_tempfilename;
std::error_code m_error_code;
std::uint32_t m_filesize_location,
m_checksum_location,
m_name_location = 0,
+ m_status_location = 0,
m_class_location = 0,
m_level_location = 0,
m_starting_location = 0,
@@ -108,8 +110,8 @@ namespace d2ce
void calculateChecksum();
void initialize();
- bool openD2S(const char* filename, bool validateChecksum = true);
- bool openJson(const char* filename);
+ bool openD2S(const std::filesystem::path& path, bool validateChecksum = true);
+ bool openJson(const std::filesystem::path& path);
void readHeader();
void readHeader(const Json::Value& root);
bool isValidHeader() const;
@@ -137,13 +139,20 @@ namespace d2ce
~Character();
// File operations
- bool open(const char* filename, bool validateChecksum = true);
+ bool open(const std::filesystem::path& path, bool validateChecksum = true);
bool refresh();
bool save();
bool saveAsD2s();
void close();
- const char *getPathName() const;
- std::string asJson(bool bSerializedFormat = false) const;
+ const std::filesystem::path& getPath() const;
+ std::string asJson(bool bSerializedFormat = false) const; // utf-8
+
+ void setDefaultTxtReader();
+ void setTxtReader(const ITxtReader& txtReader);
+ const ITxtReader& getTxtReader() const;
+
+ const std::string& getLanguage() const;
+ const std::string& setLanguage(const std::string& lang) const;
bool is_open() const;
bool is_json() const;
@@ -185,7 +194,8 @@ namespace d2ce
EnumAct getTitleAct() const;
void ensureTitleAct(EnumAct act);
EnumCharClass getClass() const;
- std::string getClassName() const;
+ const std::string& getClassName() const;
+ const std::string& getClassCode() const;
EnumDifficulty getDifficultyLastPlayed() const;
EnumAct getStartingAct() const;
EnumAct getLastAct() const;
@@ -202,6 +212,7 @@ namespace d2ce
void setIsResurrectedCharacter(bool flag);
bool isDeadCharacter() const;
void setIsDeadCharacter(bool flag);
+ bool isFemaleCharacter() const;
bool isGameComplete() const;
void setGameComplete();
bool isDifficultyComplete(d2ce::EnumDifficulty diff) const;
@@ -209,7 +220,13 @@ namespace d2ce
void setNoDifficultyComplete();
std::uint32_t getLevel() const;
+ std::uint32_t getMaxLevel() const;
std::uint32_t getExperience() const;
+ std::uint32_t getMaxExperience() const;
+ std::uint32_t getMinExperience(std::uint32_t level) const;
+ std::uint32_t getMinExperienceLevel() const;
+ std::uint32_t getNextExperience(std::uint32_t level) const;
+ std::uint32_t getNextExperienceLevel() const;
std::uint32_t getMaxGoldInBelt() const;
std::uint32_t getMaxGoldInStash() const;
std::uint32_t getMinStrength() const;
@@ -231,7 +248,8 @@ namespace d2ce
std::uint32_t getStatPointsEarned(std::uint32_t level) const;
std::uint32_t getLevelFromTotalStatPoints() const;
std::uint32_t getLevelFromStatPointsEarned(std::uint32_t earned) const;
-
+ std::uint32_t getLevelFromExperience() const;
+ std::uint32_t getLevelFromExperience(std::uint32_t experience) const;
// Quests
const ActsInfo& getQuests();
void updateQuests(const ActsInfo& qi);
@@ -253,6 +271,8 @@ namespace d2ce
void resetSkills();
void clearSkillChoices();
+ bool getSkillBonusPoints(std::vector& points) const;
+
// Items
size_t getNumberOfItems() const;
diff --git a/source/d2ce/CharacterConstants.h b/source/d2ce/CharacterConstants.h
index 3903feb5..d515c047 100644
--- a/source/d2ce/CharacterConstants.h
+++ b/source/d2ce/CharacterConstants.h
@@ -25,6 +25,7 @@
#include "bitmask.hpp"
#include
#include
+#include
namespace d2ce
{
@@ -39,15 +40,13 @@ namespace d2ce
constexpr std::uint32_t APPEARANCES_LENGTH = 32;
// character version
- enum class EnumCharVersion : std::uint32_t { v100 = 0x47, v107 = 0x57, v108 = 0x59, v109 = 0x5C, v110 = 0x60, v115 = 0x61 };
+ enum class EnumCharVersion : std::uint32_t { v100 = 0x47, v107 = 0x57, v108 = 0x59, v109 = 0x5C, v110 = 0x60, v115 = 0x61, v116 = 0x62};
- constexpr EnumCharVersion APP_CHAR_VERSION = EnumCharVersion::v115; // default version used by application
+ constexpr EnumCharVersion APP_CHAR_VERSION = EnumCharVersion::v116; // default version used by application
// character class
enum class EnumCharClass : std::uint8_t { Amazon, Sorceress, Necromancer, Paladin, Barbarian, Druid, Assassin };
- const std::array ClassNames = { "Amazon", "Sorceress", "Necromancer", "Paladin", "Barbarian", "Druid", "Assassin"};
-
// character status
enum class EnumCharStatus : std::uint8_t { NoDeaths, Hardcore = 0x04, Died = 0x08, Expansion = 0x20, Ladder = 0x40, Dead = 0x0C };
BITMASK_DEFINE_VALUE_MASK(EnumCharStatus, 0xFF);
@@ -58,6 +57,7 @@ namespace d2ce
None = 0, SirDame, LordLady, BaronBaroness
};
+ //---------------------------------------------------------------------------
// error codes
enum class CharacterErrc
{
@@ -78,5 +78,114 @@ namespace d2ce
const char* name() const noexcept override;
std::string message(int ev) const override;
};
+
+ class ITxtDocument
+ {
+ public:
+ virtual ~ITxtDocument() = default;
+ virtual std::string GetRowName(SSIZE_T pRowIdx) const = 0;
+ virtual std::vector GetColumnNames() const = 0;
+ virtual std::string GetColumnName(SSIZE_T pColumnIdx) const = 0;
+ virtual size_t GetRowCount() const = 0;
+ virtual size_t GetColumnCount() const = 0;
+ virtual SSIZE_T GetColumnIdx(const std::string& pColumnName) const = 0;
+ virtual std::string GetCellString(size_t pColumnIdx, size_t pRowIdx) const = 0;
+ virtual std::uint64_t GetCellUInt64(size_t pColumnIdx, size_t pRowIdx) const = 0;
+ virtual std::uint32_t GetCellUInt32(size_t pColumnIdx, size_t pRowIdx) const = 0;
+ virtual std::uint16_t GetCellUInt16(size_t pColumnIdx, size_t pRowIdx) const = 0;
+
+ // used for string localization loads
+ virtual size_t GetRowValues(size_t rowIdx, std::string& index, std::map& stringCols) const = 0;
+
+ };
+
+ //---------------------------------------------------------------------------
+ class ITxtReader
+ {
+ public:
+ virtual ~ITxtReader() {};
+ virtual std::unique_ptr GetStringTxt() const = 0;
+ virtual std::unique_ptr GetExpansionStringTxt() const = 0;
+ virtual std::unique_ptr GetPatchStringTxt() const = 0;
+ virtual std::unique_ptr GetCharStatsTxt() const = 0;
+ virtual std::unique_ptr GetPlayerClassTxt() const = 0;
+ virtual std::unique_ptr GetExperienceTxt() const = 0;
+ virtual std::unique_ptr GetHirelingTxt() const = 0;
+ virtual std::unique_ptr GetItemStatCostTxt() const = 0;
+ virtual std::unique_ptr GetItemTypesTxt() const = 0;
+ virtual std::unique_ptr GetPropertiesTxt() const = 0;
+ virtual std::unique_ptr GetGemsTxt() const = 0;
+ virtual std::unique_ptr GetBeltsTxt() const = 0;
+ virtual std::unique_ptr GetArmorTxt() const = 0;
+ virtual std::unique_ptr GetWeaponsTxt() const = 0;
+ virtual std::unique_ptr GetMiscTxt() const = 0;
+ virtual std::unique_ptr GetMagicPrefixTxt() const = 0;
+ virtual std::unique_ptr GetMagicSuffixTxt() const = 0;
+ virtual std::unique_ptr GetRarePrefixTxt() const = 0;
+ virtual std::unique_ptr GetRareSuffixTxt() const = 0;
+ virtual std::unique_ptr GetUniqueItemsTxt() const = 0;
+ virtual std::unique_ptr GetSetsTxt() const = 0;
+ virtual std::unique_ptr GetSetItemsTxt() const = 0;
+ virtual std::unique_ptr GetRunesTxt() const = 0;
+ virtual std::unique_ptr GetSkillsTxt() const = 0;
+ virtual std::unique_ptr GetSkillDescTxt() const = 0;
+ };
+
+ const ITxtReader& getDefaultTxtReader();
+
+ struct ClassSkillType
+ {
+ EnumCharClass charClass = EnumCharClass::Amazon; // To what character class is this skill assigned (null for skill not specific to a character)
+ std::uint16_t index = 0; // What index in class skill array does this skill refer to
+ std::uint16_t iconIndex = MAXUINT16; // the character icon index used by this skill
+ };
+
+ struct SkillType
+ {
+ std::string index; // The ID pointer used to reference this skill
+ std::uint16_t id = MAXUINT16; // The actual ID number of the skill, this is what the pointer actually points at, this must be a unique number
+ std::string name; // What string will be displayed in-game for this skill
+ std::string longName;
+
+ std::optional classInfo; // additional information about class-specific skills
+
+ std::uint16_t reqLevel = 1ui16; // The minimum character level required in order to put a point in this skill
+ std::vector reqSkills; // The ID pointers (index) of the other skills you need in order to put your first point in this skill
+ };
+
+ //---------------------------------------------------------------------------
+ class CharClassHelper
+ {
+ public:
+ static const std::string& getClassName(std::int16_t idx); // localized name
+ static const std::string& getClassName(EnumCharClass charClass); // localized name
+ static const std::string& getClassName(const std::string& classCode); // localized name
+ static const std::string& getClassCode(std::int16_t idx);
+ static const std::string& getClassCode(EnumCharClass charClass);
+
+ static bool getEnumCharClassByName(const std::string& name, EnumCharClass& value); // localized name
+ static bool getEnumCharClassByIndex(const std::string& name, EnumCharClass& value); // non-localized name
+ static bool getEnumCharClassByCode(const std::string& classCode, EnumCharClass& value);
+
+ static const std::string& getStrAllSkills(std::int16_t idx);
+ static const std::string& getStrAllSkills(EnumCharClass charClass);
+ static const std::string& getStrSkillTab(std::int16_t tab, std::int16_t idx);
+ static const std::string& getStrSkillTab(std::int16_t tab, EnumCharClass charClass);
+ static const std::string& getStrClassOnly(std::int16_t idx);
+ static const std::string& getStrClassOnly(EnumCharClass charClass);
+
+ // skill tab names sorted by ascending tab number
+ static const std::map>& getSklTreeTab(std::int16_t tab, std::int16_t idx);
+ static const std::map>& getSklTreeTab(std::int16_t tab, EnumCharClass charClass); // The skill ids for the skill tab, organized by row and column
+ static const std::string& getSkillTabName(std::int16_t tab, std::int16_t idx);
+ static const std::string& getSkillTabName(std::int16_t tab, EnumCharClass charClass);
+
+ static const SkillType& getSkillById(std::uint16_t skill);
+ static const SkillType& getSkillByClass(EnumCharClass charClass, std::uint16_t skill);
+ static std::string getSkillIndexById(std::uint16_t id);
+ static std::string getSkillNameById(std::uint16_t id);
+ static std::string getSkillNameByClass(EnumCharClass charClass, std::uint16_t skill);
+ static const SkillType& getSkillByIndex(const std::string& index);
+ };
}
//---------------------------------------------------------------------------
\ No newline at end of file
diff --git a/source/d2ce/CharacterStats.cpp b/source/d2ce/CharacterStats.cpp
index e60c343c..a31b54ba 100644
--- a/source/d2ce/CharacterStats.cpp
+++ b/source/d2ce/CharacterStats.cpp
@@ -19,63 +19,1092 @@
#include "pch.h"
#include "CharacterStats.h"
+#include "Character.h"
#include "SkillConstants.h"
#include "ItemConstants.h"
+#include
+
//---------------------------------------------------------------------------
namespace d2ce
{
+ constexpr std::uint32_t MAX_EXPERIENCE = 3600000000ui32; // experience max value (game limit)
+ constexpr std::uint32_t MAX_NUM_LEVELS = 99ui32; // level max value (game limit)
+
constexpr std::array STATS_MARKER = { 0x67, 0x66 }; // alternatively "gf"
constexpr std::uint16_t STAT_MAX = 16;
constexpr std::uint16_t STAT_END_MARKER = 0x1FF;
constexpr size_t STAT_BITS = 9;
constexpr std::array V110_BITS_PER_STAT = { 10,10,10,10,10,8,21,21,21,21,21,21,7,32,25,25 };
- constexpr std::uint32_t MIN_START_STATS_POS = 765;
+ constexpr std::uint32_t MIN_START_STATS_POS = 765;
+
+ constexpr std::array SKILLS_MARKER = { 0x69, 0x66 }; // alternatively "if"
+
+ std::map> s_MinExpRequired;
+ void InitExperienceData(const ITxtReader& txtReader)
+ {
+ static const ITxtReader* pCurTextReader = nullptr;
+ if (!s_MinExpRequired.empty())
+ {
+ if (pCurTextReader == &txtReader)
+ {
+ // already initialized
+ return;
+ }
+
+ s_MinExpRequired.clear();
+ }
+
+ pCurTextReader = &txtReader;
+ auto pDoc(txtReader.GetExperienceTxt());
+ auto& doc = *pDoc;
+ std::map> minExpRequired;
+ size_t numRows = doc.GetRowCount();
+ const SSIZE_T levelColumnIdx = doc.GetColumnIdx("Level");
+ if (levelColumnIdx < 0)
+ {
+ return;
+ }
+
+ std::map classColumnIndex;
+ classColumnIndex[d2ce::EnumCharClass::Amazon] = doc.GetColumnIdx("Amazon");
+ if (classColumnIndex[d2ce::EnumCharClass::Amazon] < 0)
+ {
+ return;
+ }
+
+ classColumnIndex[d2ce::EnumCharClass::Sorceress] = doc.GetColumnIdx("Sorceress");
+ if (classColumnIndex[d2ce::EnumCharClass::Sorceress] < 0)
+ {
+ return;
+ }
+
+ classColumnIndex[d2ce::EnumCharClass::Necromancer] = doc.GetColumnIdx("Necromancer");
+ if (classColumnIndex[d2ce::EnumCharClass::Necromancer] < 0)
+ {
+ return;
+ }
+
+ classColumnIndex[d2ce::EnumCharClass::Paladin] = doc.GetColumnIdx("Paladin");
+ if (classColumnIndex[d2ce::EnumCharClass::Paladin] < 0)
+ {
+ return;
+ }
+
+ classColumnIndex[d2ce::EnumCharClass::Barbarian] = doc.GetColumnIdx("Barbarian");
+ if (classColumnIndex[d2ce::EnumCharClass::Barbarian] < 0)
+ {
+ return;
+ }
+
+ classColumnIndex[d2ce::EnumCharClass::Druid] = doc.GetColumnIdx("Druid");
+ if (classColumnIndex[d2ce::EnumCharClass::Druid] < 0)
+ {
+ return;
+ }
+
+ classColumnIndex[d2ce::EnumCharClass::Assassin] = doc.GetColumnIdx("Assassin");
+ if (classColumnIndex[d2ce::EnumCharClass::Assassin] < 0)
+ {
+ return;
+ }
+
+ std::string strValue;
+ std::uint32_t exp = 0;
+ std::uint32_t level = 0;
+ for (size_t i = 0; i < numRows; ++i)
+ {
+ strValue = doc.GetCellString(levelColumnIdx, i);
+ if (strValue.empty())
+ {
+ // skip
+ continue;
+ }
+
+ if (strValue == "MaxLvl")
+ {
+ // first row is the MaxLvl row, so initialize vector to fit size
+ for (auto colIdx : classColumnIndex)
+ {
+ level = 99ui32;
+ strValue = doc.GetCellString(colIdx.second, i);
+ if (!strValue.empty())
+ {
+ level = std::min(doc.GetCellUInt32(colIdx.second, i), 127ui32);
+ }
+
+ minExpRequired[colIdx.first].resize(level + 1, 0ui32);
+ }
+ continue;
+ }
+
+ for (auto colIdx : classColumnIndex)
+ {
+ exp = 0;
+ strValue = doc.GetCellString(colIdx.second, i);
+ if (!strValue.empty())
+ {
+ exp = doc.GetCellUInt32(colIdx.second, i);
+ }
+
+ auto& expReqList = minExpRequired[colIdx.first];
+ if (expReqList.size() >= i)
+ {
+ expReqList[i - 1] = exp;
+ }
+ }
+ }
+
+ s_MinExpRequired.swap(minExpRequired);
+ }
+
+ struct CharacterInfoType
+ {
+ std::string ClassIndex; // The character class this line refers to (this is just a reference field, you can't actually change this).
+ std::string ClassName; // The localized name for the class
+ d2ce::EnumCharClass ClassEnum = d2ce::EnumCharClass::Amazon;
+ std::string Code; // code used to reference this class in other files
+ std::uint32_t Version = 0;
+
+ std::uint32_t Strength = 0; // The amount of strength this character class will start with.
+ std::uint32_t Dexterity = 0; // The amount of dexterity this character class will start with.
+ std::uint32_t Energy = 0; // The amount of energy this character class will start with.
+ std::uint32_t Vitality = 0; // The amount of vitality this character class will start with.
+ std::uint32_t TotalStats = 0; // The total amount of the above starting stats
+ std::uint32_t Stamina = 0; // The amount of stamina this character class will start with.
+
+ std::uint32_t HpAdd = 0; // The amount of life added to the amount of life granted by the vit column.
+ std::uint32_t LifePerLevel = 0; // Amount of life earned for each level up. This value is in fourths, thus the lowest bonus possible is 64/256 (on quarter of one on-screen point, the fractional value is used by the game however).
+ std::uint32_t StaminaPerLevel = 0; // Amount of stamina earned for each level up. This value is in fourths, thus the lowest bonus possible is 64/256 (on quarter of one on-screen point, the fractional value is used by the game however).
+ std::uint32_t ManaPerLevel = 0; // Amount of mana earned for each level up. This value is in fourths, thus the lowest bonus possible is 64/256 (on quarter of one on-screen point, the fractional value is used by the game however).
+ std::uint32_t StatPerLevel = 0; // Amount of stat points earned at each level up.
+
+ std::uint32_t LifePerVitality = 0; // Amount of life earned for each point invested in vitality. This value is in fourths, thus the lowest bonus possible is 64/256 (on quarter of one on-screen point, the fractional value is used by the game however).
+ std::uint32_t StaminaPerVitality = 0; // Amount of stamina earned for each point invested in vitality. This value is in fourths, thus the lowest bonus possible is 64/256 (on quarter of one on-screen point, the fractional value is used by the game however).
+
+ std::uint32_t ManaPerMagic = 0; // Amount of mana earned for each point invested in energy. This value is in fourths, thus the lowest bonus possible is 64/256 (on quarter of one on-screen point, the fractional value is used by the game however).
+
+ std::string StrAllSkills; // This field tells the game what string to display for the bonus to all class skills (ex: +1 to all Amazon skills).
+ std::string StrSkillTab1; // This field tells the game what string to display for the bonus to all skills of the first skill tab (ex: +1 to all Bow and Crossbow skills).
+ std::string StrSkillTab2; // This field tells the game what string to display for the bonus to all skills of the second skill tab (ex: +1 to all Passive and Magic skills).
+ std::string StrSkillTab3; // This field tells the game what string to display for the bonus to all skills of the third skill tab (ex: +1 to all Javelin and Spear skills).
+ std::string StrClassOnly; // This field tells the game what string to display for class specific items (and class specific skill bonus) (ex: Amazon Only).
+
+ std::string StrSklTreeTab1; // The string to display for the first skill tab
+ std::string StrSklTreeTab2; // the string to display for the second skill tab
+ std::string StrSklTreeTab3; // the string to display for the third skill tab
+
+ std::map> SklTreeTab1; // The skill ids for the first skill tab, organized by row and column
+ std::map> SklTreeTab2; // The skill ids for the second skill tab, organized by row and column
+ std::map> SklTreeTab3; // The skill ids for the third skill tab, organized by row and column
+
+ std::vector Skills; // skill ids in original array position as found in character file
+ };
+
+ std::map s_CharClassEnumNameMap = { {"Amazon", d2ce::EnumCharClass::Amazon},
+ {"Sorceress", d2ce::EnumCharClass::Sorceress}, {"Necromancer", d2ce::EnumCharClass::Necromancer},
+ {"Paladin", d2ce::EnumCharClass::Paladin}, {"Barbarian", d2ce::EnumCharClass::Barbarian},
+ {"Druid", d2ce::EnumCharClass::Druid}, {"Assassin", d2ce::EnumCharClass::Assassin} };
+ std::map s_CharClassNameMap;
+ std::map s_CharClassEnumMap;
+ std::map s_CharClassInfo;
+ void InitCharStatsData(const ITxtReader& txtReader)
+ {
+ static const ITxtReader* pCurTextReader = nullptr;
+ if (!s_CharClassInfo.empty())
+ {
+ if (pCurTextReader == &txtReader)
+ {
+ // already initialized
+ return;
+ }
+
+ s_CharClassNameMap.clear();
+ s_CharClassEnumMap.clear();
+ s_CharClassInfo.clear();
+ }
+
+ InitExperienceData(txtReader);
+ pCurTextReader = &txtReader;
+ auto pDoc(txtReader.GetCharStatsTxt());
+ auto& doc = *pDoc;
+ std::map charClassNameMap;
+ std::map charClassEnumMap;
+ std::map charClassInfo;
+ size_t numRows = doc.GetRowCount();
+ const SSIZE_T classColumnIdx = doc.GetColumnIdx("class");
+ if (classColumnIdx < 0)
+ {
+ return;
+ }
+
+ const SSIZE_T strColumnIdx = doc.GetColumnIdx("str");
+ if (strColumnIdx < 0)
+ {
+ return;
+ }
+
+ const SSIZE_T dexColumnIdx = doc.GetColumnIdx("dex");
+ if (dexColumnIdx < 0)
+ {
+ return;
+ }
+
+ const SSIZE_T intColumnIdx = doc.GetColumnIdx("int");
+ if (intColumnIdx < 0)
+ {
+ return;
+ }
+
+ const SSIZE_T vitColumnIdx = doc.GetColumnIdx("vit");
+ if (vitColumnIdx < 0)
+ {
+ return;
+ }
+
+ const SSIZE_T staminaColumnIdx = doc.GetColumnIdx("stamina");
+ if (staminaColumnIdx < 0)
+ {
+ return;
+ }
+
+ const SSIZE_T hpaddColumnIdx = doc.GetColumnIdx("hpadd");
+ if (hpaddColumnIdx < 0)
+ {
+ return;
+ }
+
+ const SSIZE_T lifePerLevelColumnIdx = doc.GetColumnIdx("LifePerLevel");
+ if (lifePerLevelColumnIdx < 0)
+ {
+ return;
+ }
+
+ const SSIZE_T staminaPerLevelColumnIdx = doc.GetColumnIdx("StaminaPerLevel");
+ if (staminaPerLevelColumnIdx < 0)
+ {
+ return;
+ }
+
+ const SSIZE_T manaPerLevelColumnIdx = doc.GetColumnIdx("ManaPerLevel");
+ if (manaPerLevelColumnIdx < 0)
+ {
+ return;
+ }
+
+ const SSIZE_T lifePerVitalityColumnIdx = doc.GetColumnIdx("LifePerVitality");
+ if (lifePerVitalityColumnIdx < 0)
+ {
+ return;
+ }
+
+ const SSIZE_T staminaPerVitalityColumnIdx = doc.GetColumnIdx("StaminaPerVitality");
+ if (staminaPerVitalityColumnIdx < 0)
+ {
+ return;
+ }
+
+ const SSIZE_T manaPerMagicColumnIdx = doc.GetColumnIdx("ManaPerMagic");
+ if (manaPerMagicColumnIdx < 0)
+ {
+ return;
+ }
+
+ const SSIZE_T statPerLevelColumnIdx = doc.GetColumnIdx("StatPerLevel");
+ if (statPerLevelColumnIdx < 0)
+ {
+ return;
+ }
+
+ const SSIZE_T strAllSkillsColumnIdx = doc.GetColumnIdx("StrAllSkills");
+ if (strAllSkillsColumnIdx < 0)
+ {
+ return;
+ }
+
+ const SSIZE_T strSkillTab1ColumnIdx = doc.GetColumnIdx("StrSkillTab1");
+ if (strSkillTab1ColumnIdx < 0)
+ {
+ return;
+ }
+
+ const SSIZE_T strSkillTab2ColumnIdx = doc.GetColumnIdx("StrSkillTab2");
+ if (strSkillTab2ColumnIdx < 0)
+ {
+ return;
+ }
+
+ const SSIZE_T strSkillTab3ColumnIdx = doc.GetColumnIdx("StrSkillTab3");
+ if (strSkillTab3ColumnIdx < 0)
+ {
+ return;
+ }
+
+ const SSIZE_T strClassOnlyColumnIdx = doc.GetColumnIdx("StrClassOnly");
+ if (strClassOnlyColumnIdx < 0)
+ {
+ return;
+ }
+
+ std::uint16_t version = 0;
+ std::string strValue;
+ std::string index;
+ std::uint16_t idx = 0;
+ for (size_t i = 0; i < numRows; ++i)
+ {
+ index = doc.GetCellString(classColumnIdx, i);
+ if (index.empty())
+ {
+ // skip
+ continue;
+ }
+
+ if (index == "Expansion")
+ {
+ // skip
+ version = 100;
+ continue;
+ }
+
+ auto iter = s_CharClassEnumNameMap.find(index);
+ if (iter != s_CharClassEnumNameMap.end())
+ {
+ charClassEnumMap[iter->second] = idx;
+ }
+
+ LocalizationHelpers::GetStringTxtValue(index, strValue);
+ charClassNameMap[strValue] = idx;
+ auto& item = charClassInfo[idx];
+ ++idx;
+ item.ClassIndex = index;
+ item.Version = version;
+ item.ClassEnum = iter->second;
+ item.ClassName = strValue;
+
+ strValue = doc.GetCellString(strColumnIdx, i);
+ if (!strValue.empty())
+ {
+ item.Strength = doc.GetCellUInt32(strColumnIdx, i);
+ }
+
+ strValue = doc.GetCellString(dexColumnIdx, i);
+ if (!strValue.empty())
+ {
+ item.Dexterity = doc.GetCellUInt32(dexColumnIdx, i);
+ }
+
+ strValue = doc.GetCellString(intColumnIdx, i);
+ if (!strValue.empty())
+ {
+ item.Energy = doc.GetCellUInt32(intColumnIdx, i);
+ }
+
+ strValue = doc.GetCellString(vitColumnIdx, i);
+ if (!strValue.empty())
+ {
+ item.Vitality = doc.GetCellUInt32(vitColumnIdx, i);
+ }
+ item.TotalStats = item.Strength + item.Dexterity + item.Energy + item.Vitality;
+
+ strValue = doc.GetCellString(staminaColumnIdx, i);
+ if (!strValue.empty())
+ {
+ item.Stamina = doc.GetCellUInt32(staminaColumnIdx, i);
+ }
+
+ strValue = doc.GetCellString(hpaddColumnIdx, i);
+ if (!strValue.empty())
+ {
+ item.HpAdd = doc.GetCellUInt32(hpaddColumnIdx, i);
+ }
+
+ strValue = doc.GetCellString(lifePerLevelColumnIdx, i);
+ if (!strValue.empty())
+ {
+ item.LifePerLevel = doc.GetCellUInt32(lifePerLevelColumnIdx, i);
+ }
+
+ strValue = doc.GetCellString(staminaPerLevelColumnIdx, i);
+ if (!strValue.empty())
+ {
+ item.StaminaPerLevel = doc.GetCellUInt32(staminaPerLevelColumnIdx, i);
+ }
+
+ strValue = doc.GetCellString(manaPerLevelColumnIdx, i);
+ if (!strValue.empty())
+ {
+ item.ManaPerLevel = doc.GetCellUInt32(manaPerLevelColumnIdx, i);
+ }
+
+ strValue = doc.GetCellString(lifePerVitalityColumnIdx, i);
+ if (!strValue.empty())
+ {
+ item.LifePerVitality = doc.GetCellUInt32(lifePerVitalityColumnIdx, i);
+ }
+
+ strValue = doc.GetCellString(staminaPerVitalityColumnIdx, i);
+ if (!strValue.empty())
+ {
+ item.StaminaPerVitality = doc.GetCellUInt32(staminaPerVitalityColumnIdx, i);
+ }
+
+ strValue = doc.GetCellString(manaPerMagicColumnIdx, i);
+ if (!strValue.empty())
+ {
+ item.ManaPerMagic = doc.GetCellUInt32(manaPerMagicColumnIdx, i);
+ }
+
+ strValue = doc.GetCellString(statPerLevelColumnIdx, i);
+ if (!strValue.empty())
+ {
+ item.StatPerLevel = doc.GetCellUInt32(statPerLevelColumnIdx, i);
+ }
+
+ strValue = doc.GetCellString(strAllSkillsColumnIdx, i);
+ if (!LocalizationHelpers::GetStringTxtValue(strValue, item.StrAllSkills))
+ {
+ std::stringstream ss;
+ ss << "%+d to ";
+ ss << item.ClassIndex;
+ ss << " Skill Levels";
+ item.StrAllSkills = ss.str();
+ }
+
+ std::string strFind = "%+d";
+ auto strPos = item.StrAllSkills.find(strFind);
+ if (strPos != item.StrAllSkills.npos)
+ {
+ item.StrAllSkills.replace(strPos, strFind.size(), "+{1}");
+ }
+ else
+ {
+ // support classic txt files
+ item.StrAllSkills = "+{1} " + item.StrAllSkills;
+ }
+
+ strValue = doc.GetCellString(strSkillTab1ColumnIdx, i);
+ LocalizationHelpers::GetStringTxtValue(strValue, item.StrSkillTab1);
+ strFind = "%+d";
+ strPos = item.StrSkillTab1.find(strFind);
+ if (strPos != item.StrSkillTab1.npos)
+ {
+ item.StrSkillTab1.replace(strPos, strFind.size(), "{2}");
+ }
+ else
+ {
+ // support classic txt files
+ strFind = "%d";
+ if (strPos != item.StrSkillTab1.npos)
+ {
+ item.StrSkillTab1.replace(strPos, strFind.size(), "{2}");
+ }
+ }
+
+ strValue = doc.GetCellString(strSkillTab2ColumnIdx, i);
+ LocalizationHelpers::GetStringTxtValue(strValue, item.StrSkillTab2);
+ strFind = "%+d";
+ strPos = item.StrSkillTab2.find(strFind);
+ if (strPos != item.StrSkillTab2.npos)
+ {
+ item.StrSkillTab2.replace(strPos, strFind.size(), "{2}");
+ }
+ else
+ {
+ // support classic txt files
+ strFind = "%d";
+ if (strPos != item.StrSkillTab2.npos)
+ {
+ item.StrSkillTab2.replace(strPos, strFind.size(), "{2}");
+ }
+ }
+
+ strValue = doc.GetCellString(strSkillTab3ColumnIdx, i);
+ LocalizationHelpers::GetStringTxtValue(strValue, item.StrSkillTab3);
+ strFind = "%+d";
+ strPos = item.StrSkillTab3.find(strFind);
+ if (strPos != item.StrSkillTab3.npos)
+ {
+ item.StrSkillTab3.replace(strPos, strFind.size(), "{2}");
+ }
+ else
+ {
+ // support classic txt files
+ strFind = "%d";
+ if (strPos != item.StrSkillTab2.npos)
+ {
+ item.StrSkillTab2.replace(strPos, strFind.size(), "{2}");
+ }
+ }
+
+ strValue = doc.GetCellString(strClassOnlyColumnIdx, i);
+ if (!LocalizationHelpers::GetStringTxtValue(strValue, item.StrClassOnly))
+ {
+ std::stringstream ss;
+ ss << "(";
+ ss << item.ClassIndex;
+ ss << " Only)";
+ item.StrClassOnly = ss.str();
+ }
+
+ switch (item.ClassEnum)
+ {
+ case EnumCharClass::Amazon:
+ LocalizationHelpers::GetStringTxtValue("SkillCategoryAm1", item.StrSklTreeTab1, "Javelin and Spear");
+ LocalizationHelpers::GetStringTxtValue("SkillCategoryAm2", item.StrSklTreeTab2, "Passive and Magic");
+ LocalizationHelpers::GetStringTxtValue("SkillCategoryAm3", item.StrSklTreeTab3, "Bow and Crossbow");
+ break;
+
+ case EnumCharClass::Sorceress:
+ LocalizationHelpers::GetStringTxtValue("SkillCategorySo1", item.StrSklTreeTab1, "Cold Spells");
+ LocalizationHelpers::GetStringTxtValue("SkillCategorySo2", item.StrSklTreeTab2, "Lightning Spells");
+ LocalizationHelpers::GetStringTxtValue("SkillCategorySo3", item.StrSklTreeTab3, "Fire Spells");
+ break;
+
+ case EnumCharClass::Necromancer:
+ LocalizationHelpers::GetStringTxtValue("SkillCategoryNe1", item.StrSklTreeTab1, "Summoning");
+ LocalizationHelpers::GetStringTxtValue("SkillCategoryNe2", item.StrSklTreeTab2, "Poison and Bone");
+ LocalizationHelpers::GetStringTxtValue("SkillCategoryNe3", item.StrSklTreeTab3, "Curses");
+ break;
+
+ case EnumCharClass::Paladin:
+ LocalizationHelpers::GetStringTxtValue("SkillCategoryPa1", item.StrSklTreeTab1, "Defensive Auras");
+ LocalizationHelpers::GetStringTxtValue("SkillCategoryPa2", item.StrSklTreeTab2, "Offensive Auras");
+ LocalizationHelpers::GetStringTxtValue("SkillCategoryPa3", item.StrSklTreeTab3, "Combat Skills");
+ break;
+
+ case EnumCharClass::Barbarian:
+ LocalizationHelpers::GetStringTxtValue("SkillCategoryBa1", item.StrSklTreeTab1, "Warcries");
+ LocalizationHelpers::GetStringTxtValue("SkillCategoryBa2", item.StrSklTreeTab2, "Combat Masteries");
+ LocalizationHelpers::GetStringTxtValue("SkillCategoryBa3", item.StrSklTreeTab3, "Combat Skills");
+ break;
+
+ case EnumCharClass::Druid:
+ LocalizationHelpers::GetStringTxtValue("SkillCategoryDr1", item.StrSklTreeTab1, "Elemental");
+ LocalizationHelpers::GetStringTxtValue("SkillCategoryDr2", item.StrSklTreeTab2, "Shape Shifting");
+ LocalizationHelpers::GetStringTxtValue("SkillCategoryDr3", item.StrSklTreeTab3, "Summoning");
+ break;
+
+ case EnumCharClass::Assassin:
+ LocalizationHelpers::GetStringTxtValue("SkillCategoryAs1", item.StrSklTreeTab1, "Martial Arts");
+ LocalizationHelpers::GetStringTxtValue("SkillCategoryAs2", item.StrSklTreeTab2, "Shadow Disciplines");
+ LocalizationHelpers::GetStringTxtValue("SkillCategoryAs3", item.StrSklTreeTab3, "Traps");
+ break;
+ }
+ }
+
+ s_CharClassNameMap.swap(charClassNameMap);
+ s_CharClassEnumMap.swap(charClassEnumMap);
+ s_CharClassInfo.swap(charClassInfo);
+ }
+
+ std::map s_CharClassCodeMap;
+ void InitPlayerClassData(const ITxtReader& txtReader)
+ {
+ static const ITxtReader* pCurTextReader = nullptr;
+ if (!s_CharClassInfo.empty())
+ {
+ if (pCurTextReader == &txtReader)
+ {
+ // already initialized
+ return;
+ }
+
+ s_CharClassNameMap.clear();
+ s_CharClassEnumMap.clear();
+ s_CharClassInfo.clear();
+ s_CharClassCodeMap.clear();
+ }
+
+ InitCharStatsData(txtReader);
+ pCurTextReader = &txtReader;
+ auto pDoc(txtReader.GetPlayerClassTxt());
+ auto& doc = *pDoc;
+ std::map charClassCodeMap;
+ size_t numRows = doc.GetRowCount();
+ const SSIZE_T classColumnIdx = doc.GetColumnIdx("Player Class");
+ if (classColumnIdx < 0)
+ {
+ return;
+ }
+
+ const SSIZE_T codeColumnIdx = doc.GetColumnIdx("Code");
+ if (codeColumnIdx < 0)
+ {
+ return;
+ }
+
+ std::string strValue;
+ std::string code;
+ for (size_t i = 0; i < numRows; ++i)
+ {
+ code = doc.GetCellString(codeColumnIdx, i);
+ if (code.empty())
+ {
+ // skip
+ continue;
+ }
+
+ strValue = doc.GetCellString(classColumnIdx, i);
+ if (strValue.empty())
+ {
+ // skip
+ continue;
+ }
+
+ if (strValue == "Expansion")
+ {
+ // skip
+ continue;
+ }
+
+ auto iterEnum = s_CharClassEnumNameMap.find(strValue);
+ if (iterEnum == s_CharClassEnumNameMap.end())
+ {
+ // skip
+ continue;
+ }
+
+ auto iterIdx = s_CharClassEnumMap.find(iterEnum->second);
+ if (iterIdx == s_CharClassEnumMap.end())
+ {
+ // skip
+ continue;
+ }
+
+ auto iter = s_CharClassInfo.find(iterIdx->second);
+ if (iter == s_CharClassInfo.end())
+ {
+ // skip
+ continue;
+ }
+
+ iter->second.Code = code;
+ charClassCodeMap[code] = iter->second.ClassEnum;
+ }
+
+ s_CharClassCodeMap.swap(charClassCodeMap);
+ }
+
+ const CharacterInfoType& GetCharClassInfo(std::int16_t idx)
+ {
+ auto iter = s_CharClassInfo.find(idx);
+ if (iter == s_CharClassInfo.end())
+ {
+ static CharacterInfoType badValue;
+ return badValue;
+ }
+
+ return iter->second;
+ }
- constexpr std::array SKILLS_MARKER = { 0x69, 0x66 }; // alternatively "if"
+ EnumCharClass GetEnumCharClassByIndex(std::int16_t idx)
+ {
+ auto classInfo = GetCharClassInfo(idx);
+ return classInfo.ClassEnum;
+ }
+
+ const CharacterInfoType& GetCharClassInfo(EnumCharClass charClass)
+ {
+ auto iter = s_CharClassEnumMap.find(charClass);
+ if (iter == s_CharClassEnumMap.end())
+ {
+ static CharacterInfoType badValue;
+ return badValue;
+ }
+
+ return GetCharClassInfo(iter->second);
+ }
+
+ const CharacterInfoType& GetCharClassInfo(const std::string& charCode)
+ {
+ auto iter = s_CharClassCodeMap.find(charCode);
+ if (iter == s_CharClassCodeMap.end())
+ {
+ static CharacterInfoType badValue;
+ return badValue;
+ }
+
+ return GetCharClassInfo(iter->second);
+ }
- constexpr std::uint32_t BARBARIAN_VITALITY_MIN = 25;
- constexpr std::uint32_t PALADIN_VITALITY_MIN = 25;
- constexpr std::uint32_t DRUID_VITALITY_MIN = 25;
- constexpr std::uint32_t AMAZON_VITALITY_MIN = 20;
- constexpr std::uint32_t ASSASSIN_VITALITY_MIN = 20;
- constexpr std::uint32_t NECROMANCER_VITALITY_MIN = 15;
- constexpr std::uint32_t SORCERESS_VITALITY_MIN = 10;
+ struct SkillsInfoType
+ {
+ SkillType Skill;
- constexpr std::uint32_t SORCERESS_ENERGY_MIN = 35;
- constexpr std::uint32_t NECROMANCER_ENERGY_MIN = 25;
- constexpr std::uint32_t ASSASSIN_ENERGY_MIN = 25;
- constexpr std::uint32_t DRUID_ENERGY_MIN = 20;
- constexpr std::uint32_t AMAZON_ENERGY_MIN = 15;
- constexpr std::uint32_t PALADIN_ENERGY_MIN = 15;
- constexpr std::uint32_t BARBARIAN_ENERGY_MIN = 10;
+ std::string Desc;
+ std::uint16_t Tab = 0;
+ std::uint16_t Row = 0;
+ std::uint16_t Col = 0;
+ };
- constexpr std::uint32_t AMAZON_DEXTERITY_MIN = 25;
- constexpr std::uint32_t NECROMANCER_DEXTERITY_MIN = 25;
- constexpr std::uint32_t SORCERESS_DEXTERITY_MIN = 25;
- constexpr std::uint32_t BARBARIAN_DEXTERITY_MIN = 20;
- constexpr std::uint32_t PALADIN_DEXTERITY_MIN = 20;
- constexpr std::uint32_t ASSASSIN_DEXTERITY_MIN = 20;
- constexpr std::uint32_t DRUID_DEXTERITY_MIN = 20;
+ std::map s_SkillDescMap;
+ std::map s_SkillIndexMap;
+ std::map s_SkillInfoMap;
+ void InitSkillDescData(const ITxtReader& txtReader)
+ {
+ auto pDoc(txtReader.GetSkillDescTxt());
+ auto& doc = *pDoc;
+ size_t numRows = doc.GetRowCount();
+ const SSIZE_T skilldescColumnIdx = doc.GetColumnIdx("skilldesc");
+ if (skilldescColumnIdx < 0)
+ {
+ return;
+ }
- constexpr std::uint32_t BARBARIAN_STRENGTH_MIN = 30;
- constexpr std::uint32_t AMAZON_STRENGTH_MIN = 20;
- constexpr std::uint32_t ASSASSIN_STRENGTH_MIN = 20;
- constexpr std::uint32_t PALADIN_STRENGTH_MIN = 25;
- constexpr std::uint32_t NECROMANCER_STRENGTH_MIN = 15;
- constexpr std::uint32_t DRUID_STRENGTH_MIN = 15;
- constexpr std::uint32_t SORCERESS_STRENGTH_MIN = 10;
+ const SSIZE_T skillPageColumnIdx = doc.GetColumnIdx("SkillPage");
+ if (skillPageColumnIdx < 0)
+ {
+ return;
+ }
- // 98 skill points for leveling up 1-99. Additional 4 per difficulty from quests
- constexpr std::uint32_t MAX_SKILL_CHOICES_EARNED = 110;
+ const SSIZE_T skillRowColumnIdx = doc.GetColumnIdx("SkillRow");
+ if (skillRowColumnIdx < 0)
+ {
+ return;
+ }
- // 495 stat points for leveling up 1-99. Additional 5 per difficulty from quests
- constexpr std::uint32_t MAX_STAT_POINTS = 510;
-}
-//---------------------------------------------------------------------------
+ const SSIZE_T skillColumnColumnIdx = doc.GetColumnIdx("SkillColumn");
+ if (skillColumnColumnIdx < 0)
+ {
+ return;
+ }
+
+ const SSIZE_T strnameColumnColumnIdx = doc.GetColumnIdx("str name");
+ if (strnameColumnColumnIdx < 0)
+ {
+ return;
+ }
+
+ const SSIZE_T strlongColumnColumnIdx = doc.GetColumnIdx("str long");
+ if (strlongColumnColumnIdx < 0)
+ {
+ return;
+ }
+
+ const SSIZE_T iconCelColumnIdx = doc.GetColumnIdx("IconCel");
+ if (iconCelColumnIdx < 0)
+ {
+ return;
+ }
+
+ std::string strValue;
+ auto iter = s_SkillDescMap.begin();
+ for (size_t i = 0; i < numRows; ++i)
+ {
+ strValue = doc.GetCellString(skilldescColumnIdx, i);
+ if (strValue.empty())
+ {
+ // skip
+ continue;
+ }
+
+ iter = s_SkillDescMap.find(strValue);
+ if (iter == s_SkillDescMap.end())
+ {
+ // skip
+ continue;
+ }
+
+ auto& skillInfo = s_SkillInfoMap[iter->second];
+
+ strValue = doc.GetCellString(strnameColumnColumnIdx, i);
+ if (!strValue.empty())
+ {
+ LocalizationHelpers::GetStringTxtValue(strValue, skillInfo.Skill.name, skillInfo.Skill.index.c_str());
+ }
+
+ strValue = doc.GetCellString(strlongColumnColumnIdx, i);
+ if (!strValue.empty())
+ {
+ LocalizationHelpers::GetStringTxtValue(strValue, skillInfo.Skill.longName);
+ }
+
+ if (skillInfo.Skill.classInfo.has_value())
+ {
+ strValue = doc.GetCellString(iconCelColumnIdx, i);
+ if (!strValue.empty())
+ {
+ skillInfo.Skill.classInfo.value().iconIndex = doc.GetCellUInt16(iconCelColumnIdx, i);
+ }
+
+ strValue = doc.GetCellString(skillPageColumnIdx, i);
+ if (!strValue.empty())
+ {
+ skillInfo.Tab = doc.GetCellUInt16(skillPageColumnIdx, i);
+ }
+
+ strValue = doc.GetCellString(skillRowColumnIdx, i);
+ if (!strValue.empty())
+ {
+ skillInfo.Row = doc.GetCellUInt16(skillRowColumnIdx, i);
+ }
+
+ strValue = doc.GetCellString(skillColumnColumnIdx, i);
+ if (!strValue.empty())
+ {
+ skillInfo.Col = doc.GetCellUInt16(skillColumnColumnIdx, i);
+ }
+
+ if ((skillInfo.Tab > 0) && (skillInfo.Tab <= 3) && (skillInfo.Row > 0) && (skillInfo.Col > 0))
+ {
+ auto iterIdx = s_CharClassEnumMap.find(skillInfo.Skill.classInfo.value().charClass);
+ if (iterIdx != s_CharClassEnumMap.end())
+ {
+ auto iterClass = s_CharClassInfo.find(iterIdx->second);
+ if (iterClass != s_CharClassInfo.end())
+ {
+ switch (skillInfo.Tab)
+ {
+ case 1:
+ skillInfo.Tab = 3;
+ iterClass->second.SklTreeTab3[skillInfo.Row][skillInfo.Col] = skillInfo.Skill.id;
+ break;
+
+ case 2:
+ iterClass->second.SklTreeTab2[skillInfo.Row][skillInfo.Col] = skillInfo.Skill.id;
+ break;
+
+ case 3:
+ skillInfo.Tab = 1;
+ iterClass->second.SklTreeTab1[skillInfo.Row][skillInfo.Col] = skillInfo.Skill.id;
+ break;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ void InitSkillInfoData(const ITxtReader& txtReader)
+ {
+ static const ITxtReader* pCurTextReader = nullptr;
+ if (!s_SkillInfoMap.empty())
+ {
+ if (pCurTextReader == &txtReader)
+ {
+ // already initialized
+ return;
+ }
+
+ s_SkillDescMap.clear();
+ s_SkillIndexMap.clear();
+ s_SkillInfoMap.clear();
+ }
+
+ InitPlayerClassData(txtReader);
+ pCurTextReader = &txtReader;
+ auto pDoc(txtReader.GetSkillsTxt());
+ auto& doc = *pDoc;
+ std::map