Respect the spawn area health override in the monster attribute system - #958
Open
nolt wants to merge 1 commit into
Open
Respect the spawn area health override in the monster attribute system#958nolt wants to merge 1 commit into
nolt wants to merge 1 commit into
Conversation
…te system An npc whose spawn area defines a MaximumHealthOverride starts with that health (AttackableNpcBase.Initialize), but MonsterAttributeHolder kept resolving Stats.MaximumHealth from the monster definition. Every percentage-based readout therefore divided the real health by an unrelated maximum. Most visible in Blood Castle: the castle gate and the statue of saint have 150k - 650k health while their definitions say 5 million, so the health status byte of the extended hit packet came out as 8 - 33 of 250 and both showed a nearly empty bar at full health. Summoned monsters are affected the other way around - their override exceeds the definition value, so the ratio is greater than 1 and the byte cast wraps around. Returning the override from GetValueOfAttribute keeps the per-definition attribute cache untouched, so ApplyChanges stays valid, and leaves the per-instance composable attributes (MonsterAttributeScaler, castle siege npcs) in charge, because they are resolved first.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
An npc whose
MonsterSpawnAreadefines aMaximumHealthOverrideis spawned with that health,but
MonsterAttributeHolderkept resolvingStats.MaximumHealthfrom theMonsterDefinition.Everything which reports health as a percentage therefore divided the real health by an
unrelated maximum. This change returns the override from
GetValueOfAttribute, so current andmaximum health of such an npc are consistent again.
The symptom
In Blood Castle, the Castle Gate and the Statue of Saint show a health bar which is only a
couple of segments long from the very moment they become attackable, while a regular monster at
full health shows a full bar. Damaging them barely moves the bar until they die.
Regular monsters are unaffected, and the client side math is correct - the client simply renders
the ratio the server sends.
Stock Season 6 data:
MaximumHealthRoot cause
AttackableNpcBase.Initialize(src/GameLogic/NPC/AttackableNpcBase.cs:168) takes thecurrent health from the spawn area:
MonsterAttributeHolder.GetValueOfAttributeresolvesStats.MaximumHealthfrom theper-definition stat dictionary and knows nothing about the override - only
Stats.CurrentHealthis mapped toHealth.ShowHitExtendedPlugIn.CalcStatStatus(
src/GameServer/RemoteView/World/ShowHitExtendedPlugIn.cs:67) sends(byte)Math.Round(current / maximum * 250), which for the BC8 gate is650 000 / 5 000 000 * 250 = 33.Summoned monsters are affected the other way around:
PlayerSummon.CreateAsync(
src/GameLogic/PlayerSummon.cs:62-63) sets the override tomax + max * SummonedMonsterHealthIncrease, so the ratio becomes greater than 1 and the(byte)cast wraps around (master level 1 gives 401 -> 145). This is not observable in the original
client, which puts summons in scope as players and gates both health bars on the monster kind,
but the value sent was wrong regardless.
The fix
One lookup in
MonsterAttributeHolder.GetValueOfAttribute, placed after the per-instance_attributesdictionary and before the per-definition_statAttributes:Why here:
ApplyChanges(configuration reload on arunning server) keeps working and no monster definition gets polluted with instance state.
MonsterAttributeScalerand
CastleSiegeAttackableNpc, which compose elements ontoStats.MaximumHealth, keepprecedence and their behaviour is unchanged.
Initializebecomes self-consistent: whatever it used as the starting health is also what theattribute system reports as the maximum.
Alternatives considered and rejected: writing the override into the stat dictionary (would
either mutate the shared cache or force a per-instance copy for every spawned npc), and mapping
it in
StatMapping(that dictionary is only consulted after_statAttributes, which alreadycontains
MaximumHealthfrom the definition).Testing
(PostgreSQL, default configuration, three game servers).
/startbc, entered with anInvisibility Cloak +8, and the Castle Gate and Statue of Saint now show a full health bar when
they become attackable, decreasing proportionally to the damage dealt. Regular monsters on the
map are unchanged.
MaximumHealthelement takes precedence by design), and the summon path, which cannot beobserved in the original client because summons never get a health bar there.
MonsterAttributeHolder.