From 4aa0f3131132ad88f1bbd41d3d0b4bac6ce630c5 Mon Sep 17 00:00:00 2001 From: DMD Date: Fri, 19 Aug 2016 03:33:03 +0300 Subject: [PATCH] Eldritch Knight pretty much done & Levelup Feats GUI WIP 5 --- Infrastructure/include/graphics/math.h | 1 + TemplePlus/d20_class.cpp | 4 + TemplePlus/d20_class.h | 1 + .../python/python_integration_class_spec.cpp | 14 + .../python/python_integration_class_spec.h | 2 + TemplePlus/temple_enums.h | 1 + TemplePlus/tig/tig.cpp | 5 + TemplePlus/ui/ui_char_editor.cpp | 331 +++++++++++++++--- tpdata/templeplus/lib/templeplus/constants.py | 1 - tpdata/tpgamefiles.dat | Bin 140615 -> 141617 bytes 10 files changed, 302 insertions(+), 58 deletions(-) diff --git a/Infrastructure/include/graphics/math.h b/Infrastructure/include/graphics/math.h index b4a03112b..b2ad0caee 100644 --- a/Infrastructure/include/graphics/math.h +++ b/Infrastructure/include/graphics/math.h @@ -62,4 +62,5 @@ struct TigRect { void FitInto(const TigRect &boundingRect); bool Intersects(const TigRect &other); bool Intersects(const TigRect &other, TigRect &intersection); + bool ContainsPoint(int px, int py); }; diff --git a/TemplePlus/d20_class.cpp b/TemplePlus/d20_class.cpp index d3b8f15db..a580faa3a 100644 --- a/TemplePlus/d20_class.cpp +++ b/TemplePlus/d20_class.cpp @@ -453,6 +453,10 @@ bool D20ClassSystem::IsSelectingFeatsOnLevelup(objHndl handle, Stat classEnum){ return pythonClassIntegration.IsSelectingFeatsOnLevelup(handle, classEnum); } +void D20ClassSystem::LevelupGetBonusFeats(objHndl handle, Stat classEnum){ + pythonClassIntegration.LevelupGetBonusFeats(handle, classEnum); +} + bool D20ClassSystem::IsSelectingSpellsOnLevelup(objHndl handle, Stat classEnum){ return pythonClassIntegration.IsSelectingSpellsOnLevelup(handle, classEnum); diff --git a/TemplePlus/d20_class.h b/TemplePlus/d20_class.h index 8f539c39e..25d78b86c 100644 --- a/TemplePlus/d20_class.h +++ b/TemplePlus/d20_class.h @@ -150,6 +150,7 @@ struct D20ClassSystem : temple::AddressTable // Levelup bool IsSelectingFeatsOnLevelup(objHndl handle, Stat classEnum); + void LevelupGetBonusFeats( objHndl handle, Stat classEnum); bool IsSelectingSpellsOnLevelup(objHndl handle, Stat classEnum); void LevelupInitSpellSelection(objHndl handle, Stat classEnum); diff --git a/TemplePlus/python/python_integration_class_spec.cpp b/TemplePlus/python/python_integration_class_spec.cpp index a95e8f500..b15dad6b4 100644 --- a/TemplePlus/python/python_integration_class_spec.cpp +++ b/TemplePlus/python/python_integration_class_spec.cpp @@ -200,6 +200,19 @@ bool PythonClassSpecIntegration::IsSelectingFeatsOnLevelup(objHndl handle, Stat } +void PythonClassSpecIntegration::LevelupGetBonusFeats(objHndl handle, Stat classEnum){ + auto classSpecEntry = mScripts.find(classEnum); + if (classSpecEntry == mScripts.end()) + return; + + auto attachee = PyObjHndl_Create(handle); + auto args = Py_BuildValue("(O)", attachee); + Py_DECREF(attachee); + + RunScriptDefault0(classSpecEntry->second.id, (EventId)ClassSpecFunc::LevelupGetBonusFeats, args) ; + Py_DECREF(args); +} + bool PythonClassSpecIntegration::IsSelectingSpellsOnLevelup(objHndl handle, Stat classEnum){ auto classSpecEntry = mScripts.find(classEnum); if (classSpecEntry == mScripts.end()) @@ -265,6 +278,7 @@ static std::map classSpecFunctions = { { ClassSpecFunc::GetFeats,"GetClassFeats" }, { ClassSpecFunc::IsSelectingFeatsOnLevelup, "IsSelectingFeatsOnLevelup" }, + { ClassSpecFunc::LevelupGetBonusFeats, "LevelupGetBonusFeats" }, { ClassSpecFunc::LevelupCheckSpells, "LevelupCheckSpells" }, { ClassSpecFunc::IsSelectingSpellsOnLevelup, "IsSelectingSpellsOnLevelup" }, diff --git a/TemplePlus/python/python_integration_class_spec.h b/TemplePlus/python/python_integration_class_spec.h index 64ce9171c..2ca68adef 100644 --- a/TemplePlus/python/python_integration_class_spec.h +++ b/TemplePlus/python/python_integration_class_spec.h @@ -28,6 +28,7 @@ enum class ClassSpecFunc : int { // levelup callbacks IsSelectingFeatsOnLevelup, + LevelupGetBonusFeats, LevelupCheckSpells, IsSelectingSpellsOnLevelup, @@ -62,6 +63,7 @@ class PythonClassSpecIntegration : public PythonIntegration { // levelup bool IsSelectingFeatsOnLevelup(objHndl handle, Stat classEnum); + void LevelupGetBonusFeats(objHndl handle, Stat classEnum); bool IsSelectingSpellsOnLevelup(objHndl handle, Stat classEnum); void LevelupInitSpellSelection(objHndl handle, Stat classEnum); diff --git a/TemplePlus/temple_enums.h b/TemplePlus/temple_enums.h index 4f2a0af35..4087da399 100644 --- a/TemplePlus/temple_enums.h +++ b/TemplePlus/temple_enums.h @@ -681,6 +681,7 @@ enum feat_enums : int { FEAT_GREATER_RAGE = 675, FEAT_GREATER_WEAPON_SPECIALIZATION_GAUNTLET = 676, FEAT_GREATER_WEAPON_SPECIALIZATION_UNARMED_STRIKE_MEDIUM_SIZED_BEING = 677, + FEAT_GREATER_WEAPON_SPECIALIZATION_BASTARD_SWORD = 733, /// ... rest of Greater Weapon Specializations ... FEAT_GREATER_WEAPON_SPECIALIZATION_GRAPPLE = 746, FEAT_TIRELESS_RAGE = 747, diff --git a/TemplePlus/tig/tig.cpp b/TemplePlus/tig/tig.cpp index 7641103c5..c6519ef2f 100644 --- a/TemplePlus/tig/tig.cpp +++ b/TemplePlus/tig/tig.cpp @@ -1,3 +1,4 @@ +#include "..\..\Infrastructure\include\graphics\math.h" #include "stdafx.h" #include "tig.h" #include "tig_tabparser.h" @@ -273,6 +274,10 @@ bool TigRect::Intersects(const TigRect& other, TigRect& intersection) { return true; } +bool TigRect::ContainsPoint(int pX, int pY){ + return (pX >= x && pX <= x + width && pY >= y && pY <= y + height); +} + RECT TigRect::ToRect() { return{x, y, x + width, y + height}; } diff --git a/TemplePlus/ui/ui_char_editor.cpp b/TemplePlus/ui/ui_char_editor.cpp index 467a93823..8384f975c 100644 --- a/TemplePlus/ui/ui_char_editor.cpp +++ b/TemplePlus/ui/ui_char_editor.cpp @@ -125,6 +125,7 @@ class UiCharEditor { BOOL FeatsWndMsg(int widId, TigMsg* msg); void FeatsWndRender(int widId); + BOOL FeatsEntryBtnMsg(int widId, TigMsg* msg); void FeatsEntryBtnRender(int widId); BOOL FeatsExistingBtnMsg(int widId, TigMsg* msg); @@ -167,10 +168,15 @@ class UiCharEditor { bool SpellIsForbidden(int spEnum); bool SpellIsAlreadyKnown(int spEnum, int spellClass); std::string GetFeatName(feat_enums feat); // includes strings for Mutli-selection feat categories e.g. FEAT_WEAPON_FOCUS + TigTextStyle & GetFeatStyle(feat_enums feat, bool allowMultiple = true); bool FeatAlreadyPicked(feat_enums feat); bool FeatCanPick(feat_enums feat); bool IsSelectingRangerSpec(); bool IsClassBonusFeat(feat_enums feat); + void SetBonusFeats(std::vector & fti); + void FeatsSanitize(); + void FeatsMultiSelectActivate(feat_enums feat); + feat_enums FeatsMultiGetFirst(feat_enums feat); // first alphabetical // widget IDs int classWndId = 0; @@ -286,7 +292,7 @@ class UiCharEditor { std::unique_ptr mClass; std::vector mSpellInfo; std::vector mAvailableSpells; // spells available for learning - std::vector mExistingFeats, mSelectableFeats, mMultiSelectFeats, mMultiSelectMasterFeats; + std::vector mExistingFeats, mSelectableFeats, mMultiSelectFeats, mMultiSelectMasterFeats, mBonusFeats; //std::unique_ptr mStats; //std::unique_ptr mFeatures; //std::unique_ptr mSkills; @@ -392,6 +398,9 @@ PYBIND11_PLUGIN(tp_char_editor){ ; // methods mm + .def("set_bonus_feats", [](std::vector & fti){ + uiCharEditor.SetBonusFeats(fti); + }) .def("get_spell_enums", []()->std::vector& { return uiCharEditor.GetKnownSpellInfo(); }) @@ -549,13 +558,14 @@ void UiCharEditor::BtnStatesUpdate(int systemId){ ui.ButtonSetButtonState(stateBtnIds[4], UBS_NORMAL); + mIsSelectingBonusFeat = false; // feats and features if (classCode >= stat_level_barbarian){ auto classLvlNew = GetNewLvl(classCode); if (d20ClassSys.IsSelectingFeatsOnLevelup(handle, classCode) ) { - ui.ButtonSetButtonState(stateBtnIds[4], UBS_NORMAL); + ui.ButtonSetButtonState(stateBtnIds[4], UBS_NORMAL); // feats mIsSelectingBonusFeat = true; } @@ -814,11 +824,10 @@ BOOL UiCharEditor::FeatsCheckComplete(){ auto &selPkt = GetCharEditorSelPacket(); // is a 3rd level and no feat chosen - auto newLvl = objects.StatLevelGet(handle, stat_level) + 1; - if (!(newLvl % 3) && selPkt.feat0 == FEAT_NONE) + if (IsSelectingNormalFeat() && selPkt.feat0 == FEAT_NONE) return 0; - if (IsSelectingBonusFeat() && !mBonusFeatOk) // the logic will be handled in the msg callbacks & Python API now + if (IsSelectingBonusFeat() && selPkt.feat2 == FEAT_NONE) // the logic will be handled in the msg callbacks & Python API now return 0; return 1; @@ -830,6 +839,7 @@ void UiCharEditor::FeatsFinalize() void UiCharEditor::FeatsReset(CharEditorSelectionPacket & selPkt){ mFeatsActivated = false; + mIsSelectingBonusFeat = false; selPkt.feat0 = FEAT_NONE; selPkt.feat1 = FEAT_NONE; @@ -1131,6 +1141,9 @@ void UiCharEditor::FeatsActivate(){ auto &selPkt = GetCharEditorSelPacket(); mIsSelectingBonusFeat = d20ClassSys.IsSelectingFeatsOnLevelup(handle, selPkt.classCode); + mBonusFeats.clear(); + if (mIsSelectingBonusFeat) + d20ClassSys.LevelupGetBonusFeats(handle, selPkt.classCode); feat_enums existingFeats[122]; auto isRangerStyleChoosing = selPkt.classCode == stat_level_ranger && (objects.StatLevelGet(handle, stat_level_ranger) == 1); @@ -1617,34 +1630,12 @@ void UiCharEditor::FeatsWndRender(int widId){ RenderHooks::RenderRectInt(featsMainWnd.x + 3, featsMainWnd.y + 36, 185, 227, 0xFF5D5D5D); UiRenderer::DrawTextInWidget(widId, featsAvailTitleString, featsAvailTitleRect, whiteTextGenericStyle); - auto getFeatStyle = [](feat_enums feat, bool allowMultiple = true){ - auto newLvl = uiCharEditor.GetNewLvl(); - if ( (allowMultiple || !uiCharEditor.FeatAlreadyPicked(feat) ) - && uiCharEditor.FeatCanPick(feat) ) - { - if (uiCharEditor.featsMultiSelected == feat){ - return uiCharEditor.blueTextStyle; - } - if (feats.IsClassFeat(feat)){ // class Specific feat - return uiCharEditor.featsClassStyle; - } - else if (uiCharEditor.IsClassBonusFeat(feat)) // is choosing class bonus right now - { - return uiCharEditor.featsGoldenStyle; - } - else - return uiCharEditor.featsBonusTextStyle; - } - - return uiCharEditor.featsGreyedStyle; - }; - // Feat Slot if (IsSelectingNormalFeat()){ RenderHooks::RenderRectInt(featsSelectedBorderRect.x , featsSelectedBorderRect.y, featsSelectedBorderRect.width, featsSelectedBorderRect.height, 0xFFFFffff); UiRenderer::DrawTextInWidget(widId, featsTitleString, featsTitleRect, featsBonusTextStyle); if (selPkt.feat0 != FEAT_NONE){ - UiRenderer::DrawTextInWidget(widId, GetFeatName(selPkt.feat0), feat0TextRect ,getFeatStyle(selPkt.feat0)); + UiRenderer::DrawTextInWidget(widId, GetFeatName(selPkt.feat0), feat0TextRect ,GetFeatStyle(selPkt.feat0)); } } @@ -1655,7 +1646,7 @@ void UiCharEditor::FeatsWndRender(int widId){ UiRenderer::DrawTextInWidget(widId, featsClassBonusTitleString, featsClassBonusRect, featsGoldenStyle); if (selPkt.feat2 != FEAT_NONE){ - UiRenderer::DrawTextInWidget(widId, GetFeatName(selPkt.feat2), feat2TextRect, getFeatStyle(selPkt.feat2)); + UiRenderer::DrawTextInWidget(widId, GetFeatName(selPkt.feat2), feat2TextRect, GetFeatStyle(selPkt.feat2)); } } @@ -1670,48 +1661,181 @@ void UiCharEditor::FeatsWndRender(int widId){ BOOL UiCharEditor::FeatsWndMsg(int widId, TigMsg * msg) { - return 0; -} + if (msg->type == TigMsgType::WIDGET) { + auto msgW = (TigMsgWidget*)msg; + if (msgW->widgetEventType == TigMsgWidgetEvent::Scrolled) { + ui.ScrollbarGetY(featsScrollbarId, &featsScrollbarY); + ui.ScrollbarGetY(featsExistingScrollbarId, &featsExistingScrollbarY); + } + return FALSE; + } -BOOL UiCharEditor::FeatsEntryBtnMsg(int widId, TigMsg * msg) -{ - return 0; + if (msg->type != TigMsgType::MOUSE) + return FALSE; + + + auto msgM = (TigMsgMouse*)msg; + auto &selPkt = GetCharEditorSelPacket(); + + if (msgM->buttonStateFlags & MouseStateFlags::MSF_RMB_RELEASED && ui.IsWidgetHidden(featsMultiSelectWndId)) { + + auto putFeat = false; + feat_enums feat; + + // cycle thru widgets to find the one where the RMB happened + for (auto i=0; i < FEATS_AVAIL_BTN_COUNT; i++){ + if (!featsBtnRects[i].ContainsPoint(msgM->x - featsMainWnd.x, msgM->y - featsMainWnd.y)) + continue; + + auto featIdx = i + featsScrollbarY; + if (featIdx >= (int)mSelectableFeats.size()) + break; + + feat = (feat_enums)mSelectableFeats[featIdx].featEnum; + + + if (IsSelectingNormalFeat() && selPkt.feat0 == FEAT_NONE){ + selPkt.feat0 = feat; + putFeat = true; + break; + } + else if (IsSelectingBonusFeat() && selPkt.feat2 == FEAT_NONE) + { + selPkt.feat2 = feat; + putFeat = true; + break; + } + } + if (putFeat){ + + if (feats.IsFeatMultiSelectMaster(feat)){ + FeatsMultiSelectActivate(feat); + } + FeatsSanitize(); + } + + if (featsSelectedBorderRect.ContainsPoint(msgM->x, msgM->y)){ + selPkt.feat0 = FEAT_NONE; + } + else if (featsClassBonusRect.ContainsPoint(msgM->x, msgM->y) && IsSelectingBonusFeat()){ + selPkt.feat2 = FEAT_NONE; + } + + } + + if (!(msgM->buttonStateFlags & MouseStateFlags::MSF_SCROLLWHEEL_CHANGE)) + return TRUE; + + TigMsgMouse msgCopy = *msgM; + msgCopy.buttonStateFlags = MouseStateFlags::MSF_SCROLLWHEEL_CHANGE; + + if ((int)msgM->x >= featsMainWnd.x + 3 && (int)msgM->x <= featsMainWnd.x + 188 + && (int)msgM->y >= featsMainWnd.y +36 && (int)msgM->y <= featsMainWnd.y + 263) { + ui.WidgetCopy(featsScrollbarId, &featsScrollbar); + if (featsScrollbar.handleMessage) + return featsScrollbar.handleMessage(featsScrollbarId, (TigMsg*)&msgCopy); + } + + if ((int)msgM->x >= featsMainWnd.x + 207 && (int)msgM->x <= featsMainWnd.x + 392 + && (int)msgM->y >= featsMainWnd.y + 118 && (int)msgM->y <= featsMainWnd.y + 263) { + ui.WidgetCopy(featsExistingScrollbarId, &featsExistingScrollbar); + if (featsExistingScrollbar.handleMessage) + return featsExistingScrollbar.handleMessage(featsExistingScrollbarId, (TigMsg*)&msgCopy); + } + + return FALSE; } -void UiCharEditor::FeatsEntryBtnRender(int widId){ +BOOL UiCharEditor::FeatsEntryBtnMsg(int widId, TigMsg * msg){ + + if (msg->type != TigMsgType::WIDGET) + return 0; + auto msgW = (TigMsgWidget*)msg; auto widIdx = ui.WidgetlistIndexof(widId, &featsAvailBtnIds[0], FEATS_AVAIL_BTN_COUNT); auto featIdx = widIdx + featsScrollbarY; if (widIdx == -1 || featIdx >= (int)mSelectableFeats.size()) - return; + return FALSE; auto featInfo = mSelectableFeats[featIdx]; auto feat = (feat_enums)featInfo.featEnum; auto &selPkt = GetCharEditorSelPacket(); + auto btn = ui.GetButton(widId); + + switch (msgW->widgetEventType){ + case TigMsgWidgetEvent::Clicked: + if (!FeatAlreadyPicked(feat) && FeatCanPick(feat)){ + auto origX = msgW->x - btn->x, origY = msgW->y - btn->y; + auto style = uiCharEditor.GetFeatStyle(feat); + auto featCallback = [origX, origY, feat, style](int x, int y) { + std::string text(uiCharEditor.GetFeatName(feat)); + UiRenderer::PushFont(PredefinedFont::PRIORY_12); + TigRect rect(x - origX, y - origY, 180, uiCharEditor.FEATS_AVAIL_BTN_HEIGHT); + tigFont.Draw(text.c_str(), rect, style); + UiRenderer::PopFont(); + }; + mouseFuncs.SetCursorDrawCallback(featCallback, (uint32_t)&featCallback); - auto newLvl = GetNewLvl(selPkt.classCode); - auto style = featsBonusTextStyle; - if (FeatAlreadyPicked(feat) || !FeatCanPick(feat)) { - style = featsGreyedStyle; - } - else if (featsMultiSelected == feat) - { - style = blueTextStyle; - } - else - { - if (feats.IsClassFeat(feat)) - { - style = featsClassStyle; } - else if (IsClassBonusFeat(feat)) { - style = featsGoldenStyle; + return TRUE; + case TigMsgWidgetEvent::MouseReleased: + if (helpSys.IsClickForHelpActive()){ + mouseFuncs.SetCursorDrawCallback(nullptr, 0); + helpSys.PresentWikiHelp(109 + feat); + return TRUE; } - } + case TigMsgWidgetEvent::MouseReleasedAtDifferentButton: + if (FeatAlreadyPicked(feat) || !FeatCanPick(feat)) + return TRUE; + mouseFuncs.SetCursorDrawCallback(nullptr, 0); + + // check if inserted into the normal slot + if (featsSelectedBorderRect.ContainsPoint(msgW->x, msgW->y) && IsSelectingNormalFeat()){ + selPkt.feat0 = feat; + if (feats.IsFeatMultiSelectMaster(feat)) + FeatsMultiSelectActivate(feat); + } + // check if inserted into the bonus slot + else if (IsSelectingBonusFeat() + && featsClassBonusBorderRect.ContainsPoint(msgW->x, msgW->y) && IsClassBonusFeat(feat)){ + selPkt.feat2 = feat; + if (feats.IsFeatMultiSelectMaster(feat)) + FeatsMultiSelectActivate(feat); + } + FeatsSanitize(); + return TRUE; + case TigMsgWidgetEvent::Entered: + temple::GetRef(0x10162A10)(FeatsMultiGetFirst(feat), temple::GetRef(0x10C76B48), 1024u); // UiTooltipSetForFeat + temple::GetRef(0x10162C00)(temple::GetRef(0x10C76B48)); // UiCharTextboxSet + return TRUE; + case TigMsgWidgetEvent::Exited: + temple::GetRef(0x10162C00)(""); // UiCharTextboxSet + return TRUE; + default: + return FALSE; + + } + return TRUE; +} + + + + + +void UiCharEditor::FeatsEntryBtnRender(int widId){ + + auto widIdx = ui.WidgetlistIndexof(widId, &featsAvailBtnIds[0], FEATS_AVAIL_BTN_COUNT); + auto featIdx = widIdx + featsScrollbarY; + if (widIdx == -1 || featIdx >= (int)mSelectableFeats.size()) + return; + + auto featInfo = mSelectableFeats[featIdx]; + auto feat = (feat_enums)featInfo.featEnum; + UiRenderer::PushFont(PredefinedFont::PRIORY_12); - UiRenderer::DrawTextInWidget(featsMainWndId, GetFeatName(feat), featsBtnRects[widIdx], style); + UiRenderer::DrawTextInWidget(featsMainWndId, GetFeatName(feat), featsBtnRects[widIdx], GetFeatStyle(feat, false)); UiRenderer::PopFont(); } @@ -2205,7 +2329,7 @@ void UiCharEditor::ClassSetPermissibles(){ bool UiCharEditor::IsSelectingNormalFeat(){ auto handle = GetEditedChar(); - auto newLvl = objects.StatLevelGet(handle, stat_level)+1; + auto newLvl = GetNewLvl(); return (newLvl % 3) == 0; } @@ -2340,6 +2464,31 @@ std::string UiCharEditor::GetFeatName(feat_enums feat){ } +TigTextStyle & UiCharEditor::GetFeatStyle(feat_enums feat, bool allowMultiple){ + auto &selPkt = GetCharEditorSelPacket(); + auto newLvl = uiCharEditor.GetNewLvl(selPkt.classCode); + + if ( (allowMultiple || !uiCharEditor.FeatAlreadyPicked(feat)) + && uiCharEditor.FeatCanPick(feat)) + { + if (uiCharEditor.featsMultiSelected == feat) { + return uiCharEditor.blueTextStyle; + } + if (feats.IsClassFeat(feat)) { // class Specific feat + return uiCharEditor.featsClassStyle; + } + else if (uiCharEditor.IsClassBonusFeat(feat)) // is choosing class bonus right now + { + return uiCharEditor.featsGoldenStyle; + } + else + return uiCharEditor.featsBonusTextStyle; + } + + return uiCharEditor.featsGreyedStyle; + +} + bool UiCharEditor::FeatAlreadyPicked(feat_enums feat){ if (feats.IsFeatPropertySet(feat, 0x1) // can be gained multiple times || feats.IsFeatMultiSelectMaster(feat)) @@ -2371,7 +2520,7 @@ bool UiCharEditor::FeatCanPick(feat_enums feat){ // TODO extend the specials if (feat == FEAT_IMPROVED_TRIP || feat == FEAT_IMPROVED_DISARM){ - if (selPkt.classCode == stat_level_monk && objects.StatLevelGet(handle, stat_level_monk) == 6) + if (selPkt.classCode == stat_level_monk && GetNewLvl(stat_level_monk) == 6) return true; } if (!feats.IsFeatPartOfMultiselect(feat)){ @@ -2445,10 +2594,17 @@ bool UiCharEditor::IsSelectingRangerSpec() } bool UiCharEditor::IsClassBonusFeat(feat_enums feat){ - // TODO generalize + // mBonusFeats is delivered via the python class API + for (auto it : mBonusFeats) { + if (it.featEnum == feat) + return true; + } + + // the old stuff auto &selPkt = GetCharEditorSelPacket(); auto newLvl = GetNewLvl(selPkt.classCode); + switch(selPkt.classCode){ case stat_level_fighter: return feats.IsFighterFeat(feat); @@ -2471,6 +2627,67 @@ bool UiCharEditor::IsClassBonusFeat(feat_enums feat){ } } +void UiCharEditor::SetBonusFeats(std::vector& fti){ + mBonusFeats.clear(); + for (auto it : fti) { + uiCharEditor.mBonusFeats.push_back(it); + } +} + +void UiCharEditor::FeatsSanitize(){ + auto &selPkt = GetCharEditorSelPacket(); + + for (auto i=0; i < 3; i++){ // check if any of the feat now lack the prereq (due to user removal). loop three times to ensure up-to-date state. + if (selPkt.feat0 != FEAT_NONE && !FeatCanPick(selPkt.feat0)) + selPkt.feat0 = FEAT_NONE; + if (selPkt.feat1 != FEAT_NONE && !FeatCanPick(selPkt.feat1)) { + selPkt.feat1 = FEAT_NONE; + } + if (selPkt.feat2 != FEAT_NONE && !FeatCanPick(selPkt.feat2) && !IsSelectingRangerSpec()) + selPkt.feat2 = FEAT_NONE; + } + + +} + +void UiCharEditor::FeatsMultiSelectActivate(feat_enums feat){ + + auto &selPkt = GetCharEditorSelPacket(); + if (feat == FEAT_WEAPON_FINESSE){ + if (selPkt.feat0 == FEAT_WEAPON_FINESSE) + selPkt.feat0 = FEAT_WEAPON_FINESSE_DAGGER; + if (selPkt.feat1 == FEAT_WEAPON_FINESSE) + selPkt.feat1 = FEAT_WEAPON_FINESSE_DAGGER; + if (selPkt.feat2 == FEAT_WEAPON_FINESSE) + selPkt.feat2 = FEAT_WEAPON_FINESSE_DAGGER; + return; + } + + ui.WidgetBringToFront(featsMultiSelectWndId); +} + +feat_enums UiCharEditor::FeatsMultiGetFirst(feat_enums feat){ + switch(feat) + { + case FEAT_EXOTIC_WEAPON_PROFICIENCY: + return FEAT_EXOTIC_WEAPON_PROFICIENCY_BASTARD_SWORD; + case FEAT_IMPROVED_CRITICAL: + return FEAT_IMPROVED_CRITICAL_BASTARD_SWORD; + case FEAT_MARTIAL_WEAPON_PROFICIENCY: + return FEAT_MARTIAL_WEAPON_PROFICIENCY_BATTLEAXE; + case FEAT_SKILL_FOCUS: + return FEAT_SKILL_FOCUS_APPRAISE; + case FEAT_GREATER_WEAPON_FOCUS: + return FEAT_GREATER_WEAPON_FOCUS_BASTARD_SWORD; + case FEAT_WEAPON_SPECIALIZATION: + return FEAT_WEAPON_SPECIALIZATION_BASTARD_SWORD; + case FEAT_GREATER_WEAPON_SPECIALIZATION: + return FEAT_GREATER_WEAPON_SPECIALIZATION_BASTARD_SWORD; + default: + return feat; + } +} + class UiCharEditorHooks : public TempleFix { diff --git a/tpdata/templeplus/lib/templeplus/constants.py b/tpdata/templeplus/lib/templeplus/constants.py index f2c70845f..75f475414 100644 --- a/tpdata/templeplus/lib/templeplus/constants.py +++ b/tpdata/templeplus/lib/templeplus/constants.py @@ -1497,7 +1497,6 @@ feat_none = 649 feat_exotic_weapon_proficiency_head = 650 feat_improved_critical_head = 651 -feat_exotic_weapon_proficiency_head = 652 feat_martial_weapon_proficiency_head = 652 feat_skill_focus_head = 653 feat_weapon_finesse_head = 654 diff --git a/tpdata/tpgamefiles.dat b/tpdata/tpgamefiles.dat index 6d65c3e668e42bbfe8246d70431c6778de8bf028..8e922073af3599b031d12c96a1542486c50a8692 100644 GIT binary patch delta 5305 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 Uwc6~uC2ygrBL_;%2VM>T2fr96djJ3c delta 4223 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