Skip to content

dotnet 8 conversion + attempt at "hitless practice" feature - #3

Open
nacitar wants to merge 2 commits into
borgCode:masterfrom
nacitar:dotnet8/hitless-practice
Open

dotnet 8 conversion + attempt at "hitless practice" feature#3
nacitar wants to merge 2 commits into
borgCode:masterfrom
nacitar:dotnet8/hitless-practice

Conversation

@nacitar

@nacitar nacitar commented Jun 2, 2026

Copy link
Copy Markdown

This PR is primarily a write-up of my findings while trying to add a feature I very much want for my own practice. I am hoping that someone with a stronger understanding of the game internals and the hook/ASM side of this codebase might be able to take the idea further or implement it more cleanly.

I made an earnest attempt to solve it myself and will keep iterating on it regardless, but I want to be upfront that this branch is just the best that I could manage.

This branch also includes a .NET 8 conversion so I can build and test on ArchLinux. That part is directly useful to me because the older framework setup is much harder to deal with when not on Windows.

Goal

The feature I want is a more natural way to practice hitless runs.

If I do not get hit, the run should behave exactly like a normal run.

If I do get hit, I want special behavior that extends fights until I've managed to clear the full enemy healthbar hitless.

Desired behavior

When the player is hit by an enemy:

  1. The player's health should be maintained.
  2. Otherwise-fatal hits should not kill the player.
  3. Enemies should heal back to the health they spawned with.
  4. Ideally this could support either all enemies or bosses only.

Important detail: restore to spawned/observed health, not always full max health. For example, the second Bell Gargoyle should only heal back to its half-health spawn state.

When the player is not hit by an enemy:

  1. Fall damage and environmental damage should still work normally.
  2. Fall/environmental damage should still be able to kill the player.
  3. Death planes should still kill the player normally.

In other words, the only behavior I want to change is what happens when the player is hit by an enemy. The run should be indistinguishable from a vanilla run if the player is never hit.

Why I want this

The idea is that the game still feels like normal gameplay, but if I get hit I effectively have to replay the full boss health bar in order to move on.

To me, that feels much more fun than making both the player and the boss unkillable and then grinding for a long time. It preserves the natural rhythm of the run while automatically pushing practice toward the parts that actually need work. At least for me, that makes it feel less like "practice" and more like just playing the game for fun on a given day, gradually getting better while the game implicitly extends my practice in areas where I still make mistakes.

Existing features I investigated

The two existing features that seemed most relevant are No Death and No Damage.

For the desired functionality, every column below would ideally be TRUE:

No Damage From Hits Survive Fatal Hits Death Planes Kill Fall Damage Works Falls Can Kill
No Death FALSE TRUE FALSE TRUE FALSE
No Damage TRUE TRUE TRUE FALSE FALSE

Ignoring enemy healing for a moment, No Damage initially looked like the most promising approach. The problem is that it also negates fall/environmental damage, which means no RTSR setup and generally makes it unusable for the behavior I want.

No Death gives a closer approximation if I accept some compromises. Assuming I ignore death planes and accept that falls/environmental damage can hurt me but not kill me, it becomes usable if I can separately restore player health on hit and heal enemies on hit.

What I used to build this branch

One of your other repositories, AutoHitCounter, shows that the problem of external hit detection has already been solved. That gave me a proof of concept I could borrow from, because without that prior work and without deeper expertise here, I do not think I would have been able to get this part working myself. By pulling ideas from that project into this one, I was able to get to the point where SilkySouls can react to the player being hit.

What I added here

I added Heal enemies when player hit.

This tracks enemies' health when they appear, stores those observed values, and restores them to those amounts whenever the player is hit. That means it preserves cases like the second Bell Gargoyle correctly; restoring that enemy to absolute full health would be wrong.

I also added On non-fatal player hit: no player damage.

This does not truly prevent the damage event. Instead, it immediately restores the player's health afterward. I could only make this work for non-fatal hits. If the hit would have been fatal, the death processing was winning the race before the health restoration took effect, and I could not figure out how to resolve that cleanly.

Best approximation I could get working

Right now, my best approximation of the intended feature is:

  1. Turn on No Death.
  2. Turn on On non-fatal player hit: no player damage so normal enemy hits restore the player's health.
  3. Turn on Heal enemies when player hit.

With that combination, the feature is effectively implemented except for two remaining limitations:

  1. Death planes do not kill the player.
  2. Fall/environmental damage can hurt the player, but cannot kill the player.

So this gets very close to what I want, but it is still not fully correct. That said, it is fully playable as a prototype for the feature I am after, and it is already good enough for me to use for practice even though it is not yet ideal.

Why I am opening this anyway

I suspect there is a better implementation path that could solve the remaining problems more cleanly.

I am a seasoned developer, but I am not familiar with the internals of this game and I am not very capable at the assembly level. C++ is about as low-level as I comfortably go.

To get this far, I used AI with both this project's code and the AutoHitCounter code as context, and had it help me piece the approach together.

I am quite confident in the .NET 8 conversion. I am much less confident in the hook/ASM-heavy parts of this branch; some of this may be horrible AI-slop code, but I did test it quite thoroughly.

I do not expect this PR to be merged as-is. I do think this feature would be useful for people interested in practicing speedruns, though, because it lets you practice without taking the fun out of simply playing the game. It makes the process feel less rigid and less militaristic. I am hopeful that the maintainers of this project will agree that the idea is worthwhile and perhaps help turn it into something cleaner and more broadly usable.

nacitar added 2 commits May 22, 2026 14:31
Introduce a dedicated player-hit behavior service that powers two independent options in the Enemy tab: negate non-fatal player damage and heal enemies when the player is hit.

Wire the new service through main composition and enemy view model bindings, and add the required ASM/hook plumbing (scripts, offsets, code-cave layout, and resources) to detect player-hit events with attacker context.

Track observed enemy health across play (including when heal-on-hit is off) and bound tracker lifetime/size to keep restore behavior accurate while avoiding unbounded growth.
@nacitar nacitar changed the title Dotnet8/hitless practice dotnet 8 conversion + attempt at "hitless practice" feature Jun 2, 2026
@nacitar

nacitar commented Jun 3, 2026

Copy link
Copy Markdown
Author

One other deficit here that I neglected to consider is that ideally you'd still be able to die to Seath in the first encounter.

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.

1 participant