#Damage Events
3554 messages ยท Page 4 of 4 (latest)
internal annotation working wonders
maybe we shouldn't just be marking the entire container as internal -.-
I do have it local. i'm making sure the change works as intended before committing that part
already removed in local. same as my comment above, getting gametests working before doing another commit
๐
I'm thinking it's probably a good idea to add comments to my gametest to explain what it's doing, but none of the other tests have that. Are we avoiding the clutter of comments or was it just not done in the past and probably should be?
added them for now. I figure it's trivial to remove if we decide we don't want them.
alright. PR changed from draft to open.
alright so
- Helper methods ?
- Removing clamp on IReductionModifier ?
- Javadoc fixes ?
- Logic fix on absorption ?
- Remove internal annotation from Damage Container ?
looks like it's all covered
yeah. The only change i realize i could go back and do it swap the use of persistent data to data attachments. I was just glad to get the test finally working i forgot to go back and do that.
that's how they are supposed to be per Maty.
they're supposed to be broken?
wild
I'd prefer a standard where they didn't look broken but whatever lol
if i read correctly what people said above, it's just a display bug with intelliJ not an incorrect format
you are referring to the /**{@return words and more words}*/ syntax right?
alright. gametest refactored to use attachments. That was delightfully easy to switch to
more docs are never bad, I say keep them
Can someone pin the PR for quick reference?
thanks
@sand copper
Since the PR is still open
Thoughts onchanging this parameter to be damageIn?
it's a misleading name currently
it's not misleading, the value passed into that method is the reduction value from vanilla. the damage is changed after this method is returned and the outgoing value is applied to the damage
so like in the gametest, the reduction amount on 20 points of damage for wearing a diamond chestplate is 1.959991 That gets passed into this function, not the 20. we add 2 in our gametest, then the damage amount is reducted to 16.040009
I don't want to seem pushy, but i also don't know the internal procedures. Do i need to request a review of this PR or will that happen automatically?
just poke it
diamond armour only eliminates 2 damage on a 20 damage hit?
that doesn't seem right
not a full set. just the chestplate. that's before enchantments too.
ah
it doesn't usually hurt to ask for someone to review a PR in #neoforge-github if it's not been looked at in some appreciable amount of time (or needs review to guide future decisions)
and for rereviews, my recommendation is usually to immediately rerequest a review in the GH UI, and then ask for a reviewer in Discord after some time if it hasn't been reviewed again
@quick kernel i made the change you requested.
๐
@sand copper Added review
Something feels really wrong there
like critically wrong
I've re-read it a bunch of times and either the patch format is blinding me, or that'll outright break absorption
alright, then let's look at unpatched vanilla and figure out how it handles absorption and then see if these changes violate any logical assumptions. so here's unpatched vanilla
and the current patch (soft wrap on to condense SS size)
I think the line that bothers you is the removal of the latter setAbsorption call after setHealth.
here's how that plays out with sample data
the latter call does absolutely nothing. The setAbsorption call clamps the value to zero and the max absorption amount, so even with negative values, the actual amount stored is zero.
So, either there is some other mechanism i don't see, or this is leftover code from when it mattered that hasn't been removed since it does nothing.
the only change i could make to the patch is removing the Math.min on the setAbsorption call since the clamp already does what that was intending to do. That should probably change.
since it was unnecessary to capture the damage after absorption prior to setting the absorption (due to how reduction modifications and new damage are applied), the f1 variable does not reflect the actual amount of absorption to be removed after the setHealth call. This combined with its redundancy make it necessary to remove.
how does this do anything other than getting the sum of all reductions
I can't see how it does anything other than that
that's exactly what it is supposed to do.
no it's not
why not mapToFloat 
it's meant to get the amount of damage absorbed
that's why it's there
I.E amount of damage that absorption eliminated
oh! i see what you mean. it should be capturing the absorption reduction only
๐ค why does vanilla only award stats for absorption below 3.4...? does vanilla not have absorption over that amount?
3.4E37F
that's 3 with 37 0's after it
lol
I.E. max float
basically if it's a positive float
damn scientific notation
yeah. so f now just gets set wiith this.damageContainers.peek().getReduction(Reduction.ABSORPTION)
has to be clamped
or does it 
should we allow more absorption than has absorption hearts?
I guess so?
anyway
good question. I gotta get off my train. we currently allow the reudction to exceed the actual absorption amount and record that but obviously that isn't what's applied to the entity
the other issue here is the application of absorption in relation to damage incoming
I.e. you shouldn't be able to absorb more damage than there was damage to absorb
which doesn't appear to be the case here at all currently
it just looks to take the absorb amount the player currently has, then uses it
you really need to be clamping the reduction of absorbtion to the newDamage at that stage of the chain
which is what vanilla does here
it then naturally uses that clamped value to feed back into the reduction of the absorption on the entity
which we don't, we just.. reduce it by the full amount?
and also because of that, f will never be properly clamped either
I'm heading off for the night, but there's definitely some issues here to fix
the rest of the Pr looked fine, even if I think the inline braced javadocs are stupid as fk
What if I want my feature to cause absorption to consume and heal the player. Going back to our point about not arbitrarily clamping reductions. If I modify reduction to be greater than the damage dealt, it now heals. Should that not be an option?
it's also worth reminding that this line does three things. It starts with a vanilla value of absorption, applies any stored reduction functions, stores this final reduction value, and applies that final reduction value to the new damage. Which means we are letting users decide what the absorption value should be. There is no enforcement of whether the value matches the actual value, because we don't really care. We consume whatever value the modders have told us to consume. Vanilla is going to cap that at the actual value when updating it's own record of absorption, but the damage reduction is going to follow a modded pattern which states it doesn't have to conform to the actual value.
yeah, that min function wasn't necessary. i removed it.
that's unrelated
what I'm saying is the initial value you're inserting is the full absorption value
regardless of damage
so if I have 5 absorption, and do 3 damage
you're just saying it's gonna absorb 5 damage, even though there's only 3 to absorb
which then reflects onto where you setabsorptionamount again, cus you're just setting it based on the full amount
oh, i see what you mean! that would mean having more absorption than the damage received would always heal you
no it' mean any time the damage is less than your absorption, you'd consume all absorption regardless
the initial input needs to be clamped at the value of the newDamage at that stage
and if users want to increase it after that (in the event) they can
yes. that i agree
thne you have to recalculate the amount of damage absorbed, and use that for f
what are you recalcualting for f? if you pass in the lesser of damage or absorption into the setReduction call and nothing modifies that value, you still get all of the damage that was absorbed. and if it is modified by a mod, you are still getting what the mod intended to be absorbed.
if a mod modifies the reduction, then the amount of absorption will change, no?
yes
so that difference needs to be accounted for when re-setting absorption, and granting the stat
it is?
as an example....
okay as I wrote this out i saw the issue. ugh this is getting messy
lol
okay, we may want to reinstate that clamping on reductions. if you want to heal the mob do that as its own effect in the event.
this isn't really related to healing
this is just about ensuring we consume absorption properly
clamp the absorption value in setReduction to the current newdamage is step 1
then step 2 is basically just copy what vanilla had for f1, but with the reduction instead of the absorptionamount
negative damage from over-reductions is what i was referring to as healing
yeah I don't see a problem here with that
that can remain as-is, we just need to ensure we're sanitising the actual absorption values used
which has no real impact on the damage
the issues I've brought up are entirely unrelated to damage lol
it's all about the absorption
your step 2 isn't required. what happens in vanilla in f1 is caching the damage after absorbtion. then it reduces absorption by subtaracting theis calculated post-absorption damage from the daamge, then f is set by getting that same difference again. That difference is what we are already storing in getReduction so we don't need to change how f is calculated.
we do, because getReduction isn't clamped by damage or 0 for this
so could say I want 5000 damage absorbed
but if the attack is only 5 damage
then only 5 damage goes into the stat
it is now.
yeah. running checks now. then i'll push
pushed
yeah still same issue
there's nothing stopping people arbitrarily breaking the stat gain because they set an absorb value higher than is possible to absorb
there's essentially two ways to approach a fix, depending on what ideology we take
How? I clamped the modifier and the reduction to the damage value and zero.
oh I didn't even see that set/modify reduction clamp
That definitely shouldn't be there
now we're clamping values arbitrarily
If we have two mods that modify the value, one reduces it by 5 and the other increases it by 5
and the damage is 3
then what'll happen now is the resultant damage will be 5, whereas before it would've been 3
this breaks proper stacking modifiers
the modifiers should be allowed to basically go to any value, since stacking means any value is valid
then we sanitise the value before using it
I can tell this damage thing has got your head in a spin lol
clamp needs to go, then we have to decide on one of two ideologies for the last fix:
Ideology 1:
Users should be able to modify the entity's remaining absorption after an attack by amounts that don't make sense to the damage - I.E. Reducing absorption hearts by 6, even if they're only taking 3 damage
Ideology 2:
Users shouldn't be able to modify the entity's remaining absorption value outside of what is possible for the damage taken - I.E. you can only reduce the absorption hearts by up to the amount of damage the event has
then once that's decided on if you want you can send me the post-PR actuallyHurt method and I can show you want I mean for solutions on it
in ideology one, what if you want to reduce by 6 but there was only 4 absorption to begin with? if we are only recieving 3 damage at this point in the sequence what is the final new damage value from this example?
I mean regardless of what we do
we can only reduce absorption value by the amount of absorption they have
so that's not really affected
if we're taking 3 damage, have 4 absorption hearts, and tell the event we want the absorption reduction to be 6, then the result would be:
Damage: 0
Absorption Hearts Remaining: 0
Container reduction value for absorption: 6
For Ideology 1
Results for ideology 2:
Damage: 0
Absorption hearts remaining: 1
Container reduction value for absorption: 6
I think one is what we want. but one more scenario to make sure i understand these correctly. how woudl it look with 20 damage, 4 hearts, increased to 6 by modifiers?
also this is so true. i'm in a weird brainfog today.
20 dmg, 4 absorb, 6 modified reduction
Scenario 1:
Damage: 14
Absorption hearts remaining: 0
Container reduction value for absorption: 6
Scenario 2:
Damage: 14
Absorption hearts remaining: 0
Container reduction value for absorption: 6
it only has an effect where the damage is less than the reduction value & absorb
because the issue stems from whether you should be able to consume more hearts than the damage could possibly consume
there's problems with both ideologies, so it's a "pick one" scenario
Ideology 1:
Pros: Allows for people to directly modify the resultant absorption value on the entity even if it doesn't align with the amount of damage being absorbed
Cons: Means that increasing the amount of absorption will unnecessarily and possibly unintentionally consume additional hearts even though they're not doing anything
Food for thought: If you want to directly modify the absorption hearts, you could hook damage post and modify it there?
Ideology 2:
Pros: Means that users can safely increase the absorption value without worrying about unintentionally consuming an entity's absorption despite it not being used to absorb anything
Cons: Can't use the modifier to directly modify the residual absorption on an entity
personally my pick is #2
since it protects innocent usage and you can still modify absorption with a later event
๐ค I think it comes down to what's intuitive to users. I think if you say the absorption consumption is different than what was fed it, it should consume that amount. which means option 1 is still the better option.
normally I'd agree, but then it comes down to whether you think it's intuitive that the reduction directly affects how much should be consumed - or how much it should absorb
cus if a user says they want the reduction to be 6, the question becomes - Do they want to consume 6 absorption even if the damage isn't 6? Or do they just want the maximum possible absorption to be 6, if there's enough damage to satisfy that
so it's a matter of whether we want heart effectiveness to change instead of just heart consumption
heart effectiveness is untouched
it's purely down to heart consumption
should heart consumption be clamped to the amount of hearts actually consumed by the damage
or should be be based on the reduction value set
i mean like if i'm reducing the value by 6 but only 3 hearts are being consumed the effectiveness of each heart is effectively doubled.
what if there's only 2 damage to begin with
should the user have 1 absorb heart remaining? or none
so you're saying 2 damage in 3 hearts. if I want hearts to be twice as effective then in that case i'd expect only 1 heart consumed, but all damage reduced, which wouldn't work. Which as i write that i realize that heart effectiveness is not a viable approach for this API. we need hearts to equal damage (or at least we make that assumption). So then we need option 1 because if we have 3 abs and 2 damage and we double via modifier, then we have 0/0/4. likewise if we have 20 damage, 3 hearts and a doubling function then we end with 14/0/6. so we need to clamp the inbound value of the reduction function to the actual hearts. but we don't clamp the modifiers or their impact on the new damage. then hearts themselves are reduced up to the modifier amount (or stop at zero if higher than the hearts)
nah you're misinterpreting it
logically, doubling the reduction of absorption doubles the effectiveness of the hearts you have
but functionally, it just adds additional pseudo-hearts
thinking about it in terms of effectiveness doesn't work - as you've just realised
you have to think about it in terms of adding or removing hearts
okay, so we store the amount of hearts expected to be consumed in f1 before we apply reductions. if I then pass in a reduction function of (c, r) -> 0 so that no matter the absorption, it will never reduce the damage. do we still consume hearts?
no in either ideology
then we have a problem.
ironically, if you're concerned about the 0/0/4 scenario, then you really should be opting for ideology 2
how do we store the pre-modified absorption and then callback to that value after modifications to decide if we are reducing hearts or not?
you don't need to store the pre-modified absorption at all
here, throw me the post-PR actuallyHurt method
0/0/4 is what i would like to see happening.
I'll show you
protected void actuallyHurt(DamageSource p_21240_, float p_21241_) {
if (!this.isInvulnerableTo(p_21240_)) {
this.damageContainers.peek().setReduction(net.neoforged.neoforge.common.damagesource.DamageContainer.Reduction.ARMOR, this.damageContainers.peek().getNewDamage() - this.getDamageAfterArmorAbsorb(p_21240_, this.damageContainers.peek().getNewDamage()));
this.getDamageAfterMagicAbsorb(p_21240_, this.damageContainers.peek().getNewDamage());
this.damageContainers.peek().setReduction(net.neoforged.neoforge.common.damagesource.DamageContainer.Reduction.ABSORPTION, Math.min(this.getAbsorptionAmount(), this.damageContainers.peek().getNewDamage()));
this.setAbsorptionAmount(this.getAbsorptionAmount() - this.damageContainers.peek().getReduction(net.neoforged.neoforge.common.damagesource.DamageContainer.Reduction.ABSORPTION));
float f1 = net.neoforged.neoforge.common.CommonHooks.onLivingDamagePre(this, this.damageContainers.peek());
float f = this.damageContainers.peek().getReduction(net.neoforged.neoforge.common.damagesource.DamageContainer.Reduction.ABSORPTION);
if (f > 0.0F && f < 3.4028235E37F && p_21240_.getEntity() instanceof ServerPlayer serverplayer) {
serverplayer.awardStat(Stats.DAMAGE_DEALT_ABSORBED, Math.round(f * 10.0F));
}
if (f1 != 0.0F) {
this.getCombatTracker().recordDamage(p_21240_, f1);
this.setHealth(this.getHealth() - f1);
this.gameEvent(GameEvent.ENTITY_DAMAGE);
this.onDamageTaken(this.damageContainers.peek());
}
net.neoforged.neoforge.common.CommonHooks.onLivingDamagePost(this, this.damageContainers.peek());
}
}
you'll also need to factor in this behavior ```java
@ApiStatus.Internal
public void setReduction(Reduction reduction, float amount) {
float modifiedReduction = Mth.clamp(modifyReduction(reduction, amount), 0f, this.getNewDamage());
this.reductions.put(reduction, modifiedReduction);
this.newDamage -= modifiedReduction;
}
private float modifyReduction(Reduction type, float reduction) {
for (var func : reductionFunctions.getOrDefault(type, List.of())) {
reduction = func.modify(this, reduction);
}
return reduction;
}
how it was before your last push was fine and how it should be
the clamps are part of the debate so change them as you see fit
both classes? or just the damage container changes?
just that one specific clamp in modifyReduction
needs to go, that's basically an essential
yeah that one i agree needs to go based on both options
the one in setReduction I would argue needs to go for the same reason
if we allow stacking of modifiers, then any value is valid until used
although that is post-stack
yeah ok that one's fine
cus it's post-stack and leaving it unsanitised after that point poses a problem
uhhh
actually it reintroduces the original issue that pup had
lol
damage is fun
oh right. the clamp needs to be on amount not the function
we can clamp it in either place, this was just less messy than doing it in the patch
wait. we already have a clamp with the min function
yep
that clamp is redundant
that's correct sanitation
also don't forget this setReduction thing applies to all modifiers
not just absorption lol
foggy brain moment
๐ค hmm. yeah i'm not sure which ideology is better. i get the implementations. reading the top comment though, i'm not sure which is better. In one method you can lose more hearts than you lose health. in the other you can lose more health than you lose hearts.
uh no that should never happen
did I miss something
#2 should be the same functionality as #1, with the only difference being the residual hearts is clamped to the damage it could've absorbed
i was just going off your comments. let me check if that's possible based on the code
based on how your wrote #1 i could take 1 damage but still lose 4 hearts of abs
yeah, that's why I don't like ideology #1
because it's disconnected from reality
the only "benefit" it has realistically is that it's the most 'raw' interpretation of the event handling
which technically allows more flexibility for users, but I just see it harming people unintentionally
oh yeah. two. i like two.
the reduction in the container would still be high, but the actual impact on absorption would be constrained. yeah that one makes sense
๐
then strip out the two clamps in set/modify reduction
and base your adjustment on my screenshot there
// Ideology 2
// Increasing the absorption heart reduction increases the amount of hearts consumed, but only up to the available damage
protected void actuallyHurt(DamageSource p_21240_, float p_21241_) {
if (!this.isInvulnerableTo(p_21240_)) {
this.damageContainers.peek().setReduction(net.neoforged.neoforge.common.damagesource.DamageContainer.Reduction.ARMOR, this.damageContainers.peek().getNewDamage() - this.getDamageAfterArmorAbsorb(p_21240_, this.damageContainers.peek().getNewDamage()));
this.getDamageAfterMagicAbsorb(p_21240_, this.damageContainers.peek().getNewDamage());
// Cache pre-absorb damage here
float damage = this.damageContainers.peek().getNewDamage();
this.damageContainers.peek().setReduction(net.neoforged.neoforge.common.damagesource.DamageContainer.Reduction.ABSORPTION, Math.min(this.getAbsorptionAmount(), damage));
// Cache absorb here, clamping to damage since you can't consume more absorb than damage dealt
float absorbed = Math.min(damage, this.damageContainers.peek().getReduction(net.neoforged.neoforge.common.damagesource.DamageContainer.Reduction.ABSORPTION));
// Clamp to not set negative absorb hearts here
this.setAbsorptionAmount(Math.max(0, this.getAbsorptionAmount() - absorbed));
float f1 = net.neoforged.neoforge.common.CommonHooks.onLivingDamagePre(this, this.damageContainers.peek());
// Use ONLY absorbed value here, since you can't have absorbed more damage than existed
float f = absorbed;
if (f > 0.0F && f < 3.4028235E37F && p_21240_.getEntity() instanceof ServerPlayer serverplayer) {
serverplayer.awardStat(Stats.DAMAGE_DEALT_ABSORBED, Math.round(f * 10.0F));
}
if (f1 != 0.0F) {
this.getCombatTracker().recordDamage(p_21240_, f1);
this.setHealth(this.getHealth() - f1);
this.gameEvent(GameEvent.ENTITY_DAMAGE);
this.onDamageTaken(this.damageContainers.peek());
}
net.neoforged.neoforge.common.CommonHooks.onLivingDamagePost(this, this.damageContainers.peek());
}
}
reviewed and approved
although it still has my conversation as unresolved
and I.. do not know how to make that go away
I'll just delete the comment
there we go
@echo kiln all you
I will try to get a few minutes to take a look at it sometime in the next few days if no one beats me to it
before the PR gets merged, please investigate armor durability draining. It's draining way too fast in neo compared to vanilla and unbreaking isnt helping according to a player
May be related to the damage pipeline
Ok just pushed the fix into the damage pipeline improvement PR so it wont get lost
https://github.com/neoforged/NeoForge/pull/1177/commits/ff6fbef6934a56609a5eeed023b6444b93701c77
TIL you could commit directly to my PR
there's a checkbox for allowing maintainers to directly commit to your PR, which is ticked by default
ah. i guess i misunderstood what that box meant. not a bad box to have for sure though
the reason it's there is because a maintainer pushing to your branch is literally someone pushing on your fork's branch -- it'll run any actions on your repo as configured, which includes secrets passed to the actions, so there's the potential of maintainers accessing your repo secrets
practically nobody runs Actions (and stores secrets) on their forks though
GH has no chill with this description lol.
@echo kiln You alright if I just merge the PR or did you want to investigate it closer
I would appreciate one more set of eyes briefly skimming the logic of actuallyhurt post-pr before merging
Go for it
We can always spin up another PR afterwards. Seems good to me and we need some of the fixes too
woot!
I may had forgotten to test unbreaking
I just updated the PR body to include the fixes for armor
Is it better to have the LivingDamageEvent.Pre fired before absorption calculation?
Player treats absorption as part of health, and having damage event fired after absorption calculation will make amplifications in the event bypass absorption, and make reductions in the event cause more consumptions of absorption than expected.
you sholdn't be using .Pre to modify absorption
that's what the absorption stage modifier in incomingdamageevent is for
you should almost never be using .pre to modify damage at all
I'm not modifying absorption. I'm modifying damage
It's a protective equipment
so It makes sense to modify it in .Pre instead of Incoming
Or do you think I should do it as part of reductions?
If its something that is "like" absorption I would think you would apply it in .Pre, by reducing the damage from Pre and applying it against your thing
Since Pre is post-mitigation "about to be applied" damage
It's something reducing damage multiplicatively
for example, -10% damage
For example, player takes 10 damage, with this, they take 9 damage.
But if player has 10 absorption, currently it will consume all 10 absorption
For that I would apply it in Incoming
since you would want it applied pre-mitigation
if it's part of incoming, it will be calculated before armor, enchantment, and magic
That should be fine for your purposes
especially if its just a multiplicative reduction
then everyone just use event priorities again?
if that's the case, should setNewDamage in LivingDamageEvent.Pre be removed?
No, because some may need to consume post-mitigation damage
i.e. if you were implementing an absorption-like effect
But for this feature I would expect you use a HIGH priority LivingIncomingDamageEvent listener
you mean low
or at least a normal prio
because high is for weapons
I don't think there was an established convention for the priorities
at least I don't see one in the javadocs
Weapons for sure should be calculated before armors
Yeah, normal prio seems fine then
I thought separation of offensive modification and defensive modification is part of the purpose of this refactor
That only really ended up happening for multiple damage reductions
I will be observing the current form over the lifecycle of 1.21 and see how it pans out
ehh?
if you want to modify outgoing damage, you use setamount in incoming
if you want to modify incoming damage, you either use setamount, or add a reduction modifier
reduction modifiers run at each stage, not "before" anything
including absorption
Currently damage modification priorities are:
- Incoming
- Armor
- Magic
- Absorption
- Damage.Pre
My proposed changes:
- Incoming
- Armor
- Magic
- Damage.Pre
- Absorption
yes
You can unwind absorption in .Pre if you want to apply it against something else iirc
I just want custom multiplicative modifications be applied before absorption, as it makes more sense to players
If absorption was after the event you lose the ability to care about it at all
but after armour and magic?
Only armor is even sensitive to the actual incoming damage value like that (since it's nonlinear)
yes
then use a modifier on absorption and set a listener priority?
enchantments / potions are multiplicative so the application timing is irrelevant
this is back to the whole 'we need a whole convoluted system for every applicaiton'
That was me
which we definitively voted down cus it got ridiculous trying to fit everyone's niche
I'm still not sure why you can't just do an absorption modifier here
that sounds like it'd work fine
I have several nonlinear defensive curios that I expect to be applied after magic and before absorption
also I am not sure how this javadoc line got through
<li>only the {@link LivingIncomingDamageEvent EntityPreDamageEvent}
right so if you put a modifier for the absorption stage and adjust your listener priority?
modifier modifies reduction or damage?
I thought it modifies reduction
let me check again
the reduction function modifies the effectiveness of that reduction
it takes a bit to wrap your head around
It is, however, difficult to link damage to absorption and remaining HP
My original proposal is to have a modifier for damage.
since absorption isn't a typical damage reduction, but more like a "post-mitigation damage consumer"
yes, that's why I want Damage.Pre to be called before that
but the more important thing is that, should offensive and defensive effects be separated by events or by priorities?
I don't think they should be separated by events necessarily
since that would cause us to just fire two events at once
Priorities are a fine system for that goal
but then the majority of modders would just use normal priority
for both offensive and defensive
That is (imo) a documentation issue
the reduction is a modifier for the damage though
modifying the reduction modifies the damage
it does but you wouldn't be able to do the intended effect here
it would be a mult_base if you do that though
which is reduce the incoming damage by 10%, and then have absorption be computed appropriately
mult_base works for offensive modifiers, but it's terrible for defensive modifiers
mult_base < mult_total for offensive, but mult_base > mult_total for defensive
I can find ways around my problem, but I'm more concerned about how other mods would implement their modifiers
It would be chaotic if the system goes back to the 1.20 style where modifiers from all mods are mixed together
Solidifying the conventions for priority usage early will be important
what
That wouldn't be necessary
the worst part is when someone write offensive an defensive modifiers in the same event listener
that should be prevented
the problem here is you're trying to make a perfect world out of something that'll never be perfect
I can agree with wanting direct-modifiers after each stage or whatever, but the question then gets raised of when it becomes just too much to cater for in what's meant to be a generified event system
The absorption thing should probably be addressed though
yeah I'm still not seeing the problem with absorption
Mod X wants to apply a post-mitigation damage reduction modifier Y
This modifier should reduce the incoming damage (after armor / magic), and then the reduced damage should be applied to the player (which would first apply to absorption)
oh
treating absorption ideologically as a function of health rather than a modifier itself
In the current system, the damage will have already been applied to absorption, meaning the absorption takes A (unreduced) damage, and the player takes (D - A) * Y
Yeah
in reality the function should be D = D * Y and the player's absorption should be hit with the post-reduction value
Yeah that would be good
what if we just add one more callback to damagecontainer
that's still stage-based
but instead of being a reduction, it gives you the newDamage post-reduction, and expects you to return a new modifieddamage
I'm not a fan of it basically just being a setAmount for every stage ideologically, but I think it's realistically the only way we'd be able to cater to the more niche audience without it being a battle every time
Well, there is an alternative
which is basically to gather a stack of attribute-modifier like things for each stage and apply them
honestly I thought that was what we were going to end up with when this started, but we didn't arrive there
That's what I proposed as well
Hence my comment about observing the current system over the lifespan of 1.21
that was discussed and denied
we had a full blown conversation about that that already ended
I know. Because I proposed too many operations
yes but that's the real problem lol
it's never enough
there's always "just add X"
because it's such an intricate system
Well you don't actually need operators
you just need a stack of functions
right now we only have a stack of functions on the reductions
which is a bit weird
yeah
the reduction thing I did think was a bit odd personally
seemed like an arbitrary reduction of capability from just outright unaryoperator'ing the resultant damage
the reduction modifier is designed for weapons that can penetrate protections. Not for defensive gears to reduce damage
Do open a github issue about the absorption thing though
It makes sense to move it, and if people would like to interact with it they have access to the entity and can do so
ok
I think it makes sense ideologically to consider absorption a function of health
rather than a modifying factor
so yeah
and I'd agree with a stack of operating BiFunctions on the damage for each stage
honestly I'm not entirely sure i see the point in the reductionfunctions if operating functions are gonna be present
I think the original intention was to prevent people just doing the functional equivalent of .setAmount on any stage
Also, regarding this line, should it be wrapped by if(!source.is(DamageTypeTags.NO_IMPACT))?
vanilla doesn't wrap it
hence NO_IMPACT tag has no effect on non-player
yep
that is something that would have to be raised as a mojira bug first
^
if a Mojira bug is opened, and marked as confirmed, we can wrap it accordingly
that being said
it looks intentional to me
just stupidly designed
cus they use no impact for the hurtmark
ah
in 1.20.1 they basically only used it for explosion mitigation
then when breeze crap came in they had to redo how they handled explosions a bunch
I still have one damage mixin somewhere..
what about allowing crit on sweep
I have a dangerous local ref mixin on that
yeah this'd be the same thing you had lol
Currently crit will disable sweep. It's probably an unintended consequence. Should it be changed?
is that a neoforge issue? or vanilla?
im confident that sweep and crits are intentionally disjoint, you can only sweep while grounded in vanilla
(personally though, I do loathe having to choose between aoe or what I have normalized as my "normal damage" via jump critting lol)
objectively its not intrinsically poor, I just hate it myself
if you never sweep why not simply be an axe main? ๐
bhop atkspd
you need actually 1.67, but 1.6 is close enough that you won't feel it too badly with low latency (or singleplayer)
I think another detail that adds to this, is unlike other reductions, nothing is consumed in the process of applying those reductions. Absorption is though, which makes it resource-like, like health. And before someone says armor durability, it's not the same. a reduction in health/absorption is a complete reduction in the maximum that can be reduced/taken, whereas the armor is always equally effevtive with durability only mattering when the item breaks as to whether there is or is not a reduction.
I think the problem is that in vanilla crit is only falling attack, which is disjoint from sweep. However, mods may allow other forms of crit that might not be disjoint from crit.
maybe modify crit event a little bit to allow mods to determine whether to disable sweep
that'll be a separate PR
yes
@quick kernel Sorry to bother, but is there a document or something that summarizes the damage events? I'd like to update the /docs damageevents command to explain the new system.
sent you a ping
Made by @quick kernel
The series of events for an attack goes in this order:
- LivingAttackEvent - Use this if you want to catch any time an entity hits another, regardless of damage or whether it will be successful or not
- LivingHurtEvent - Use this if you want to handle an attack that is likely to hit, but before any potions, armour, or enchantments take effect
- LivingDamageEvent - Use this if you want to take action as an entity is definitely being damaged, with the final damage amount being available.
- LivingDeathEvent - Use this event if you want to handle an entity's death for any reason, with the exception of drops.
- Global Loot Modifiers - Use these if you want to handle any drops from a dying entity.
i was curious. i didn't know it existed until now
soo the PR was closed?
spino's yeah they closed it. @sand copper did you want to make a PR to do the change you wanted? or should I give it a stab? (After I read up the previous convos about the change)
which change?
I am not familiar with the damage pipeline so I would have to catch up to speed
ah
yeah these PRs really need to be discussed and reviewed by the active people here first
that PR would've caused issues and I didn't even know it existed lol
so if I am reading this right and convo before, the change is to take this
and make it this
feed the pre damage to the absorption code. Get the absorbed damage amount. And pass the diff between pre damage and absorbed damage to get the remaining damage to continue on
Think I did this right. Logic seems to flow the way the convos described. But another pair of eyes would be good
https://github.com/neoforged/NeoForge/pull/1324/files
gametests 
wat
Wait, is this livingDamageSequenceEvents not consistent every run???
wtf
might be float precision issuesโข๏ธ
if that test is doing an == on a float it's mcfuckedโข๏ธ
but the test seems to be runnign every server tick for GameTestServer? it keeps firing when breakpoint at the assertions
livingDamageSequenceEvents in LivingEntityEventTests
which assertions
the ones inside the thenWaitUntil block on 362-369?
this is a pretty terrible check though ngl
because it does the format and checks against a string literal, but the displayed "expected vs actual" do not match what the check is looking for
helper.assertTrue(formatter.format(player.getHealth()).equals("16.333334"), "player health expected 16.333334, actually " + player.getHealth());
That's the line I am failing on consistently in my pr
Wait, why are... the last few checks are using the wrong attachment data for the error reporting...
ok i think i fixed game test
That matches what I understand was discussed also.
https://github.com/neoforged/NeoForge/pull/1324 Tele's PR for the Absorption tweaks
can we get that pinned also?
is there any documentation anywhere that will need updating if we make this change?
The java doc on Pre, and maybe the sequence in DamageContainer.
And potentially whatever @maiden dove is doing with the /doc command.
Meant to reply to this
the blog post maybe
what am I doing with what
#1198800008225501285 message you referenced updating the docs, so wasn't sure if these changes affected that work
[Reference to](#1198800008225501285 message) #1198800008225501285 [โค ](#1198800008225501285 message)@quick kernel Sorry to bother, but is there a document or something that summarizes the damage events? I'd like to update the /docs damageevents command to explain the new system.
Ah that
I... honestly don't know? You tell me:
/docs damageevents
Ugh, I despise commands, especially on mobile
I'll get back to you in a bit, when I'm back at my computer
Script failed execution due to an exception: **SyntaxError: script.js:6:180 Expected comma but found private_ident
2. LivingIncomingDamageEvent - Use this event to modify the incoming damage, cancel the attack, or apply specific damage modifiers (E.G. armour, enchantment, mob effect) via #addModifier
^
script.js:18:11 Expected ; but found :
'title': {
^
script.js:19:13 Expected ; but found :
'value': The Damage Event Sequence,
^
script.js:20:5 Expected an operand but found ,
},
^
**
the command isn't even valid smh
Script:
const description = 'The damage event sequence'
function execute()
{
replyEmbed(
{
'description': `Unsure how to use NeoForge's damage API?
Here's how it works:
1. **EntityInvulnerabilityCheckEvent** - Called for all entities (even non-living). Use this to apply additional or revoke vanilla invulnerabilities. Do not use for chance-based or resource-consuming effects
2. **LivingIncomingDamageEvent** - Use this event to modify the incoming damage, cancel the attack, or apply specific damage modifiers (E.G. armour, enchantment, mob effect) via `#addModifier`
3. **LivingShieldBlockEvent** - Use this event to change or add item-based blocking mechanics (such as custom shields). Non-item based blocking should be done in `LivingIncomingDamageEvent`
4. **LivingDamageEvent.Pre** - Use this event to apply final modifications to the final damage. ONLY USE THIS IF YOU CANNOT USE `LivingIncomingDamageEvent`
5. **LivingIncomingDamageEvent.Post** - Use this to react to an attack, regardless of whether it did actual damage or not
6. **LivingEntity#onDamageTaken** - Override this method instead of using `LivingDamageEvent.Post` if you have a custom entity that reacts to taking damage
Want more info? See the detailed writeup done for the PR: <https://gist.github.com/Tslat/0ef11e57036f7fe253268b5a64aa455e>
_Made by @quick kernel_`,
'title': {
'value': `The Damage Event Sequence`,
},
'color': 536870911,
}
);
}
damageevents
@gusty moss (561254664750891009)
Uses: 6 total: 2 prefix, 4 slash
someone forgot to escape the `
ah, I see
Unsure how to use NeoForge's damage API?
Here's how it works:
- EntityInvulnerabilityCheckEvent - Called for all entities (even non-living). Use this to apply additional or revoke vanilla invulnerabilities. Do not use for chance-based or resource-consuming effects
- LivingIncomingDamageEvent - Use this event to modify the incoming damage, cancel the attack, or apply specific damage modifiers (E.G. armour, enchantment, mob effect) via
#addModifier - LivingShieldBlockEvent - Use this event to change or add item-based blocking mechanics (such as custom shields). Non-item based blocking should be done in
LivingIncomingDamageEvent - LivingDamageEvent.Pre - Use this event to apply final modifications to the final damage. ONLY USE THIS IF YOU CANNOT USE
LivingIncomingDamageEvent - LivingIncomingDamageEvent.Post - Use this to react to an attack, regardless of whether it did actual damage or not
- LivingEntity#onDamageTaken - Override this method instead of using
LivingDamageEvent.Postif you have a custom entity that reacts to taking damage
Want more info? See the detailed writeup done for the PR: https://gist.github.com/Tslat/0ef11e57036f7fe253268b5a64aa455e
Made by @quick kernel
@sand copper Would you say it needs updating, or is it fine as is?
it's still correct
blog seems fine
ready for approval and merge if knightminer's comment is not a concern:
https://github.com/neoforged/NeoForge/pull/1324
not a concern
Question: should there be an event for changing damage source? If so, how should it be implemented? I think it has been discussed some while ago
The proposal that I have in my head for this is implementing a mutable set of tags on DamageSource which can be adjusted in-flight
initially set to the tags from the damage type
it has definitely come up multiple times yeah
I kinda wanna keep it separate to this PR/topic though
better suited as its own self-contained brainstorm/topic
yeah. that discussion is more closely aligned with the idea of multi-source damage which we talked about addressing later once these changes settled in
@sand copper Would you be able to approve this? Stable is very close now so window for pr is closing
https://github.com/neoforged/NeoForge/pull/1324
done. still need an approval from someone with write access though
yeah ik. I just wanted your eyes on it as I had a review request from you lol
now to wait till other maintainers are online
gotcha
@sand copper going to dump some more thoughts in here because I realize that trying to manage the concept of "being killed in the middle of the stack" is an absolute nightmare.
Moving Post to the bottom where I have it fixes the issue I'm triggering, but does not prevent the exact same class of issue from being triggered in Pre. There's also ILivingEntityExtension#onDamageTaken which should probably go down to where Post now is (based on its own javadocs).
For managing this error state in Pre, I think we have a couple options...
- assert
!this.deadafter the event returns. Completely forbade killing the entity inPreand require people defer it toPost, or let vanilla handle killing the entity when the stack returns.
This seems okay, because IMO something that kills the entity (i.e. dealing new damage by re-invoking hurtServer) should not happen in Pre and should be relegated upwards to the cancellable LivingIncomingDamageEvent or Post
- Instrument handling in the middle of
hurtServerto handle the case whereactuallyHurtcompletes and the entity comes outthis.dead. Vanilla does not handle this case. We can make it happen but it requires some potentially iffy patches and careful logic. Not sure if there's a usecase that needs this compared to (1).
I think this is where @quick kernel had notionally eluded to the expansion of the pipeline to include multi-damage events, but we kept that out of scope for the first pass. This whole scenario feels like we are teasing at the multi-damage-source framework again. Which feels like something where we either obliterate the Stack implementation in favor of an internalized handler of multiple damage sources, or we keep the Stack implementation and continue where you've started towards a safely-recursive pipeline.
I think I would be in favor of (1) and simply requiring that people who want to deal multiple damages do it either "always before" or "always after" the real damage dealing phase (where Pre gets fired from)
There's some more nuance to getting things correct though, mostly little semantic things that I doubt anyone cares too greatly about
i.e. we currently don't propagate back the damage value to lastHurt and it always gets the non-Pre-modified damage since actuallyHurt doesn't communicate the damage upward
We also have getPostAttackInvulnerabilityTicks operate in an invalid state if we enter into the "dealing damage during iframe timer since the new attack was bigger"
For that I need to add this.damageContainers.peek().setPostAttackInvulnerabilityTicks(this.invulnerableTime); before actuallyHurt and then this.invulnerableTime = this.damageContainers.peek().getPostAttackInvulnerabilityTicks(); afterwards
wait a minute
this.damageContainers.push(new net.neoforged.neoforge.common.damagesource.DamageContainer(source, damage));
if (net.neoforged.neoforge.common.CommonHooks.onEntityIncomingDamage(this, this.damageContainers.peek())) return false;
does this just leak the container forever?
that's the line in my comment i mentioned in my first response
Ah, yes it is
I started looking at this thing in depth and there are quite a few weirdnesses
Some I'm inclined to just throw assertions at for now
i.e. I currently have updated
public static boolean onEntityIncomingDamage(LivingEntity entity, DamageContainer container) {
Preconditions.checkArgument(!entity.isDeadOrDying(), "The LivingIncomingDamageEvent cannot be fired with a dead entity.");
var event = NeoForge.EVENT_BUS.post(new LivingIncomingDamageEvent(entity, container));
return event.isCanceled() || entity.isDeadOrDying();
}
Okay, I think I have everything in a sane place. PR updated. Don't have a description written up just yet, but you can take a look. You probably don't need my extra context to understand it