Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
bd883fc
Split `ShowTargetProtection` into two separate settings`ShowTargetPro…
Myszax Dec 8, 2025
bea5ae7
Changed icon protecions from shields to damage popup icons
Myszax Dec 10, 2025
9c7c9bf
Note about immunities added to README.md
Myszax Dec 10, 2025
9ada44d
Added `TargetProtectionIconStyle` option to choose between old and ne…
Myszax Dec 11, 2025
25ea1f9
Changed italic style for protection icons decription
Myszax Dec 11, 2025
a51be9b
Gray color for protection icon is reserved only for `NPC_FLAG_IMMORTA…
Myszax Dec 11, 2025
ed7a81f
Now it's checking all damage types instead of `GetTopDmgIndex`
Myszax Dec 15, 2025
fb907a2
Changed immune text value
Myszax Dec 21, 2025
328984d
Refactored `GetDamageIndexes`
Myszax Dec 22, 2025
b0a6c26
Added null check for active spell in `BuildFightModeDamage` to avoid …
Myszax Dec 22, 2025
3d4a97f
Mark munition damage type instead of distance weapon if possible
Myszax Dec 22, 2025
98596b1
Distance weapon damage type marking depends on `OnDamage_Hit` hook
Myszax Dec 22, 2025
d80da36
Added support for saving distance damage type to ini
Myszax Dec 23, 2025
8d14f4c
Changed `MarkSpellDamage` to `MarkIntDamageType` and parameter
Myszax Dec 23, 2025
80dd58a
Added little spacing between protection icons
Myszax Feb 3, 2026
b5794f1
Removed `ZUTILITIES_TEMP` section
Myszax Feb 4, 2026
f42c11e
Fixed writing `DistanceWeaponDamageType` value to ini
Myszax Feb 4, 2026
5066c60
Added missing `DistanceWeaponDamageType` to auto generated ini
Myszax Feb 5, 2026
7d06bf4
Added new option `TargetProtectionIconPosition`
Myszax Feb 5, 2026
9f075fd
Refactor for rendering methods
Myszax Feb 5, 2026
52142d2
Added option to hide some protection icons
Myszax Feb 15, 2026
b4f1ad6
Removed `AllButZeros` from `TargetProtectionMode`
Myszax Feb 15, 2026
98a4409
merge
Myszax Feb 19, 2026
07c8aac
Added `BuildRenderData`
Myszax Feb 19, 2026
89aa2ad
Added `DamageMaskHelper`
Myszax Feb 21, 2026
98fbcbd
Removed `GetProtectionStatusesVisibleCount`
Myszax Feb 21, 2026
096a9f8
Added `ProtectionRenderMode` and `ProtectionContext`
Myszax Feb 21, 2026
1a68185
Cleanup dummy arguments from StatusBar
Myszax Feb 21, 2026
85d3ce4
Removed `const_cast` from `RenderProtection`. It uses reference to ve…
Myszax Feb 23, 2026
916b763
Refactor + added new margin method
Myszax Feb 23, 2026
c690bc6
Fixed `RenderProtectionIconsRight` spacing between icons
Myszax Feb 24, 2026
456092c
Divide into model+layout+render
Myszax Feb 24, 2026
616e24c
Removed per-frame vector allocations
Myszax Feb 24, 2026
e0daffc
Render methods are using layout as parameter from now on
Myszax Feb 25, 2026
86e22d5
`protectionModel` is now cached
Myszax Feb 25, 2026
fa1b7f0
rename fields in `ProtectionModel`
Myszax Feb 25, 2026
aebba45
Added cache for icons (partly)
Myszax Feb 26, 2026
60877e7
Refactored `BuildProtectionLayout`
Myszax Feb 26, 2026
96476c4
`ProtectionLayout` changed `size` to `iconSize
Myszax Feb 26, 2026
9a5e7f7
Switched from vector to normal array to squeeze more optimization
Myszax Feb 26, 2026
0dad37b
Reorder variables and methods in files
Myszax Feb 28, 2026
85d6db0
Revert .sln and .gitignore changes from 98a4409
Myszax Feb 28, 2026
f5f3d87
Added filter for `FocusStatusBar`
Myszax Feb 28, 2026
6de53d0
Moved `FocusStatusBar` filter into `PlayerStatus`
Myszax Feb 28, 2026
97e0abf
Merge branch 'master' into dev
Myszax Feb 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 48 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,26 @@ This is a plugin with a set of many quality-of-life and utility features made fo
- World speed will cycle in the order of set multipliers after pressing `Z`. Shortcut can also be changed with `KeyTimeMultiplier` option.
- Currently used multiplier will be shown next to the time icon.

- Displays protection icon and value next to the focused npc hp bar.

- Setting `ShowTargetProtection` option to `0` disables this feature, `1` displays the protection that matches currently used weapon and `2` shows all protection stats.
- `ShowProtOnlyInFight` and `ShowProtAllDamageTypes` are extra options that can be used, to alter what is displayed.
- Displays protection icons and values next to the focused npc hp bar.

- Setting are split in two modes and works separately, fight and no fight. Fight means when melee/distance weapon or spell is drawn, No fight means otherwise.
- `ShowTargetProtectionNoFight` option for no fight mode.
- `ShowTargetProtectionInFight` option for fight mode.
- There are 3 options for above settings:
- `0` - _Disabled_ No icons and values will be shown.
- `1` - _CurrentWeapon_ Only icons and values for current drawn or equipped weapon will be shown. In no fight mode, there is the possibility of more than one icon and value being shown, depending on the equipped distance/melee weapon and/or spell.
- `2` - _All_ Displays all icons and values for all protections. This option could be filtered with some of the options described in section below.
- Note that:
- Transformation into a monster is treated as fight mode.
- When option is set higher than `0` and target has flag `NPC_FLAG_IMMORTAL` then only one icon (cracked shield) will be shown. There is no point in showing all the icons if target is immortal.
- Immunity to a specific protection type is treated as non-zero value and is utilized in the hide options described in the section below.
- Icon style is determined by `TargetProtectionIconStyle` option. Set it to `0` - _DamagePopup_ then corresponding protection icons and colors will be same as damage popup. Set it to `1` - _Shields_ for simple shield icons distinguished only by colors.
- Icon position is defined by `TargetProtectionIconPosition` option. Set it to `0` - _Top_ then protection icons will be positioned above focused health bar in row. Set it to `1` - _Right_ then icons will be positioned in column to the right of focused health bar. Note that if there will be only one icon to show then this setting will be omitted temporary and icon will be shown as close as possible to the right of the focused health bar.
- Some protection icons can be hidden but only when `ShowTargetProtectionInFight` or `ShowTargetProtectionNoFight` option is set to `2` - _All_. Conditions are checked from top to bottom, so for example if `HideTargetProtectionZeroValues` is enabled and `HideTargetProtectionFallDamage` is disabled and target has fall damage protection equal to zero then fall damage icon will not be shown.
- `HideTargetProtectionZeroValues` option hides protection icons with zero value, but they will still be shown if target is immune.
- `HideTargetProtectionFallDamage` option hides fall damage protection icon even if target is immune.
- `HideTargetProtectionFlyDamage` option hides fly damage protection icon even if target is immune.
- `HideTargetProtectionFireDamage` option hides fire damage protection icon even if target is immune.

- Displays coin icon next to the focused npc name if player can pickpocket him.

Expand Down Expand Up @@ -199,14 +215,33 @@ SaveReminder=5
; ... Time in minutes after which the reminder to save the game appears on the screen
; ... set to -1 to disable

ShowTargetProtection=1
; ... enables for currently equipped weapon (1) or shows all protection stats (2) or disables (0) protection icon and value next to the focused npc hp bar
ShowTargetProtectionNoFight=0
;... specifies mode for showing target protection in no fight mode by
;... (0) - 'Disabled', (1) - 'CurrentWeapon', (2) - 'All'

ShowTargetProtectionInFight=1
;... specifies mode for showing target protection in fight mode by
;... (0) - 'Disabled', (1) - 'CurrentWeapon', (2) - 'All'

TargetProtectionIconStyle=0
;... specifies style for protection icons
;... (0) - 'DamagePopup', (1) - 'Shields'

TargetProtectionIconPosition=0
;... specifies position for protection icons
;... (0) - 'Top', (1) - 'Right'

ShowProtOnlyInFight=1
; ... enables (1) or disables (0) showing protection stats only during combat
HideTargetProtectionZeroValues=0
... hides protection icons with zero value; (0) - 'Disabled', (1) - 'Enabled'

ShowProtAllDamageTypes=0
; ... enables (1) or disables (0) showing all protection stats, even if they are 0
HideTargetProtectionFallDamage=0
... hides protection icon for fall damage; (0) - 'Disabled', (1) - 'Enabled'

HideTargetProtectionFlyDamage=0
... hides protection icon for fly damage; (0) - 'Disabled', (1) - 'Enabled'

HideTargetProtectionFireDamage=0
... hides protection icon for fire damage; (0) - 'Disabled', (1) - 'Enabled'

RecoveryVisualization=1
; ... enables (1) or disables (0) visualization of healing that hovered in the inventory item gives
Expand Down Expand Up @@ -313,6 +348,9 @@ DamagePopupColorDmgTypes=1

DamagePopupColorOnlyIcon=0
; ... enables (1) or disables (0) coloring only the popup icon

DistanceWeaponDamageType=64
; ... This value is used to override distance weapon protection icon type. It's maintained by plugin itself. Do not change it.
```

