Skip to content

Commit 2699d54

Browse files
committed
Fix ExploitFix.Combat.XPCeiling.Damage_Limit being ignored
The combat XP damage ceiling read a config key that never shipped in experience.yml, so the ceiling was always the hardcoded default of 100. It now reads the Damage_Limit key that experience.yml actually contains. Fixes #5309
1 parent e535d04 commit 2699d54

3 files changed

Lines changed: 102 additions & 1 deletion

File tree

Changelog.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ Version 2.2.055
55
Fixed Super Breaker and Tree Feller mishandling durability on items with a custom max_damage component (See notes)
66
Fixed Tree Feller ignoring durability damage changes made by other plugins via PlayerItemDamageEvent
77
Fixed Acrobatics Dodge exploit prevention never limiting Dodge XP from a single mob (See notes)
8+
Fixed 'ExploitFix.Combat.XPCeiling.Damage_Limit' in experience.yml being ignored (See notes)
89
Improved performance when players gain skill XP (See notes)
910
Improved performance when taking smelted items out of furnaces (See notes)
1011
Added 'ExploitFix.AcrobaticsDodgeXpFarming' to experience.yml (See notes)
@@ -23,6 +24,7 @@ Version 2.2.055
2324
Cripple now plays a single sound which can be adjusted or disabled with the CRIPPLE entry in sounds.yml.
2425
Items with a custom maximum durability (set through the max_damage item component by data packs or item plugins) previously could make super abilities stop working or appear to restore durability; ability durability loss now always uses the item's own maximum.
2526
Dodge XP farming prevention now has its own 'ExploitFix.AcrobaticsDodgeXpFarming' setting in experience.yml; it is enabled by default and existing config files do not need to be updated. A single mob stops handing out Dodge XP after a handful of dodges, and the limit clears once that mob has gone about a minute without being dodged. 'ExploitFix.Acrobatics' continues to control the Roll anti-exploit checks.
27+
'ExploitFix.Combat.XPCeiling.Damage_Limit' in experience.yml now works; before this fix the combat XP damage ceiling was always 100 no matter what the config said. Servers that customized this value will see it take effect after updating, no config changes are needed.
2628

2729
Version 2.2.054
2830
Added compatibility for new blocks and items from Chaos Cubed (Minecraft 26.2) to mcMMO

src/main/java/com/gmail/nossr50/config/experience/ExperienceConfig.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -473,7 +473,7 @@ public boolean useCombatHPCeiling() {
473473
}
474474

475475
public int getCombatHPCeiling() {
476-
return config.getInt("ExploitFix.Combat.XPCeiling.HP_Modifier_Limit", 100);
476+
return config.getInt("ExploitFix.Combat.XPCeiling.Damage_Limit", 100);
477477
}
478478

479479
public boolean isExperienceBarsEnabled() {
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package com.gmail.nossr50.config.experience;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
import static org.mockito.Answers.CALLS_REAL_METHODS;
5+
import static org.mockito.Mockito.mock;
6+
7+
import com.gmail.nossr50.config.BukkitConfig;
8+
import java.io.InputStream;
9+
import java.io.InputStreamReader;
10+
import java.lang.reflect.Field;
11+
import java.nio.charset.StandardCharsets;
12+
import org.bukkit.configuration.file.YamlConfiguration;
13+
import org.junit.jupiter.api.Nested;
14+
import org.junit.jupiter.api.Test;
15+
import org.junit.jupiter.params.ParameterizedTest;
16+
import org.junit.jupiter.params.provider.ValueSource;
17+
18+
/**
19+
* Unit tests for {@link ExperienceConfig} combat XP ceiling settings. These guard against the
20+
* getter and the shipped experience.yml disagreeing on the config key, which silently ignores
21+
* whatever value the server admin sets (#5309).
22+
*/
23+
class ExperienceConfigTest {
24+
25+
/**
26+
* Creates an {@link ExperienceConfig} whose backing config is the given YAML, without running
27+
* the file-loading constructor. The getters under test only touch the backing config.
28+
*/
29+
private static ExperienceConfig experienceConfigBackedBy(final YamlConfiguration yaml) {
30+
final ExperienceConfig experienceConfig = mock(ExperienceConfig.class, CALLS_REAL_METHODS);
31+
try {
32+
final Field configField = BukkitConfig.class.getDeclaredField("config");
33+
configField.setAccessible(true);
34+
configField.set(experienceConfig, yaml);
35+
} catch (ReflectiveOperationException e) {
36+
throw new IllegalStateException("Unable to inject backing config", e);
37+
}
38+
return experienceConfig;
39+
}
40+
41+
/** Loads the experience.yml bundled with the plugin jar. */
42+
private static YamlConfiguration shippedExperienceYaml() {
43+
final InputStream resource =
44+
ExperienceConfig.class.getClassLoader().getResourceAsStream("experience.yml");
45+
assertThat(resource).as("bundled experience.yml resource").isNotNull();
46+
return YamlConfiguration.loadConfiguration(
47+
new InputStreamReader(resource, StandardCharsets.UTF_8));
48+
}
49+
50+
@Nested
51+
class CombatXpCeiling {
52+
53+
@Test
54+
void getCombatHPCeilingShouldReturnShippedDefaultWhenConfigIsUnchanged() {
55+
// Given - the experience.yml shipped with the plugin, with no admin edits
56+
final YamlConfiguration shippedYaml = shippedExperienceYaml();
57+
final ExperienceConfig experienceConfig = experienceConfigBackedBy(shippedYaml);
58+
59+
// Then - the shipped file defines the ceiling key the plugin documents
60+
assertThat(shippedYaml.isSet("ExploitFix.Combat.XPCeiling.Damage_Limit"))
61+
.as("shipped experience.yml should contain the documented ceiling key")
62+
.isTrue();
63+
64+
// And - the getter reports the shipped value
65+
assertThat(experienceConfig.getCombatHPCeiling()).isEqualTo(100);
66+
}
67+
68+
@ParameterizedTest(name = "customized Damage_Limit {0} should be honored")
69+
@ValueSource(ints = {1, 50, 250, 5000})
70+
void getCombatHPCeilingShouldHonorCustomizedDamageLimit(final int customLimit) {
71+
/*
72+
* Intent: this is the #5309 regression guard. The getter used to read
73+
* 'HP_Modifier_Limit', a key that never shipped, so any admin edit to 'Damage_Limit'
74+
* was silently ignored and the ceiling stayed at the hardcoded 100.
75+
*/
76+
77+
// Given - a server admin customized the ceiling in their experience.yml
78+
final YamlConfiguration customizedYaml = shippedExperienceYaml();
79+
customizedYaml.set("ExploitFix.Combat.XPCeiling.Damage_Limit", customLimit);
80+
final ExperienceConfig experienceConfig = experienceConfigBackedBy(customizedYaml);
81+
82+
// When - the ceiling is read
83+
final int ceiling = experienceConfig.getCombatHPCeiling();
84+
85+
// Then - the customized value takes effect instead of the hardcoded default
86+
assertThat(ceiling).isEqualTo(customLimit);
87+
}
88+
89+
@Test
90+
void getCombatHPCeilingShouldFallBackTo100WhenKeyIsMissing() {
91+
// Given - a config with no XPCeiling section at all (e.g. a stripped-down user file)
92+
final ExperienceConfig experienceConfig =
93+
experienceConfigBackedBy(new YamlConfiguration());
94+
95+
// Then - the getter falls back to the historical default
96+
assertThat(experienceConfig.getCombatHPCeiling()).isEqualTo(100);
97+
}
98+
}
99+
}

0 commit comments

Comments
 (0)