Skip to content

Fix L’Hiver Mod: Monster HP sometimes increases during a fight#953

Draft
blackus3r wants to merge 3 commits into
Try:masterfrom
blackus3r:master
Draft

Fix L’Hiver Mod: Monster HP sometimes increases during a fight#953
blackus3r wants to merge 3 commits into
Try:masterfrom
blackus3r:master

Conversation

@blackus3r

Copy link
Copy Markdown

This should fix the issue described here: #813
I've tested it and it worked with L'hiver + Uriziel Mod.

Fix Npc_CanSeeNpc logic to use logical orientation instead of visual bone rotation

Previously, Npc_CanSeeNpc (via Npc::canRayHitPoint) relied on visual.viewDirection(), which calculates the viewing angle dynamically from the BIP01 root bone. This caused the perceived line of sight to fluctuate significantly during combat animations (e.g., when a character turns sideways to dodge or stumble).

As a result, mods relying on line of sight checks during combat (like the anti-exploit healing mechanic in L'Hiver) would break, causing enemies to regenerate mid-combat because they temporarily "lost sight" of the player due to their own animation.

Using the NPC's logical angle instead prevents these animation-based FOV blindspots and aligns the engine's behavior correctly with original ZenGin logic.

@Try

Try commented Jul 23, 2026

Copy link
Copy Markdown
Owner

Hi, @blackus3r and thanks for PR!

Use of head orientation is intended in this place. In original scripts, when npc sits on a chair/bench his face is oriented opposed to logical orientation, and then if you talk to them this script runs:

		if (Npc_CanSeeNpc(self, other))
		{
			AI_StartState 	(self, ZS_Talk, 0, ""); 	// keep talking while sit
		}	
		else 
		{
			...
			AI_StartState 	(self, ZS_Talk, 1, ""); 	// standup and turn around
		};

@blackus3r

blackus3r commented Jul 23, 2026

Copy link
Copy Markdown
Author

Hey @Try,
thanks for the important heads up!
That explains why the visual orientation was originally used in this place.
To preserve this intended behavior for chairs/benches while still fixing the combat regeneration bug in the L'Hiver mod, I've added a check for interactive objects.

What do you think about this hybrid approach?

Edit: Okay, whatever. The bug is back. I need to do more testing. I'll get back to you.

The engine treated ATR_REGENERATEHP/ATR_REGENERATEMANA as points per
second, but in the original engine the value means 1 point every N
seconds. Mods like L'Hiver use this attribute for monster regeneration:
a value of e.g. 20 healed 20 HP per second instead of 1 HP every 20
seconds, so enemies regenerated faster than the player could damage
them.
In melee the head bone of an attacking monster dives into the target;
with only a few centimeters between head and target the direction of
that vector is numerically meaningless and the +-100 deg view-angle
check fails at random. L'Hiver's anti-exploit healing reads this as
"player ran away" and restores the monster's HP mid-fight, while the
monster itself drops out of its attack state.

The line-of-sight ray still starts at the head bone; only the view
cone is measured from the npc position, matching the original engine.
@Try

Try commented Jul 23, 2026

Copy link
Copy Markdown
Owner

I'll move PR into drafts for now.

one theory: maybe in L'Hiver script never meant to reach Npc_CanSeeNpc check, and was suppose to early-out earlier

@Try
Try marked this pull request as draft July 23, 2026 22:49
@blackus3r

Copy link
Copy Markdown
Author

@Try

I built and tested another fix last night. However, it was very late, so I'll continue testing over the next few days to see if the bug has been fixed.
In any case, I've gotten much closer to identifying the problem thanks to additional logging:

Npc_CanSeeNpc computed the view-cone direction from the NPC's head bone to the target. In melee, an attacking monster's head dives into the player model — the measured distance drops to ~35 cm and the direction vector becomes numerical noise. The ±100° view-angle check then fails randomly, even with the player directly in front and a clear line of sight. L'Hiver's anti-exploit script interprets Npc_CanSeeNpc == false as "player ran away", heals the monster (+20 HP per trigger) and drops it out of its attack state.

Fix 1: Measure the view cone from the NPC position instead of the head bone — collision keeps positions ~1 m apart in melee, so the direction stays stable. The line-of-sight ray still starts at the head bone. Matches original engine behavior.

Fix 2 : ATR_REGENERATEHP/MANA was treated as points per second; the original engine defines it as 1 point every N seconds. A value of 20 healed 20 HP/s instead of 1 HP per 20 s. Not the cause for the wolf (attribute was 0), but would make any monster that uses it effectively unkillable.

Verified: Debug logging showed 14× script heals triggered by failed sight checks after the fix, wolf fights behave normally.

@Try

Try commented Jul 25, 2026

Copy link
Copy Markdown
Owner

Fix 1: Measure the view cone from the NPC position instead of the head bone
Matches original engine behavior.

Hm, look like a hack rather: it's very counter-intuitive to use head-bone as a reference for view direction, but logical position for view origin. When you mention that it matches original behavior, did you had some specific test setup?

Fix 2 : ATR_REGENERATEHP/MANA was treated as points per second

That looks solid.
Should we maybe merge regen, only for now?

@blackus3r

Copy link
Copy Markdown
Author

I phrased that too strongly. I didn’t compare it against the ZenGin binary, and I don’t have a proper setup to test the original engine. So this is based on debugging and observed behavior, not on a direct verification.

What led to the change was a debug log from a wolf fight in L’Hiver + Uriziel. In melee, the attacker’s head bone can move directly into the target. That makes the head-to-target vector extremely short, so the calculated angle is basically just numerical noise. The ±100° check then fails more or less randomly.

L’Hiver’s anti-exploit script treats that failed sight check as if the player had broken line of sight or run away, so it heals the enemy. When the cone is calculated from the body position instead, the vector stays stable and the check no longer flickers.

About the head-bone/position inconsistency: in the current version, combat no longer uses the head bone for the facing direction at all. It now uses the logical angle, which is also what the AI and turn logic use as the actual facing direction.

The head bone’s viewDirection() is only used when interactive() != nullptr, for example during a seated MOBSI dialog. In that case, the animation can physically rotate the model away from its logical angle.

So in combat it is position plus logical angle. Only for MOBSI it is position plus the actual head direction. If that is still too much for one PR, I can also remove the MOBSI special case and keep only the combat change.

@Try

Try commented Jul 26, 2026

Copy link
Copy Markdown
Owner

Sorry, but at this point you essentially introducing more and more complexity to fix case, that may not even be broken.
If you very sure, that MOBSI is special, or raycast logic, or angle logic - it has to have sufficient evidence from base game.

In current PR, ray-cast origin is still based of head-bone, while angles computes from pos - ground position. Ground position is specific to opengothic, and unlikely to be a thing in original game.

In my test, with marvin+g, it seem hostile nps can detect you even if player is stand behind:
image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants