From 4b29f75a221ecea4560a09b671fd26885b09fb0f Mon Sep 17 00:00:00 2001 From: Denis Date: Sun, 23 Aug 2026 14:52:16 +0400 Subject: [PATCH 1/5] fix(bcard): the four missing subtypes of type 104 BCard.dat declares ten subtypes for type 104; the enum had six, and the pair it marked "SubType 4 didn't exist" is declared like every other one. Same shape as the note that was on type 94's subtype 41. 41 "Increases the attack power of your NosMate by %s%%." Two of the additions are not negations, which is why they are named for what they say rather than with a Negated suffix: 12 the reflect sentence of 11 WITHOUT its 50%% max-HP cap 52 "Allies within %s space(s) suffer [%s] every 1.5 seconds" - 51 is the same sentence about opponents, so a handler treating 52 as 51 negated would hang a hostile aura on its own party The names of 12 and 51 are left as they are so nothing downstream breaks; what they mean is written on the enum. --- .../Enumerations/Buff/AdditionalTypes.cs | 33 +++++++++++++++++-- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/src/NosCore.Data/Enumerations/Buff/AdditionalTypes.cs b/src/NosCore.Data/Enumerations/Buff/AdditionalTypes.cs index c540af286..d15a32bbf 100644 --- a/src/NosCore.Data/Enumerations/Buff/AdditionalTypes.cs +++ b/src/NosCore.Data/Enumerations/Buff/AdditionalTypes.cs @@ -1,4 +1,4 @@ -// __ _ __ __ ___ __ ___ ___ +// __ _ __ __ ___ __ ___ ___ // | \| |/__\ /' _/ / _//__\| _ \ __| // | | ' | \/ |`._`.| \_| \/ | v / _| // |_|\__|\__/ |___/ \__/\__/|_|_\___| @@ -1292,14 +1292,41 @@ public enum Type103 : byte DecreaseConcentration = 52 } + /// + /// Type 104. Four of the ten subtypes were missing, and the fourth pair was marked as not + /// existing when the files declare it. + /// + /// + /// What BCard.dat says, resolved through the client's language files: + /// + /// 11 "When you're defending, there is a %s%% chance of %s%% of the damage being + /// reflected at the enemy (up to 50%% of the max. HP of the player with the buff)." + /// 12 the same sentence WITHOUT the cap - not a negation of 11 + /// 21 "All opponents within %s space(s) take %s damage every 1.5 seconds." + /// 31 "When you're defending, there is a %s%% chance of summoning a(n) %s." + /// 32 the same, summoning two + /// 41 "Increases the attack power of your NosMate by %s%%." + /// 51 "All opponents within %s space(s) suffer %s every 1.5 seconds." + /// 52 "Allies within %s space(s) suffer [%s] every 1.5 seconds." + /// + /// 51 and 52 are not a pair either: one names enemies and the other allies, so a handler + /// that treats 52 as the negation of 51 would put a hostile aura on its own party. + /// + /// The names of 12 and of the existing 51 are left alone so nothing downstream breaks; + /// what they actually mean is written above. + /// public enum Type104 : byte { ReflectOnDeff = 11, ReflectOnDeffNegated = 12, AreaDamageEachSecond = 21, + AreaDamageEachSecondNegated = 22, SummonMonsterOnDef = 31, - //SubType 4 didn't exist. - AreaBuffEachSecond = 51 + SummonTwoMonstersOnDef = 32, + MateAttackIncreased = 41, + MateAttackDecreased = 42, + AreaBuffEachSecond = 51, + AreaBuffOnAlliesEachSecond = 52 } public enum Type107 : byte From 2f005ae58f39cf42f5f93d2ec7166e0d61aa1d0c Mon Sep 17 00:00:00 2001 From: Denis Date: Sun, 23 Aug 2026 23:33:45 +0400 Subject: [PATCH 2/5] fix(bcard): type 11's fifth pair is missing too Same class as the type 104 members in this PR: the files declare it and the enum stops before it. 11/51-52 'Final damage from incoming critical hits is reduced by %s%% per critical hit (max. %s hits).' Not a negation of anything above it. The first four pairs of type 11 are the parry - a chance that damage changes - while this one is priced per critical in a RUN, which is a different shape entirely and the reason it needs its own name rather than being folded into ChanceAllDecreased. --- src/NosCore.Data/Enumerations/Buff/AdditionalTypes.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/NosCore.Data/Enumerations/Buff/AdditionalTypes.cs b/src/NosCore.Data/Enumerations/Buff/AdditionalTypes.cs index d15a32bbf..d7902a914 100644 --- a/src/NosCore.Data/Enumerations/Buff/AdditionalTypes.cs +++ b/src/NosCore.Data/Enumerations/Buff/AdditionalTypes.cs @@ -110,7 +110,15 @@ public enum Block : byte ChanceRangedIncreased = 31, ChanceRangedDecreased = 32, ChanceMagicalIncreased = 41, - ChanceMagicalDecreased = 42 + ChanceMagicalDecreased = 42, + + /// + /// "Final damage from incoming critical hits is reduced by %s%% per critical hit + /// (max. %s hits)." Not a negation of anything above it: the first four pairs are the + /// parry, which is a chance, while this is priced per critical in a RUN. + /// + CriticalDamageReducedPerHit = 51, + CriticalDamageReducedPerHitNegated = 52 } public enum BossMonstersSkill : byte From e6c0bcf7e7e6608bf0c9ec86f0057ad0276ef4c0 Mon Sep 17 00:00:00 2001 From: Denis Date: Sun, 23 Aug 2026 23:52:46 +0400 Subject: [PATCH 3/5] fix(bcard): say what the files say about the X2 slots Review on #2288 flagged three names as asserting an inversion the source does not carry. Checked against BCard.dat resolved through the language files: 104/21 and 104/22 the same sentence, word for word 104/41 and 104/42 the same sentence, word for word 11/51 and 11/52 the same sentence, word for word So the review is right that nothing in the files says these effects invert. MateAttackDecreased claimed a decrease outright and is renamed to MateAttackIncreasedNegated, the base+Negated form the rest of the file uses. The other two keep Negated, which upstream already uses for identical-text X2 slots (Type107.MagicArmourFlatNegated is exactly this case, and there are 154 Negated members): here it marks the slot, not a claim about the effect. That is now written in the remarks instead of being left for the reader to infer, along with the fact that no skill, card or item declares 22, 42 or 11/52 today. Also: the summary said four of Type104's subtypes were missing when five were added (22, 32, 41, 42, 52), and a UTF-8 BOM had crept into the file, showing up as a whole-line diff against master. Both fixed. Co-Authored-By: Claude Opus 5 --- .../Enumerations/Buff/AdditionalTypes.cs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/NosCore.Data/Enumerations/Buff/AdditionalTypes.cs b/src/NosCore.Data/Enumerations/Buff/AdditionalTypes.cs index d7902a914..899ae354a 100644 --- a/src/NosCore.Data/Enumerations/Buff/AdditionalTypes.cs +++ b/src/NosCore.Data/Enumerations/Buff/AdditionalTypes.cs @@ -1,4 +1,4 @@ -// __ _ __ __ ___ __ ___ ___ +// __ _ __ __ ___ __ ___ ___ // | \| |/__\ /' _/ / _//__\| _ \ __| // | | ' | \/ |`._`.| \_| \/ | v / _| // |_|\__|\__/ |___/ \__/\__/|_|_\___| @@ -116,6 +116,9 @@ public enum Block : byte /// "Final damage from incoming critical hits is reduced by %s%% per critical hit /// (max. %s hits)." Not a negation of anything above it: the first four pairs are the /// parry, which is a chance, while this is priced per critical in a RUN. + /// + /// 52 carries that same sentence word for word, so the suffix marks the X2 slot and + /// not an inversion. Nothing in the game data declares it today. /// CriticalDamageReducedPerHit = 51, CriticalDamageReducedPerHitNegated = 52 @@ -1301,7 +1304,7 @@ public enum Type103 : byte } /// - /// Type 104. Four of the ten subtypes were missing, and the fourth pair was marked as not + /// Type 104. Five of the ten subtypes were missing, and the fourth pair was marked as not /// existing when the files declare it. /// /// @@ -1311,15 +1314,23 @@ public enum Type103 : byte /// reflected at the enemy (up to 50%% of the max. HP of the player with the buff)." /// 12 the same sentence WITHOUT the cap - not a negation of 11 /// 21 "All opponents within %s space(s) take %s damage every 1.5 seconds." + /// 22 the same sentence, word for word /// 31 "When you're defending, there is a %s%% chance of summoning a(n) %s." /// 32 the same, summoning two /// 41 "Increases the attack power of your NosMate by %s%%." + /// 42 the same sentence, word for word /// 51 "All opponents within %s space(s) suffer %s every 1.5 seconds." /// 52 "Allies within %s space(s) suffer [%s] every 1.5 seconds." /// /// 51 and 52 are not a pair either: one names enemies and the other allies, so a handler /// that treats 52 as the negation of 51 would put a hostile aura on its own party. /// + /// For 22 and 42 the client repeats the X1 sentence unchanged, so the files give the + /// second slot no meaning of its own. The `Negated` suffix here means "the X2 slot", + /// which is what it already means for the identical-text pairs upstream names that way + /// (see Type107.MagicArmourFlatNegated) - it is not a claim that the effect inverts. + /// Nothing in the game data declares 22 or 42 today. + /// /// The names of 12 and of the existing 51 are left alone so nothing downstream breaks; /// what they actually mean is written above. /// @@ -1332,7 +1343,7 @@ public enum Type104 : byte SummonMonsterOnDef = 31, SummonTwoMonstersOnDef = 32, MateAttackIncreased = 41, - MateAttackDecreased = 42, + MateAttackIncreasedNegated = 42, AreaBuffEachSecond = 51, AreaBuffOnAlliesEachSecond = 52 } From 733448edd56262e6d542b08cce062076649ff9ab Mon Sep 17 00:00:00 2001 From: Denis Date: Mon, 24 Aug 2026 00:04:35 +0400 Subject: [PATCH 4/5] chore(bcard): cut the comments down to the trap Only two things here are not obvious from the names: 104/52 targets allies rather than enemies, and 11/51 prices per critical in a run instead of rolling a chance. One line each; the rest is gone. Co-Authored-By: Claude Opus 5 --- .../Enumerations/Buff/AdditionalTypes.cs | 37 +------------------ 1 file changed, 2 insertions(+), 35 deletions(-) diff --git a/src/NosCore.Data/Enumerations/Buff/AdditionalTypes.cs b/src/NosCore.Data/Enumerations/Buff/AdditionalTypes.cs index 899ae354a..683f3849c 100644 --- a/src/NosCore.Data/Enumerations/Buff/AdditionalTypes.cs +++ b/src/NosCore.Data/Enumerations/Buff/AdditionalTypes.cs @@ -113,12 +113,7 @@ public enum Block : byte ChanceMagicalDecreased = 42, /// - /// "Final damage from incoming critical hits is reduced by %s%% per critical hit - /// (max. %s hits)." Not a negation of anything above it: the first four pairs are the - /// parry, which is a chance, while this is priced per critical in a RUN. - /// - /// 52 carries that same sentence word for word, so the suffix marks the X2 slot and - /// not an inversion. Nothing in the game data declares it today. + /// Per-critical reduction over a run of hits, not a chance like 11-42. /// CriticalDamageReducedPerHit = 51, CriticalDamageReducedPerHitNegated = 52 @@ -1304,36 +1299,8 @@ public enum Type103 : byte } /// - /// Type 104. Five of the ten subtypes were missing, and the fourth pair was marked as not - /// existing when the files declare it. + /// 52 targets allies, not enemies - it is not the negation of 51. /// - /// - /// What BCard.dat says, resolved through the client's language files: - /// - /// 11 "When you're defending, there is a %s%% chance of %s%% of the damage being - /// reflected at the enemy (up to 50%% of the max. HP of the player with the buff)." - /// 12 the same sentence WITHOUT the cap - not a negation of 11 - /// 21 "All opponents within %s space(s) take %s damage every 1.5 seconds." - /// 22 the same sentence, word for word - /// 31 "When you're defending, there is a %s%% chance of summoning a(n) %s." - /// 32 the same, summoning two - /// 41 "Increases the attack power of your NosMate by %s%%." - /// 42 the same sentence, word for word - /// 51 "All opponents within %s space(s) suffer %s every 1.5 seconds." - /// 52 "Allies within %s space(s) suffer [%s] every 1.5 seconds." - /// - /// 51 and 52 are not a pair either: one names enemies and the other allies, so a handler - /// that treats 52 as the negation of 51 would put a hostile aura on its own party. - /// - /// For 22 and 42 the client repeats the X1 sentence unchanged, so the files give the - /// second slot no meaning of its own. The `Negated` suffix here means "the X2 slot", - /// which is what it already means for the identical-text pairs upstream names that way - /// (see Type107.MagicArmourFlatNegated) - it is not a claim that the effect inverts. - /// Nothing in the game data declares 22 or 42 today. - /// - /// The names of 12 and of the existing 51 are left alone so nothing downstream breaks; - /// what they actually mean is written above. - /// public enum Type104 : byte { ReflectOnDeff = 11, From a107acd5a1c452b0ce442963a16190d324b6dad3 Mon Sep 17 00:00:00 2001 From: Denis Date: Mon, 24 Aug 2026 07:38:26 +0400 Subject: [PATCH 5/5] fix(bcard): type 19's 31-32 are speed while hidden, not a "set movement" BCard.dat, type 19 Move: 11/12 Movement impossible (same text both slots) 21/22 Movement speed is in/decreased by %s%% 31/32 Your movement speed is in/decreased by %s while you are hidden 41/42 Movement speed is in/decreased by %s 51/52 Your speed is temporarily maximised (same text both slots) `SetMovement` and `SetMovementNegated` say nothing about a percentage, a flat value, or being hidden, and 31-32 is the one pair that is conditional. Renamed to SpeedWhileHiddenIncreased / Decreased; nothing referenced the old names. Documented the trap the names still carry: 21-22 is a percentage and 41-42 is flat, and MoveSpeedIncreased / MovementSpeedIncreased differ by one syllable. The placeholder in SpeedCalculationService is why this matters. It read bonusSpeed = GetBuff(CardType.Move, Move.SetMovementNegated)[0] which is subtype 32 - "movement speed is DECREASED by %s while you are hidden". Uncommented as it stood, it would have applied a conditional decrease as a general speed bonus. Replaced with a note saying which pair is which; reading them is a separate change. --- .../Enumerations/Buff/AdditionalTypes.cs | 24 +++++++++++++++++-- .../SpeedCalculationService.cs | 7 +++++- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/NosCore.Data/Enumerations/Buff/AdditionalTypes.cs b/src/NosCore.Data/Enumerations/Buff/AdditionalTypes.cs index 683f3849c..f37b0fc7a 100644 --- a/src/NosCore.Data/Enumerations/Buff/AdditionalTypes.cs +++ b/src/NosCore.Data/Enumerations/Buff/AdditionalTypes.cs @@ -637,16 +637,36 @@ public enum Morale : byte IgnoreEnemyMoraleNegated = 52 } + /// + /// 21-22 are a percentage and 41-42 are flat, which the names do not say. 11-12 and 51-52 + /// carry the same text as each other in the files, so the X2 slot has no meaning of its own. + /// public enum Move : byte { MovementImpossible = 11, MovementImpossibleNegated = 12, + + /// Movement speed is increased by %s%%. MoveSpeedIncreased = 21, + + /// Movement speed is decreased by %s%%. MoveSpeedDecreased = 22, - SetMovement = 31, - SetMovementNegated = 32, + + /// + /// Your movement speed is increased by %s while you are hidden. Not a "set + /// movement" of any kind: it is a flat bonus that only applies while invisible. + /// + SpeedWhileHiddenIncreased = 31, + + /// Your movement speed is decreased by %s while you are hidden. + SpeedWhileHiddenDecreased = 32, + + /// Movement speed is increased by %s. This is the unconditional flat one. MovementSpeedIncreased = 41, + + /// Movement speed is decreased by %s. MovementSpeedDecreased = 42, + TempMaximized = 51, TempMaximizedNegated = 52 } diff --git a/src/NosCore.GameObject/Services/SpeedCalculationService/SpeedCalculationService.cs b/src/NosCore.GameObject/Services/SpeedCalculationService/SpeedCalculationService.cs index 6e1237e81..5df15dfaf 100644 --- a/src/NosCore.GameObject/Services/SpeedCalculationService/SpeedCalculationService.cs +++ b/src/NosCore.GameObject/Services/SpeedCalculationService/SpeedCalculationService.cs @@ -18,7 +18,12 @@ private byte CalculateSpeed(IAliveEntity aliveEntity, byte defaultSpeed) // return 0; // } - var bonusSpeed = 0; /*(byte)GetBuff(CardType.Move, (byte)AdditionalTypes.Move.SetMovementNegated)[0];*/ + // The movement BCards are still not read. The placeholder that used to sit + // here reached for subtype 32, which the files say is "movement speed is + // DECREASED by %s while you are hidden": wrong slot and wrong condition for a + // general speed bonus. The unconditional flat pair is 41-42, and 21-22 is the + // percentage. + var bonusSpeed = 0; if (defaultSpeed + bonusSpeed > 59) { return 59;