From e592b7d728c788ec709cece1f7afd066f26827d9 Mon Sep 17 00:00:00 2001 From: mikx Date: Sat, 30 May 2026 03:03:37 -0400 Subject: [PATCH] Materials Display In-Town & In a Mission --- README.md | 14 + gui/hud_components/player_info.gui | 162 +++++++++ info.xml | 5 + scripts/GUI/HUD/Components/HUDPlayer.as | 434 ++++++++++++++++++++++++ 4 files changed, 615 insertions(+) create mode 100644 README.md create mode 100644 gui/hud_components/player_info.gui create mode 100644 info.xml create mode 100644 scripts/GUI/HUD/Components/HUDPlayer.as diff --git a/README.md b/README.md new file mode 100644 index 0000000..979af85 --- /dev/null +++ b/README.md @@ -0,0 +1,14 @@ +![logo](https://mxio.ovh/mxdev.png) + +## MxHoH2 +Official **Heroes of Hammerwatch II** Mod. + +## Features +- Show current materials on the Main HUD in-town and in a mission. + +## How-To Install +1. Download the latest zip. +2. Copy the "MxHoH2" folder to your *game root dir*\mods directory. +3. If the "mods" directory does not exists, create it. +4. Start the game, enable mods in profil and enable "MxHoH2". +5. Enjoy! \ No newline at end of file diff --git a/gui/hud_components/player_info.gui b/gui/hud_components/player_info.gui new file mode 100644 index 0000000..0df863c --- /dev/null +++ b/gui/hud_components/player_info.gui @@ -0,0 +1,162 @@ + + + 0 64 186 33 + 321 390 32 32 + + 186 64 32 33 + 218 64 31 33 + 249 64 32 33 + + 316 32 10 14 + 316 46 10 14 + 448 0 32 32 + + 326 32 81 5 + + 326 37 9 11 + 335 37 9 11 + + 0 0 31 27 + 482 18 28 28 + 482 18 28 28 + 448 32 28 28 + + 284 219 28 28 + 292 247 24 24 + 316 247 24 24 + 256 219 28 28 + + 418 34 28 28 + 76 210 28 28 + + 206 97 15 6 + 221 97 10 6 + 232 97 10 6 + 243 97 13 6 + 257 97 11 6 + + %include "gui/icons.inc" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/info.xml b/info.xml new file mode 100644 index 0000000..9301656 --- /dev/null +++ b/info.xml @@ -0,0 +1,5 @@ + + MxHoH2 + mikx + HoH2 mikx's Tweaks Collection. + diff --git a/scripts/GUI/HUD/Components/HUDPlayer.as b/scripts/GUI/HUD/Components/HUDPlayer.as new file mode 100644 index 0000000..fc11a41 --- /dev/null +++ b/scripts/GUI/HUD/Components/HUDPlayer.as @@ -0,0 +1,434 @@ +float GetExperienceProgressToLevel(PlayerRecord@ record) +{ + int64 xpStart = record.LevelExperience(record.level - 1); + int64 xpEnd = record.LevelExperience(record.level) - xpStart; + int64 xpNow = record.experience - xpStart; + + if (record.local && g_myTownRecord !is null && g_myTownRecord.m_accomplishmentRewards !is null) + xpNow += g_myTownRecord.m_accomplishmentRewards.m_rExp; + + if (double(xpEnd) == 0) + return 0.0f; + + return xpNow / double(xpEnd); +} + +float GetExperience(PlayerRecord@ record) +{ + int64 xpStart = record.LevelExperience(record.level - 1); + int64 xpEnd = record.LevelExperience(record.level) - xpStart; + int64 xpNow = record.experience - xpStart; + + if (record.local && g_myTownRecord !is null && g_myTownRecord.m_accomplishmentRewards !is null) + xpNow += g_myTownRecord.m_accomplishmentRewards.m_rExp; + + return xpNow; +} + +class HUDPlayer : HUDComponent +{ + PortraitWidget@ m_wPortrait; + + RectWidget@ m_wBackgroundRect; + Widget@ m_wBackground; + Widget@ m_wLeftBackground2; + Widget@ m_wLeftBackground3; + Widget@ m_wLeftBackground4; + + TextWidget@ m_wMissionGold; + TextWidget@ m_wMissionWood; + TextWidget@ m_wMissionStone; + TextWidget@ m_wMissionIron; + TextWidget@ m_wMissionCrystal; + TextWidget@ m_wMissionDust; + TextWidget@ m_wMissionOre; + + TextWidget@ m_wPotCharges; + SpriteWidget@ m_wPotIcon; + SpriteWidget@ m_wPotEmptyIcon; + vec4 m_potTextColFull; + vec4 m_potTextColEmpty; + vec4 m_potTextColDrinkable; + + PlayerHealthBarWidget@ m_wBarHealth; + TextWidget@ m_wHealth; + + ScalingBarWidget@ m_wBarMana; + TextWidget@ m_wMana; + + ScalingBarWidget@ m_wBarDash; + Widget@ m_wBarDashBg; + Widget@ m_wDashSegOne; + Widget@ m_wDashSegFirst; + Widget@ m_wDashSegMid; + Widget@ m_wDashSegLast; + Widget@ m_wDashSegMax; + Widget@ m_wDashes; + + Widget@ m_wHandIcons; + + ScalingBarWidget@ m_wBarCombo; + ScalingBarWidget@ m_wBarComboH; + RadialSpriteWidget@ m_wRadialExp; + + SkillWidget@ m_wSkillTemplate; + SkillWidget@ m_wSkillWeaponTemplate; + array m_skillSlots; + Widget@ m_wMainHandSkillSlot; + Widget@ m_wOffHandSkillSlot; + + int m_maxDashCharges; + + HUDPlayer(GUIBuilder@ b) + { + super(b, "gui/hud_components/player_info.gui"); + + @m_wBackgroundRect = cast(m_widget.GetWidgetById("background-rect")); + @m_wBackground = m_widget.GetWidgetById("background"); + @m_wLeftBackground2 = m_widget.GetWidgetById("skill2"); + @m_wLeftBackground3 = m_widget.GetWidgetById("skill3"); + @m_wLeftBackground4 = m_widget.GetWidgetById("skill4"); + + @m_wPortrait = cast(m_widget.GetWidgetById("portrait")); + + @m_wMissionGold = cast(m_widget.GetWidgetById("mission-gold")); + @m_wMissionWood = cast(m_widget.GetWidgetById("mission-wood")); + @m_wMissionStone = cast(m_widget.GetWidgetById("mission-stone")); + @m_wMissionIron = cast(m_widget.GetWidgetById("mission-iron")); + @m_wMissionCrystal = cast(m_widget.GetWidgetById("mission-crys")); + @m_wMissionDust = cast(m_widget.GetWidgetById("mission-dust")); + @m_wMissionOre = cast(m_widget.GetWidgetById("mission-ore")); + + @m_wPotCharges = cast(m_widget.GetWidgetById("potion-charges")); + @m_wPotIcon = cast(m_widget.GetWidgetById("potion-icon")); + @m_wPotEmptyIcon = cast(m_widget.GetWidgetById("potion-empty-icon")); + + @m_wBarHealth = cast(m_widget.GetWidgetById("health-bar")); + @m_wHealth = cast(m_widget.GetWidgetById("health")); + + @m_wBarMana = cast(m_widget.GetWidgetById("mana-bar")); + @m_wMana = cast(m_widget.GetWidgetById("mana")); + + @m_wBarCombo = cast(m_widget.GetWidgetById("combo-bar")); + @m_wBarComboH = cast(m_widget.GetWidgetById("combo-bar-highlight")); + + @m_wBarDash = cast(m_widget.GetWidgetById("dash-bar")); + @m_wRadialExp = cast(m_widget.GetWidgetById("exp")); + + @m_wSkillTemplate = cast(m_widget.GetWidgetById("skill-template")); + @m_wSkillWeaponTemplate = cast(m_widget.GetWidgetById("skill-weapon-template")); + @m_wMainHandSkillSlot = m_widget.GetWidgetById("main-hand-slot"); + @m_wOffHandSkillSlot = m_widget.GetWidgetById("off-hand-slot"); + + @m_wDashSegOne = m_widget.GetWidgetById("dash-one"); + @m_wDashSegFirst = m_widget.GetWidgetById("dash-first"); + @m_wDashSegMid = m_widget.GetWidgetById("dash-mid"); + @m_wDashSegLast = m_widget.GetWidgetById("dash-last"); + @m_wDashSegMax = m_widget.GetWidgetById("dash-max"); + @m_wDashes = m_widget.GetWidgetById("dashes"); + @m_wBarDashBg = m_widget.GetWidgetById("dash-bar-bg"); + + @m_wHandIcons = m_widget.GetWidgetById("hands"); + + m_potTextColFull = ParseColorRGBA("#00ff00ff"); + m_potTextColEmpty = vec4(0.1f, 0.1f, 0.1f, 1.0f); + m_potTextColDrinkable = ParseColorRGBA("#ffd700ff"); + + m_wBarCombo.m_scale = 0.0f; + m_wBarComboH.m_scale = 0.0f; + m_wBarDash.SetScale(0.0f); + } + + void GetSkillSlots(Widget@ w) + { + m_skillSlots.resize(0); + GetSkillSlots(w, HashString("skill-slot")); + } + + void GetSkillSlots(Widget@ w, uint id) + { + for (uint i = 0; i < w.m_children.length(); i++) + { + if (w.m_children[i].m_idHash == id) + m_skillSlots.insertLast(w.m_children[i]); + + GetSkillSlots(w.m_children[i], id); + } + } + + void Update(int dt, PlayerRecord@ record) override + { + HUDComponent::Update(dt, record); + + if (!m_visible || record is null) + return; + + // Materials Tracking + int goldCount = record.GetMaterial(MaterialType::Gold); + int woodCount = record.GetMaterial(MaterialType::Wood); + int stoneCount = record.GetMaterial(MaterialType::Wood); + int ironCount = record.GetMaterial(MaterialType::Iron); + int crysCount = record.GetMaterial(MaterialType::Crystals); + int dustCount = record.GetMaterial(MaterialType::Dust); + int oreCount = record.GetMaterial(MaterialType::Ore); + m_wMissionGold.SetText(goldCount); + m_wMissionWood.SetText(woodCount); + m_wMissionStone.SetText(stoneCount); + m_wMissionIron.SetText(ironCount); + m_wMissionCrystal.SetText(crysCount); + m_wMissionDust.SetText(dustCount); + m_wMissionOre.SetText(oreCount); + + // Potion + int charges = record.currStats.PotionCharges - record.potionChargesUsed; + if (charges == 0) + m_wPotCharges.SetColor(m_potTextColEmpty); + else if (record.potionChargesUsed == 0) + m_wPotCharges.SetColor(m_potTextColFull); + else + m_wPotCharges.SetColor(m_potTextColDrinkable); + + m_wPotCharges.SetText("" + charges); + m_wPotIcon.m_visible = (charges > 0); + m_wPotEmptyIcon.m_visible = (charges == 0 && record.currStats.PotionCharges > 0); + + // Health + m_wBarHealth.CalculateScales(record); + if (record.IsDead()) + m_wHealth.SetText("" + 0 + "/" + record.MaxHealth()); + else + m_wHealth.SetText("" + int(ceil(record.hp * record.MaxHealth())) + "/" + record.MaxHealth()); + + // Mana + m_wBarMana.SetScale(record.mana); + m_wMana.SetText("" + int(floor(record.mana * record.MaxMana())) + "/" + record.MaxMana()); + + // Experience + m_wRadialExp.m_scale = GetExperienceProgressToLevel(record); + + // Dash charges + if (record.currStats.MaxDashes == 0 || record.dashCharges == 0) + m_wBarDash.SetScale(0.0f); + else if (record.dashCharges == record.currStats.MaxDashes) + m_wBarDash.SetScale(1.0f); + else + { + if (record.currStats.MaxDashes > 2) + { + float mid = float((record.dashCharges - 1) * m_wDashSegMid.m_width) / float(m_wBarDash.m_width); + float first = float(m_wDashSegFirst.m_width) / float(m_wBarDash.m_width); + + m_wBarDash.SetScale(first + mid); + } + else + m_wBarDash.SetScale(float(record.dashCharges) / float(record.currStats.MaxDashes)); + } + + if (record.currStats.MaxDashes != m_maxDashCharges) + UpdateDashMaxCharges(record); + + auto plr = cast(record.actor); + if (record.currStats.ComboCountMax > 0 && plr !is null) + { + m_wBarCombo.m_scale = min(1.0f, float(plr.m_comboCount) / float(record.currStats.ComboCountMax)); + int resetTime = plr.m_comboActive ? record.currStats.ComboSustainResetTime : record.currStats.ComboObtainResetTime; + m_wBarComboH.m_scale = m_wBarCombo.m_scale * (1.0f - float(plr.m_comboC) / float(resetTime)); + } + else + { + m_wBarCombo.m_scale = 0.0f; + m_wBarComboH.m_scale = 0.0f; + } + } + + void UpdateDashMaxCharges(PlayerRecord@ record) + { + if (record is null) + return; + + m_wDashes.ClearChildren(); + int width = -3; + + if (record.currStats.MaxDashes == 1) + { + auto seg = m_wDashSegOne.Clone(); + seg.m_visible = true; + width += seg.m_width; + m_wDashes.AddChild(seg); + } + else if (record.currStats.MaxDashes == 2) + { + auto seg1 = m_wDashSegFirst.Clone(); + auto seg2 = m_wDashSegLast.Clone(); + seg1.m_visible = true; + seg2.m_visible = true; + width += seg1.m_width + seg2.m_width; + + m_wDashes.AddChild(seg1); + m_wDashes.AddChild(seg2); + } + else + { + for (uint i = 0; i < record.currStats.MaxDashes; i++) + { + Widget@ seg; + if (i == 0) + @seg = m_wDashSegFirst.Clone(); + else if (i == record.currStats.MaxDashes - 1) + { + if (i >= 8) + @seg = m_wDashSegMax.Clone(); + else + { + @seg = m_wDashSegLast.Clone(); + } + } + else + @seg = m_wDashSegMid.Clone(); + + seg.m_visible = true; + width += seg.m_width; + m_wDashes.AddChild(seg); + } + } + + m_wBarDash.m_width = width; + m_wBarDashBg.m_width = width; + m_maxDashCharges = record.currStats.MaxDashes; + } + + void Refresh(PlayerRecord@ record) override + { + RefreshPotions(record); + RefreshSkills(record); + RefreshPortrait(record); + } + + void RefreshPotions(PlayerRecord@ record) + { + if (record is null) + return; + + Invalidate(); + } + + void RefreshPortrait(PlayerRecord@ record) + { + m_wPortrait.SetPortrait(record.portrait); + + m_wPortrait.m_visible = record.level > 0; + if (record.skinColor is null) + return; + + array col; + PlayerColors::GetPlayerColors(col, record); + m_wPortrait.SetColors(col); + } + + void RefreshSkills(PlayerRecord@ record) + { + array@ playerSkills; + + auto player = cast(record.actor); + if (player !is null) + @playerSkills = player.m_skills; + else + @playerSkills = PlayerHelper::GetDeadSkillList(record); + + auto mainHandWeapon = record.equipped.m_items[EquippedItemsSlots::MainHand]; + auto offHandWeapon = record.equipped.m_items[EquippedItemsSlots::OffHand]; + m_wHandIcons.m_visible = mainHandWeapon !is null && mainHandWeapon.GetSlot() == Equipment::Slot::TwoHanded; + + for (uint i = 0; i < m_skillSlots.length(); i++) + m_skillSlots[i].ClearChildren(); + + m_wMainHandSkillSlot.ClearChildren(); + m_wOffHandSkillSlot.ClearChildren(); + + Skills::Skill@ mainHandSkill; + Skills::Skill@ offHandSkill; + array skills = array(); + + for (uint i = 0; i < playerSkills.length(); i++) + { + if (playerSkills[i].GetTargetingMode() == Skills::TargetingMode::Passive) + continue; + + if (playerSkills[i].m_type == PlayerSkillType::MainHand) + @mainHandSkill = playerSkills[i]; + else if (playerSkills[i].m_type == PlayerSkillType::OffHand) + @offHandSkill = playerSkills[i]; + else if (playerSkills[i].m_type != PlayerSkillType::Dash) + skills.insertLast(playerSkills[i]); + } + + if (skills.length() <= 2) + { + m_wLeftBackground2.m_visible = true; + m_wLeftBackground3.m_visible = false; + m_wLeftBackground4.m_visible = false; + + m_wBackgroundRect.m_width = m_wBackground.m_width + m_wLeftBackground2.m_width; + GetSkillSlots(m_wLeftBackground2); + } + else if (skills.length() == 3) + { + m_wLeftBackground2.m_visible = false; + m_wLeftBackground3.m_visible = true; + m_wLeftBackground4.m_visible = false; + + m_wBackgroundRect.m_width = m_wBackground.m_width + m_wLeftBackground3.m_width; + GetSkillSlots(m_wLeftBackground3); + } + else + { + m_wLeftBackground2.m_visible = false; + m_wLeftBackground3.m_visible = false; + m_wLeftBackground4.m_visible = true; + + m_wBackgroundRect.m_width = m_wBackground.m_width + m_wLeftBackground4.m_width; + GetSkillSlots(m_wLeftBackground4); + } + + if (m_wMainHandSkillSlot !is null && mainHandSkill !is null) + { + auto newSkill = cast(m_wSkillWeaponTemplate.Clone()); + newSkill.SetID(""); + newSkill.m_visible = true; + newSkill.SetSkill(mainHandSkill, mainHandWeapon); + + auto bindingW = cast(newSkill.GetWidgetById("binding")); + bindingW.SetText(""); + + m_wMainHandSkillSlot.AddChild(newSkill); + } + + if (m_wOffHandSkillSlot !is null && offHandSkill !is null) + { + auto newSkill = cast(m_wSkillWeaponTemplate.Clone()); + newSkill.SetID(""); + newSkill.m_visible = true; + newSkill.SetSkill(offHandSkill, m_wHandIcons.m_visible ? mainHandWeapon : offHandWeapon, m_wHandIcons.m_visible); + + auto bindingW = cast(newSkill.GetWidgetById("binding")); + bindingW.SetText(""); + + m_wOffHandSkillSlot.AddChild(newSkill); + } + + for (uint i = 0; i < m_skillSlots.length() && i < skills.length(); i++) + { + auto newSkill = cast(m_wSkillTemplate.Clone()); + newSkill.SetID(""); + newSkill.m_visible = true; + newSkill.SetSkill(skills[i]); + + auto bindingW = cast(newSkill.GetWidgetById("binding")); + bindingW.SetText(""); + + m_skillSlots[i].AddChild(newSkill); + } + + Invalidate(); + } +} \ No newline at end of file