</details>
174 changes: 155 additions & 19 deletions vdf-include/SYSTEM/AUTORUN/ZUTILITIES.D
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ const int Choice_PX = 6400; // Position at left side
const int Choice_SX = 1500; // Size X
const int Choice_SY = 350; // Size Y
const int Choice_DY = 120; // Delta alignment
// Choice for Protection Option
const int ChoiceProtection_PX = 6000; // Position at left side
const int ChoiceProtection_SX = 1900; // Size X
const int ChoiceProtection_SY = 350; // Size Y
const int ChoiceProtection_DY = 120; // Delta alignment

const string MenuBackPic = "UnionMenu_BackPic.tga";
const string ItemBackPic = "";
Expand Down Expand Up @@ -87,6 +92,19 @@ instance C_MENUITEM_CHOICE_BASE(C_MENU_ITEM_DEF)
flags = flags | IT_TXT_CENTER;
};

instance C_MENUITEM_CHOICE_PROTECTION(C_MENU_ITEM_DEF)
{
backpic = ChoiceBackPic;
type = MENU_ITEM_CHOICEBOX;
fontname = FontSmall;
posx = ChoiceProtection_PX;
posy = Start_PY + ChoiceProtection_DY;
dimx = ChoiceProtection_SX;
dimy = ChoiceProtection_SY;
flags = flags & ~IT_SELECTABLE;
flags = flags | IT_TXT_CENTER;
};

