Skip to content

Commit dac2215

Browse files
committed
add bandwidth profile
1 parent 4194350 commit dac2215

File tree

9 files changed

+309
-73
lines changed

9 files changed

+309
-73
lines changed
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package me.mapacheee.extendedhorizons.bandwidth.commands;
2+
3+
import com.google.inject.Inject;
4+
import com.thewinterframework.command.CommandComponent;
5+
import me.mapacheee.extendedhorizons.shared.service.MessageService;
6+
import me.mapacheee.extendedhorizons.viewdistance.service.bandwidth.BandwidthController;
7+
import me.mapacheee.extendedhorizons.viewdistance.service.player.PlayerChunkState;
8+
import me.mapacheee.extendedhorizons.viewdistance.service.player.PlayerStateManager;
9+
import org.bukkit.command.CommandSender;
10+
import org.bukkit.entity.Player;
11+
import org.incendo.cloud.annotations.Argument;
12+
import org.incendo.cloud.annotations.Command;
13+
import org.incendo.cloud.annotations.Permission;
14+
import org.incendo.cloud.paper.util.sender.Source;
15+
16+
import java.util.UUID;
17+
18+
@CommandComponent
19+
public class BandwidthCommand {
20+
21+
private final BandwidthController bandwidthController;
22+
private final PlayerStateManager playerStateManager;
23+
private final MessageService messageService;
24+
25+
@Inject
26+
public BandwidthCommand(BandwidthController bandwidthController, PlayerStateManager playerStateManager,
27+
MessageService messageService) {
28+
this.bandwidthController = bandwidthController;
29+
this.playerStateManager = playerStateManager;
30+
this.messageService = messageService;
31+
}
32+
33+
@Command("eh|extendedhorizons|horizons|viewdistance|vd bandwidth set <player> <kbps>")
34+
@Permission("extendedhorizons.admin")
35+
public void onSet(Source source, @Argument("player") Player target, @Argument("kbps") int kbps) {
36+
CommandSender sender = source.source();
37+
38+
bandwidthController.setPlayerBandwidth(target.getUniqueId(), kbps);
39+
messageService.sendBandwidthSet(sender, target.getName(), kbps);
40+
}
41+
42+
@Command("eh|extendedhorizons|horizons|viewdistance|vd bandwidth check <player>")
43+
@Permission("extendedhorizons.admin")
44+
public void onCheck(Source source, @Argument("player") Player target) {
45+
CommandSender sender = source.source();
46+
47+
UUID id = target.getUniqueId();
48+
PlayerChunkState state = playerStateManager.get(id).orElse(null);
49+
50+
if (state == null) {
51+
messageService.sendBandwidthStateNotFound(sender);
52+
return;
53+
}
54+
55+
long tickLimit = state.getMaxBytesPerTick();
56+
long secLimit = state.getMaxBytesPerSecond();
57+
58+
boolean isCustom = tickLimit > 0;
59+
int kbps;
60+
long bytesLimit;
61+
62+
if (isCustom) {
63+
kbps = (int) (secLimit / 1024);
64+
bytesLimit = tickLimit;
65+
} else {
66+
long globalTick = bandwidthController.getDefaultMaxBytesPerTick();
67+
kbps = (int) ((globalTick * 20) / 1024);
68+
bytesLimit = globalTick;
69+
}
70+
71+
messageService.sendBandwidthCheckInfo(sender, target.getName(), isCustom, kbps, bytesLimit);
72+
}
73+
}

src/main/java/me/mapacheee/extendedhorizons/shared/config/MainConfig.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ public record BandwidthSaverConfig(
9999
@Setting("max-fake-chunks-per-tick") int maxFakeChunksPerTick,
100100
@Setting("max-bandwidth-per-player") int maxBandwidthPerPlayer,
101101
@Setting("adaptive-rate-limiting") boolean adaptiveRateLimiting,
102-
102+
@Setting("bandwidth-profiles") java.util.Map<String, Integer> bandwidthProfiles,
103103
@Setting("estimated-packet-size") int estimatedPacketSize) {
104104
}
105105
}

src/main/java/me/mapacheee/extendedhorizons/shared/config/MessageConfig.java

