Hi Can anyone point me to which files allow to show more Skills in the Character Creation / Level Up screen? BR, A
No. You can see the table address in skill.cpp, and I guess edit it in memory instead of changing the dll.
Activating the Skills can be done with the Activator HERE. Getting them to show outside levelup screen? No idea. If you do a .dll hack for it, let me know
I have created small alteration in T+: Code: void LegacySkillSystem::LoadSkillsProps(const std::string& path) { std::string error; if (!vfs->FileExists(path)) return; json11::Json json = json.parse(vfs->ReadAsString(path), error); if (json.is_null()) { throw TempleException("Unable to parse skills_props.json from {}: {}", path, error); } if (!json.is_array()) { throw TempleException("skills_props.json must start with an array at the root"); } for (auto& item : json.array_items()) { if (!item.is_object()) { logger->warn("Skipping skill that is not an object."); continue; } auto idNode = item["id"]; if (!idNode.is_number()) { logger->warn("Skipping skill that is missing 'id' attribute."); continue; } uint32_t id = (uint32_t)item["id"].int_value(); if (id < 0 || id > SkillEnum::skill_use_rope) { logger->warn("Skipping skill that is out of bounds."); continue; } if (!item["enabled"].is_number()) { logger->warn("Skipping skill that is missing 'enabled' int attribute."); continue; } if (id) skillPropsTable[id].classFlags &= ~0x80000000; else skillPropsTable[id].classFlags |= 0x80000000; } } And the file skills_props.json: Code: [ { "code": "skill_balance", "id": 22, "enabled": 1 }, { "code": "skill_climb", "id": 23, "enabled": 1 }, { "code": "skill_escape_artist", "id": 27, "enabled": 1 }, { "code": "skill_jump", "id": 32, "enabled": 1 }, { "code": "skill_jump", "id": 32, "enabled": 1 }, { "code": "skill_knowledge_arcana", "id": 33, "enabled": 1 }, { "code": "skill_knowledge_religion", "id": 34, "enabled": 1 }, { "code": "skill_knowledge_nature", "id": 35, "enabled": 1 }, { "code": "skill_knowledge_all", "id": 36, "enabled": 1 }, { "code": "skill_use_rope", "id": 41, "enabled": 1 } ] That did displayed and allowed editing Skills in the Char Editor screen. But in Inventory screen it is still not accessible. Will investigate further.
The skills UI (char_skills_ui) is a can of worms unfortunately. Troika hardcoded it so the first 21 skills are those that are enabled, and the rest come afterwards. The skills UI is hardcoded to iterate through the first 21 skills, regardless of the skill props table, and also has a 21 sized buffer for the button widgets (LgcyButton*[21] at 0x10D1A338). You'll either have to patch these limits (and perhaps re-order the skills so only the ones active are at the beginning) or rewrite the skills UI code partially or entirely - doable, but a PITA (I guess a day or two of work, plus whatever it takes to learn). For more info see UiCharSkillWidgetsInit (0x101BE110). BTW your JSON has skill_jump twice.
BTW please replace Code: id > SkillEnum::skill_use_rope With Code: id >= SkillEnum::skill_count Another BTW: don't do that, at least for the vanilla skills, since the enums are used in many places inside the CPP code. It's better to just alphabetically sort the buttons in the UI.
Rewrote original code into new ui\ui_skills.h, ui\ui_skills.cpp: Code: #include "stdafx.h" #include "ui_skills.h" #include <util/fixes.h> #include <ui/ui.h> #include <temple/dll.h> UiSkills uiSkills; class UiSkillsHooks : public TempleFix { public: void apply() override { replaceFunction<int(__cdecl)()>(0x101BE110, []() { return uiSkills.UiCharSkillWidgetsInit(); }); } } uiSkillsHooks; int UiSkills::UiCharSkillWidgetsInit() { int uiCharSkillsWndX = temple::GetRef<int>(0x10D19EA8); int uiCharSkillsWndY = temple::GetRef<int>(0x10D19EAC); int uiCharSkillsWndW = temple::GetRef<int>(0x10D19EB0); int uiCharSkillsWndH = temple::GetRef<int>(0x10D19EB4); LgcyWindow widget; memcpy(widget.name, "char_skills_ui_main_window", 27); widget.x = uiCharSkillsWndX; widget.xrelated = uiCharSkillsWndX; widget.width = uiCharSkillsWndW; widget.y = uiCharSkillsWndY; widget.yrelated = uiCharSkillsWndY; widget.type = LgcyWidgetType::Window; widget.parentId = -1; widget.widgetId = -1; widget.flags = 1; widget.height = uiCharSkillsWndH; widget.render = temple::GetRef<void(__cdecl)(int)>(0x101BCD50); //UiCharSkillsWndRender widget.handleMessage = temple::GetRef<BOOL(__cdecl)(int, TigMsg*)>(0x101BD0C0); //UiCharSkillsWndMsg; widget.renderTooltip = 0; widget.childrenCount = 0; widget.field_28c = 0; //widget.field_288 = 0; //widget.windowId = 0; int result = uiManager->AddWindow(widget); if (result) { int uiCharSkillsWndScrollbarX = temple::GetRef<int>(0x10D19EB8); int uiCharSkillsWndScrollbarYdef = temple::GetRef<int>(0x10D19EBC); int uiCharSkillsWndScrollbarH = temple::GetRef<int>(0x10D19EC4); LgcyWindow* uiCharSkillsWnd = uiManager->GetWindow(result);; temple::GetRef<LgcyWindow*>(0x10D1A330) = uiCharSkillsWnd; LgcyScrollBar scrollbar; result = temple::GetRef<int(__cdecl)(LgcyScrollBar* widg, int x, int y, int height)>(0x101FA7B0)(&scrollbar, uiCharSkillsWndScrollbarX, uiCharSkillsWndScrollbarYdef, uiCharSkillsWndScrollbarH); //UiScrollbarInit if (!result) { scrollbar.yMax = 1; scrollbar.scrollQuantum = 1; scrollbar.field8C = 1; result = uiManager->AddScrollBar(scrollbar); if (result) { result = uiManager->AddChild(uiCharSkillsWnd->widgetId, scrollbar.widgetId); if (result) { LgcyScrollBar* uiCharSkillsScrollbar = uiManager->GetScrollBar(scrollbar.widgetId); temple::GetRef<LgcyScrollBar*>(0x10D1A334) = uiCharSkillsScrollbar; LgcyButton **uiCharSkillsBtns = temple::GetRef<LgcyButton*[]>(0x10D1A338); int dword_10D19EC8 = temple::GetRef<int>(0x10D19EC8); int dword_10D19ED4 = temple::GetRef<int>(0x10D19ED4); int dword_10D19ECC = temple::GetRef<int>(0x10D19ECC); int dword_10D19ED0 = temple::GetRef<int>(0x10D19ED0); int v2 = 0, v1; LgcyButton button; while (1) { memcpy(button.name, "char_skills_ui_skill_button", 0x1Cu); v1 = v2; button.parentId = widget.widgetId; button.x = dword_10D19EC8 + widget.x; button.xrelated = dword_10D19EC8 + widget.x; button.height = dword_10D19ED4; button.y = widget.y + dword_10D19ECC + v2 * dword_10D19ED4; button.yrelated = widget.y + dword_10D19ECC + v2 * dword_10D19ED4; button.type = LgcyWidgetType::Button; button.widgetId = -1; button.flags = 0; button.width = dword_10D19ED0; button.render = temple::GetRef<void(__cdecl)(int)>(0x101BD850); //UiCharSkillsBtnRender; button.handleMessage = temple::GetRef<BOOL(__cdecl)(int, TigMsg*)>(0x101BE050); //UiCharSkillsBtnMsg; button.renderTooltip = temple::GetRef<void(__cdecl)(int x, int y, int* widgetId)>(0x101BDFC0); //UiCharSkillsBtnTooltip;; button.buttonState = LgcyButtonState::Normal; button.flags = 0; button.field8C = -1; button.field90 = -1; button.field84 = -1; button.field88 = -1; result = uiManager->AddButton(button); if (!result) break; result = uiManager->AddChild(uiCharSkillsWnd->widgetId, button.widgetId); if (!result) break; LgcyButton* b = uiManager->GetButton(button.widgetId); uiCharSkillsBtns[v2++] = b; if (v1 + 1 >= 20) return 0; } } } } } return result; } It works correctly, and does not crush on T+ exit)) Next I will need to inject into uiCharSkillsBtns new instance of the array, which I will hold in the UiSkills class. The funny thing is, that both UiCharSkillsBtnRender and UiCharSkillsBtnTooltip identify to which skill button relates to based on such loop in Vanilla: Code: _idx = 0; while ( uiCharSkillsBtns[_idx]->widgetId != widgetId ) { if ( ++_idx >= 20 ) { idx = -1; goto LABEL_5; } } idx = _idx; Meaning that it will not see a button as a skill. So rewrite is needed as well. @Sitra Achara, @_doug_ is there a way to inject via asm here (cmp eax, 14h) a new size? Code: .text:101BD8D0 loc_101BD8D0: ; CODE XREF: UiCharSkillsBtnRender+94j .text:101BD8D0 558 8B 0C 85 38+ mov ecx, uiCharSkillsBtns[eax*4] .text:101BD8D7 558 39 59 0C cmp [ecx+0Ch], ebx .text:101BD8DA 558 0F 84 AE 02+ jz loc_101BDB8E .text:101BD8E0 558 40 inc eax .text:101BD8E1 558 83 F8 14 cmp eax, 14h .text:101BD8E4 558 7C EA jl short loc_101BD8D0 .text:101BD8E6 558 8B DF mov ebx, edi
I think you may be able to use TempleFix::writeHex() to change that 14h to whatever it should be. I'd have to look into it a bit to figure out exactly what parameters to use.
That's a little dirty hehe... remember that the decompilation sometimes produces bad flow. Like that while(1) loop, and those nested if's. (those checks aren't really necessary anyway) Also, for initing the UI widget objects, most of the time you can rely on the constructor rather than manually settings the fields. Check out ui_dialog.cpp UiDialogImpl::WidgetsInit for a more streamlined example. Lastly please rename: dword_10D19EC8 - uiCharSkillsBtnXoff dword_10D19ECC - uiCharSkillsBtnYoff dword_10D19ED0 - uiCharSkillsBtnW dword_10D19ED4 - uiCharSkillsBtnH These values are based on what's specified in data/art/interface/char_ui/char_skills_ui/7_char_skills_ui.mes.
I agree, that the code should be polished, redundancies removed, loops improved and overall code readability. But this is incremental code refactor process. At least for me. First, copy code as is, with lines be as close to decompiled as they can get. It is easier to spot errors in such way. And I'd rather have intermid commit somewhere on the line. As for the improvements you mentioned Sitra, I will surely do them)) Also, the sad truth is that UiCharSkillsBtnRender and UiCharSkillsBtnTooltip would have to be refactored as well