instance C_MENUITEM_SLIDER_BASE(C_MENU_ITEM_DEF)
{
backpic = SliderBackPic;
Expand Down Expand Up @@ -886,58 +904,176 @@ instance MenuItem_Opt_Page5_ShowTargetProtection(C_MENU_ITEM)
fontname = FontSmall;
posy += Menu_DY * CurrentMenuItem_PY + Text_DY;

text[0] = "ShowTargetProtection";
text[0] = "ShowTargetProtectionNoFight";
text[1] = "protection icon and value next to the focused npc hp bar";
};

instance MenuItem_Opt_Page5_ShowTargetProtection_Choice(C_MENU_ITEM_DEF)
instance MenuItem_Opt_Page5_ShowTargetProtectionNoFight_Choice(C_MENU_ITEM_DEF)
{
C_MENUITEM_CHOICE_BASE();
C_MENUITEM_CHOICE_PROTECTION();
posy += Menu_DY * CurrentMenuItem_PY;

onchgsetoption = "ShowTargetProtection";
onchgsetoption = "ShowTargetProtectionNoFight";
onchgsetoptionsection = "zUtilities";
text[0] = "Off|Weapon|All";
text[0] = "Disabled|CurrentWeapon|All";
};

instance MenuItem_Opt_Page5_ShowProtOnlyInFight(C_MENU_ITEM)
instance MenuItem_Opt_Page5_ShowTargetProtectionInFight(C_MENU_ITEM)
{
CurrentMenuItem_PY = 2;
C_MENU_ITEM_TEXT_BASE();
fontname = FontSmall;
posy += Menu_DY * CurrentMenuItem_PY + Text_DY;

text[0] = "ShowProtOnlyInFight";
text[1] = "shows icons only if hero is in fight mode";
text[0] = "ShowTargetProtectionInFight";
text[1] = "protection icon and value next to the focused npc hp bar";
};