Lines changed: 58 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -10,65 +10,71 @@
1010
@ConfigSerializable
1111
@Configurate("messages")
1212
public record MessageConfig(
13-
String prefix,
14-
General general,
15-
ViewDistance viewDistance,
16-
List<String> stats,
17-
Errors errors,
18-
List<String> help,
19-
World world,
20-
Integrations integrations,
21-
Startup startup,
22-
Messages messages,
23-
Additional additional) {
24-
@ConfigSerializable
25-
public record General(String noPermission, String playerNotFound, String playerOnly, String configReloaded,
26-
String configError, String pluginInfo, String unknownCommand) {
27-
}
13+
String prefix,
14+
General general,
15+
ViewDistance viewDistance,
16+
List<String> stats,
17+
Errors errors,
18+
List<String> help,
19+
World world,
20+
Integrations integrations,
21+
Startup startup,
22+
Messages messages,
23+
Bandwidth bandwidth,
24+
Additional additional) {
25+
@ConfigSerializable
26+
public record General(String noPermission, String playerNotFound, String playerOnly, String configReloaded,
27+
String configError, String pluginInfo, String unknownCommand) {
28+
}
2829

29-
@ConfigSerializable
30-
public record ViewDistance(
31-
String currentDistance,
32-
String distanceChanged,
33-
String distanceSetOther,
34-
String otherCurrentDistance,
35-
String noViewData,
36-
String noViewDataOther,
37-
String reset,
38-
String maxDistanceExceeded,
39-
String minDistanceError,
40-
String serverViewDistanceError,
41-
String invalidDistance) {
42-
}
30+
@ConfigSerializable
31+
public record ViewDistance(
32+
String currentDistance,
33+
String distanceChanged,
34+
String distanceSetOther,
35+
String otherCurrentDistance,
36+
String noViewData,
37+
String noViewDataOther,
38+
String reset,
39+
String maxDistanceExceeded,
40+
String minDistanceError,
41+
String serverViewDistanceError,
42+
String invalidDistance) {
43+
}
4344

44-
@ConfigSerializable
45-
public record Errors(String databaseError, String networkError, String chunkGenerationFailed,
46-
String permissionCheckFailed, String packeteventsError) {
47-
}
45+
@ConfigSerializable
46+
public record Errors(String databaseError, String networkError, String chunkGenerationFailed,
47+
String permissionCheckFailed, String packeteventsError) {
48+
}
4849

49-
@ConfigSerializable
50-
public record World(String notFound, String usage, String configNotice, String distanceSet, String disabled,
51-
String performanceModeChanged, String maxDistanceInfo) {
52-
}
50+
@ConfigSerializable
51+
public record World(String notFound, String usage, String configNotice, String distanceSet, String disabled,
52+
String performanceModeChanged, String maxDistanceInfo) {
53+
}
5354

54-
@ConfigSerializable
55-
public record Integrations(String placeholderapiEnabled, String placeholderapiDisabled, String luckpermsEnabled,
56-
String luckpermsDisabled) {
57-
}
55+
@ConfigSerializable
56+
public record Integrations(String placeholderapiEnabled, String placeholderapiDisabled, String luckpermsEnabled,
57+
String luckpermsDisabled) {
58+
}
59+
60+
@ConfigSerializable
61+
public record Startup(String loading, String loaded, String enabled, String serverDetected,
62+
String packeteventsInitialized) {
63+
}
5864

59-
@ConfigSerializable
60-
public record Startup(String loading, String loaded, String enabled, String serverDetected,
61-
String packeteventsInitialized) {
62-
}
65+
@ConfigSerializable
66+
public record Messages(WelcomeMessage welcomeMessage) {
67+
@ConfigSerializable
68+
public record WelcomeMessage(String text) {
69+
}
70+
}
6371

64-
@ConfigSerializable
65-
public record Messages(WelcomeMessage welcomeMessage) {
6672
@ConfigSerializable
67-
public record WelcomeMessage(String text) {
73+
public record Additional(String noViewData, String distanceSetOther, String minDistanceError) {
6874
}
69-
}
7075

71-
@ConfigSerializable
72-
public record Additional(String noViewData, String distanceSetOther, String minDistanceError) {
73-
}
76+
@ConfigSerializable
77+
public record Bandwidth(String set, String stateNotFound, String checkHeader, String checkCustom,
78+
String checkCustomTick, String checkGlobal, String checkGlobalTick) {
79+
}
7480
}

src/main/java/me/mapacheee/extendedhorizons/shared/service/MessageService.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,4 +181,30 @@ public void sendWelcome(CommandSender sender, int distance) {
181181
String txt = m.messages().welcomeMessage().text().replace("{distance}", String.valueOf(distance));
182182
sendPrefixed(sender, txt);
183183
}
184+
185+
public void sendBandwidthSet(CommandSender sender, String player, int kbps) {
186+
String msg = configService.messages().bandwidth().set()
187+
.replace("{player}", player)
188+
.replace("{kbps}", String.valueOf(kbps));
189+
sendPrefixed(sender, msg);
190+
}
191+
192+
public void sendBandwidthStateNotFound(CommandSender sender) {
193+
sendPrefixed(sender, configService.messages().bandwidth().stateNotFound());
194+
}
195+
196+
public void sendBandwidthCheckInfo(CommandSender sender, String player, boolean isCustom, int kbps,
197+
long bytesLimit) {
198+
var b = configService.messages().bandwidth();
199+
200+
sendRaw(sender, b.checkHeader().replace("{player}", player));
201+
202+
if (isCustom) {
203+
sendRaw(sender, b.checkCustom().replace("{kbps}", String.valueOf(kbps)));
204+
sendRaw(sender, b.checkCustomTick().replace("{bytes}", String.valueOf(bytesLimit)));
205+
} else {
206+
sendRaw(sender, b.checkGlobal().replace("{kbps}", String.valueOf(kbps)));
207+
sendRaw(sender, b.checkGlobalTick().replace("{bytes}", String.valueOf(bytesLimit)));
208+
}
209+
}
184210
}