instance MenuItem_Opt_Page5_ShowProtOnlyInFight_Choice(C_MENU_ITEM_DEF)
instance MenuItem_Opt_Page5_ShowTargetProtectionInFight_Choice(C_MENU_ITEM_DEF)
{
C_MENUITEM_CHOICE_BASE();
C_MENUITEM_CHOICE_PROTECTION();
posy += Menu_DY * CurrentMenuItem_PY;

onchgsetoption = "ShowProtOnlyInFight";
onchgsetoption = "ShowTargetProtectionInFight";
onchgsetoptionsection = "zUtilities";
text[0] = "Off|On";
text[0] = "Disabled|CurrentWeapon|All";
};

instance MenuItem_Opt_Page5_ShowProtAllDamageTypes(C_MENU_ITEM)
instance MenuItem_Opt_Page5_TargetProtectionIconStyle(C_MENU_ITEM)
{
CurrentMenuItem_PY = 3;
C_MENU_ITEM_TEXT_BASE();
fontname = FontSmall;
posy += Menu_DY * CurrentMenuItem_PY + Text_DY;

text[0] = "ShowProtAllDamageTypes";
text[1] = "shows icons for all damage types, even if 0 protection";
text[0] = "TargetProtectionIconStyle";
text[1] = "protection icon style";
};

instance MenuItem_Opt_Page5_ShowProtAllDamageTypes_Choice(C_MENU_ITEM_DEF)
instance MenuItem_Opt_Page5_TargetProtectionIconStyle_Choice(C_MENU_ITEM_DEF)
{
C_MENUITEM_CHOICE_BASE();
C_MENUITEM_CHOICE_PROTECTION();
posy += Menu_DY * CurrentMenuItem_PY;

onchgsetoption = "ShowProtAllDamageTypes";
onchgsetoption = "TargetProtectionIconStyle";
onchgsetoptionsection = "zUtilities";
text[0] = "Off|On";
text[0] = "DamagePopup|Shields";
};

instance MenuItem_Opt_Page5_TargetProtectionIconPosition(C_MENU_ITEM)
{
CurrentMenuItem_PY = 4;
C_MENU_ITEM_TEXT_BASE();
fontname = FontSmall;
posy += Menu_DY * CurrentMenuItem_PY + Text_DY;

text[0] = "TargetProtectionIconPosition";
text[1] = "protection icon position";
};

instance MenuItem_Opt_Page5_TargetProtectionIconPosition_Choice(C_MENU_ITEM_DEF)
{
C_MENUITEM_CHOICE_PROTECTION();
posy += Menu_DY * CurrentMenuItem_PY;

onchgsetoption = "TargetProtectionIconPosition";
onchgsetoptionsection = "zUtilities";
text[0] = "Top|Right";
};

instance MenuItem_Opt_Page5_Disclaimer(C_MENU_ITEM_DEF)
{
CurrentMenuItem_PY = 5;
fontname = FontSmall;
type = MENU_ITEM_TEXT;
posx = 0;
posy = Menu_DY * CurrentMenuItem_PY + Text_DY + Start_PY;
dimx = 8100;
flags = flags & ~IT_SELECTABLE;
flags = flags | IT_TXT_CENTER;
text[0] = "Below section works only when ShowTargetProtection* = `All`";
};

instance MenuItem_Opt_Page5_HideTargetProtectionZeroValues(C_MENU_ITEM)
{
CurrentMenuItem_PY = 6;
C_MENU_ITEM_TEXT_BASE();
fontname = FontSmall;
posy += Menu_DY * CurrentMenuItem_PY + Text_DY;

text[0] = "HideTargetProtectionZeroValues";
text[1] = "hide protection icon when value equals 0";
};

instance MenuItem_Opt_Page5_HideTargetProtectionZeroValues_Choice(C_MENU_ITEM_DEF)
{
C_MENUITEM_CHOICE_PROTECTION();
posy += Menu_DY * CurrentMenuItem_PY;

onchgsetoption = "HideTargetProtectionZeroValues";
onchgsetoptionsection = "zUtilities";
text[0] = "Disabled|Enabled";
};

instance MenuItem_Opt_Page5_HideTargetProtectionFallDamage(C_MENU_ITEM)
{
CurrentMenuItem_PY = 7;
C_MENU_ITEM_TEXT_BASE();
fontname = FontSmall;
posy += Menu_DY * CurrentMenuItem_PY + Text_DY;

text[0] = "HideTargetProtectionFallDamage";
text[1] = "hide protection icon for fall damage";
};

instance MenuItem_Opt_Page5_HideTargetProtectionFallDamage_Choice(C_MENU_ITEM_DEF)
{
C_MENUITEM_CHOICE_PROTECTION();
posy += Menu_DY * CurrentMenuItem_PY;

onchgsetoption = "HideTargetProtectionFallDamage";
onchgsetoptionsection = "zUtilities";
text[0] = "Disabled|Enabled";
};

instance MenuItem_Opt_Page5_HideTargetProtectionFlyDamage(C_MENU_ITEM)
{
CurrentMenuItem_PY = 8;
C_MENU_ITEM_TEXT_BASE();
fontname = FontSmall;
posy += Menu_DY * CurrentMenuItem_PY + Text_DY;

text[0] = "HideTargetProtectionFlyDamage";
text[1] = "hide protection icon for fly damage";
};

instance MenuItem_Opt_Page5_HideTargetProtectionFlyDamage_Choice(C_MENU_ITEM_DEF)
{
C_MENUITEM_CHOICE_PROTECTION();
posy += Menu_DY * CurrentMenuItem_PY;

onchgsetoption = "HideTargetProtectionFlyDamage";
onchgsetoptionsection = "zUtilities";
text[0] = "Disabled|Enabled";
};

instance MenuItem_Opt_Page5_HideTargetProtectionFireDamage(C_MENU_ITEM)
{
CurrentMenuItem_PY = 9;
C_MENU_ITEM_TEXT_BASE();
fontname = FontSmall;
posy += Menu_DY * CurrentMenuItem_PY + Text_DY;

text[0] = "HideTargetProtectionFireDamage";
text[1] = "hide protection icon for fire damage";
};

instance MenuItem_Opt_Page5_HideTargetProtectionFireDamage_Choice(C_MENU_ITEM_DEF)
{
C_MENUITEM_CHOICE_PROTECTION();
posy += Menu_DY * CurrentMenuItem_PY;

onchgsetoption = "HideTargetProtectionFireDamage";
onchgsetoptionsection = "zUtilities";
text[0] = "Disabled|Enabled";
};
63 changes: 63 additions & 0 deletions zUtilities/DamageMaskHelper.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Supported with union (c) 2020 Union team
// Union SOURCE file

namespace GOTHIC_ENGINE
{
void DamageMaskHelper::MarkIntDamageType(const int& damageTypeMask, DamageMask& mask)
{
for (const auto& mapItem : DAMAGE_MAP) {
if (damageTypeMask & mapItem.type) {
mask.set(mapItem.index);
}
}
}

void DamageMaskHelper::MarkWeaponDamage(const oCItem* weapon, DamageMask& mask)
{
for (int i = 0; i < oEDamageIndex::oEDamageIndex_MAX; ++i) {
if (weapon->damage[i] > 0) {
mask.set(i);
}
}
}

void DamageMaskHelper::MarkMunitionDamage(const oCItem* weapon, DamageMask& mask)
{
const bool hasMunition = weapon->munition != 0;
const bool isCrossbow = (weapon->flags & ITM_FLAG_CROSSBOW) != 0;

const oCItem* leftHand = player->GetLeftHand()->CastTo<oCItem>();
const oCItem* rightHand = player->GetRightHand()->CastTo<oCItem>();
const oCItem* handItem = isCrossbow ? leftHand : rightHand;

const int handInstanz =
reinterpret_cast<int>(handItem)
? handItem->instanz
: 0;

const bool useMunition =
static_cast<bool>(
hasMunition &
(handItem != nullptr) &
(handInstanz == weapon->munition)
);

const oCItem* damageSource = useMunition ? handItem : weapon;

MarkWeaponDamage(damageSource, mask);
}

bool DamageMaskHelper::ItemHasDistanceOrMunitionCategoryFlag(const oCItem* item) {
return item->mainflag & (ITM_CAT_FF | ITM_CAT_MUN);
}

/* In G1 default damageType for spell is `oEDamageType_Blunt` so for summon/transformation etc. spells
it's best to reset this flag in mask to hide incorrect protection icon.
In G2 default damageType for spell is `oEDamageType_Magic` so it's left untouched.*/
void DamageMaskHelper::FixupSpellDamageMask(DamageMask& mask)
{
#if ENGINE <= Engine_G1A
mask.reset(oEDamageIndex_Blunt);
#endif
}
}
Loading