src/main/java/me/mapacheee/extendedhorizons/viewdistance/service/bandwidth/BandwidthController.java

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,11 @@ public class BandwidthController {
2727
private final ConfigService configService;
2828

2929
/**
30-
* Maximum bytes that can be sent per tick (50ms) to a single player.
30+
* Maximum bytes that can be sent per tick (50ms) to a single player (Default).
3131
* Defaults to 25KB (500KB/s / 20 ticks).
3232
* Updated dynamically from config.
3333
*/
34-
private volatile long maxBytesPerTick = 25000;
34+
private volatile long defaultMaxBytesPerTick = 25000;
3535

3636
@Inject
3737
public BandwidthController(PlayerStateManager playerStateManager, ConfigService configService) {
@@ -77,7 +77,13 @@ public boolean canSendData(UUID playerId, long estimatedBytes) {
7777
PlayerChunkState state = playerStateManager.getOrCreate(playerId);
7878

7979
long bytesUsedThisTick = state.getBytesThisTick();
80-
return bytesUsedThisTick + estimatedBytes < maxBytesPerTick;
80+
81+
long limit = state.getMaxBytesPerTick();
82+
if (limit <= 0) {
83+
limit = defaultMaxBytesPerTick;
84+
}
85+
86+
return bytesUsedThisTick + estimatedBytes < limit;
8187
}
8288

8389
/**
@@ -94,10 +100,15 @@ public boolean canSendDataThisSecond(UUID playerId) {
94100
}
95101

96102
long bytesSent = state.getBytesThisSecond();
97-
int maxBandwidth = configService.get().bandwidthSaver().maxBandwidthPerPlayer();
98103

99-
// maxBandwidth is in KB/s, convert to bytes
100-
return maxBandwidth <= 0 || bytesSent < (maxBandwidth * 1024L);
104+
long maxBandwidthBytes = state.getMaxBytesPerSecond();
105+
106+
if (maxBandwidthBytes <= 0) {
107+
int maxBandwidthKB = configService.get().bandwidthSaver().maxBandwidthPerPlayer();
108+
maxBandwidthBytes = maxBandwidthKB * 1024L;
109+
}
110+
111+
return maxBandwidthBytes <= 0 || bytesSent < maxBandwidthBytes;
101112
}
102113

103114
/**
@@ -122,23 +133,42 @@ public void recordDataSent(UUID playerId, long actualBytes) {
122133
}
123134

124135
/**
125-
* Gets the current maximum bytes per tick limit.
136+
* Gets the current global maximum bytes per tick limit.
126137
*
127138
* @return Maximum bytes that can be sent per tick
128139
*/
129-
public long getMaxBytesPerTick() {
130-
return maxBytesPerTick;
140+
public long getDefaultMaxBytesPerTick() {
141+
return defaultMaxBytesPerTick;
131142
}
132143

133144
/**
134-
* Updates the maximum bytes per tick based on configured bandwidth per player.
145+
* Updates the default maximum bytes per tick based on configured bandwidth per
146+
* player.
135147
*
136148
* @param bandwidthPerPlayerKB Bandwidth limit per player in KB/s
137149
*/
138150
public void updateMaxBytesPerTick(int bandwidthPerPlayerKB) {
139151
if (bandwidthPerPlayerKB > 0) {
140152
// Convert KB/s to bytes/tick (20 ticks per second)
141-
this.maxBytesPerTick = (bandwidthPerPlayerKB * 1024) / 20;
153+
this.defaultMaxBytesPerTick = (bandwidthPerPlayerKB * 1024) / 20;
154+
}
155+
}
156+
157+
/**
158+
* Sets a specific bandwidth limit for a player.
159+
*
160+
* @param playerId The player UUID
161+
* @param kilobytesPerSecond The limit in KB/s. Set to -1 to use default.
162+
*/
163+
public void setPlayerBandwidth(UUID playerId, int kilobytesPerSecond) {
164+
PlayerChunkState state = playerStateManager.getOrCreate(playerId);
165+
if (kilobytesPerSecond <= 0) {
166+
state.setMaxBytesPerTick(-1);
167+
state.setMaxBytesPerSecond(-1);
168+
} else {
169+
long bytesPerSec = kilobytesPerSecond * 1024L;
170+
state.setMaxBytesPerSecond(bytesPerSec);
171+
state.setMaxBytesPerTick(bytesPerSec / 20);
142172
}
143173
}
144174
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package me.mapacheee.extendedhorizons.viewdistance.service.bandwidth;
2+
3+
import com.google.inject.Inject;
4+
import com.thewinterframework.paper.listener.ListenerComponent;
5+
import com.thewinterframework.service.annotation.Service;
6+
import me.mapacheee.extendedhorizons.shared.service.ConfigService;
7+
import org.bukkit.event.EventHandler;
8+
import org.bukkit.event.EventPriority;
9+
import org.bukkit.event.Listener;
10+
import org.bukkit.event.player.PlayerJoinEvent;
11+
import org.bukkit.entity.Player;
12+
import org.slf4j.Logger;
13+
import org.slf4j.LoggerFactory;
14+
15+
import java.util.Map;
16+
17+
@Service
18+
@ListenerComponent
19+
public class BandwidthProfileService implements Listener {
20+
21+
private final BandwidthController bandwidthController;
22+
private final ConfigService configService;
23+
private static final Logger logger = LoggerFactory.getLogger(BandwidthProfileService.class);
24+
25+
@Inject
26+
public BandwidthProfileService(BandwidthController bandwidthController, ConfigService configService) {
27+
this.bandwidthController = bandwidthController;
28+
this.configService = configService;
29+
}
30+
31+
@EventHandler(priority = EventPriority.MONITOR)
32+
public void onPlayerJoin(PlayerJoinEvent event) {
33+
applyProfile(event.getPlayer());
34+
}
35+
36+
private void applyProfile(Player player) {
37+
Map<String, Integer> profiles = configService.get().bandwidthSaver().bandwidthProfiles();
38+
39+
if (profiles == null || profiles.isEmpty()) {
40+
return;
41+
}
42+
43+
int maxKbps = -1;
44+
45+
for (Map.Entry<String, Integer> entry : profiles.entrySet()) {
46+
String profileName = entry.getKey();
47+
int kbps = entry.getValue();
48+
String permission = "extendedhorizons.bandwidth." + profileName;
49+
50+
if (player.hasPermission(permission)) {
51+
if (kbps > maxKbps) {
52+
maxKbps = kbps;
53+
}
54+
}
55+
}
56+
57+
if (maxKbps > 0) {
58+
bandwidthController.setPlayerBandwidth(player.getUniqueId(), maxKbps);
59+
logger.info("Applied bandwidth profile to {}: {} KB/s", player.getName(), maxKbps);
60+
}
61+
}
62+
}

0 commit comments

Comments
 (0)