Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@
<dependency>
<groupId>su.nightexpress.nightcore</groupId>
<artifactId>main</artifactId>
<version>2.10.0</version>
<version>2.16.3</version>
<scope>provided</scope>
</dependency>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,11 @@ protected void onLoad() {
this.loadCrates();
this.loadUI();
this.loadDialogs();
this.plugin.runTask(task -> this.reportProblems()); // After everything is loaded.
this.plugin.runTask(() -> this.reportProblems()); // After everything is loaded.

this.addListener(new CrateListener(this.plugin, this));

this.addAsyncTask(this::playCrateEffects, 1L);
this.addTask(this::playCrateEffects, 1L);
this.addAsyncTask(this::saveCrates, Config.CRATE_SAVE_INTERVAL.get());
}

Expand Down Expand Up @@ -743,8 +743,11 @@ public void playCrateEffects() {
Location location = worldPos.toLocation();
if (location == null) return;

CrateUtils.getPlayersForEffects(location).forEach(player -> {
effect.playStep(location, particle, player);
// Dispatch onto the region/thread that owns this location before touching world/player state (Folia).
this.plugin.runTask(location, () -> {
CrateUtils.getPlayersForEffects(location).forEach(player -> {
effect.playStep(location, particle, player);
});
});
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
Expand Down Expand Up @@ -106,7 +107,8 @@ public Crate(@NotNull CratesPlugin plugin, @NotNull Path path, @NotNull String i

this.costMap = new LinkedHashMap<>();
this.rewardMap = new LinkedHashMap<>();
this.blockPositions = new HashSet<>();
// Air-block filtering on load() removes entries from a region thread per position (Folia), so this needs to be concurrent-safe.
this.blockPositions = ConcurrentHashMap.newKeySet();
this.milestones = new HashSet<>();
this.description = new ArrayList<>();
}
Expand Down Expand Up @@ -219,11 +221,21 @@ private void load(@NotNull FileConfig config) throws IllegalStateException {
this.addCost(cost);
});

this.blockPositions.addAll(config.getStringList("Block.Positions").stream().map(WorldPos::deserialize).toList());
List<WorldPos> positions = config.getStringList("Block.Positions").stream().map(WorldPos::deserialize).toList();
this.blockPositions.addAll(positions);

if (!Config.isCrateInAirBlocksAllowed()) {
this.blockPositions.removeIf(pos -> {
Block block = pos.toBlock();
return block != null && block.isEmpty();
positions.forEach(pos -> {
Location location = pos.toLocation();
if (location == null) return;

// Dispatch onto the region/thread that owns this block before reading its state (Folia).
this.plugin.runTask(location, () -> {
Block block = pos.toBlock();
if (block != null && block.isEmpty()) {
this.blockPositions.remove(pos);
}
});
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public DataManager(@NotNull CratesPlugin plugin) {

@Override
protected void onLoad() {
this.plugin.runTaskAsync(task -> this.loadData());
this.plugin.runTaskAsync(() -> this.loadData());

this.addAsyncTask(this::saveCrateDatas, Config.DATA_CRATE_DATA_SAVE_INTERVAL.get());
this.addAsyncTask(this::saveRewardLimits, Config.DATA_REWARD_LIMITS_SAVE_INTERVAL.get());
Expand Down Expand Up @@ -150,13 +150,13 @@ public GlobalCrateData getCrateDataOrCreate(@NotNull Crate crate) {
if (data != null) return data;

GlobalCrateData fresh = GlobalCrateData.create(crate);
this.plugin.runTaskAsync(task -> this.plugin.getDataHandler().insertCrateData(fresh));
this.plugin.runTaskAsync(() -> this.plugin.getDataHandler().insertCrateData(fresh));
this.crateDataMap.put(fresh.getCrateId(), fresh);
return fresh;
}

public void deleteCrateData(@NotNull Crate crate) {
this.plugin.runTaskAsync(task -> this.plugin.getDataHandler().deleteCrateData(crate));
this.plugin.runTaskAsync(() -> this.plugin.getDataHandler().deleteCrateData(crate));
this.crateDataMap.remove(crate.getId());
}

Expand All @@ -168,7 +168,7 @@ public RewardData getRewardLimitOrCreate(@NotNull Reward reward, @Nullable Playe
if (limit != null) return limit;

RewardData fresh = RewardData.create(reward, player);
this.plugin.runTaskAsync(task -> this.plugin.getDataHandler().insertRewardLimit(fresh));
this.plugin.runTaskAsync(() -> this.plugin.getDataHandler().insertRewardLimit(fresh));
this.addRewardLimit(fresh);
return fresh;
}
Expand All @@ -190,29 +190,29 @@ private void addRewardLimit(@NotNull RewardData limit) {
}

public void deleteRewardLimit(@NotNull RewardData limit) {
this.plugin.runTaskAsync(task -> this.plugin.getDataHandler().deleteRewardLimit(limit));
this.plugin.runTaskAsync(() -> this.plugin.getDataHandler().deleteRewardLimit(limit));
this.rewardLimitMap.remove(getRewardKey(limit));
}

public void deleteRewardLimits(@NotNull Crate crate) {
String crateId = crate.getId();

this.plugin.runTaskAsync(task -> this.plugin.getDataHandler().deleteRewardLimits(crate));
this.plugin.runTaskAsync(() -> this.plugin.getDataHandler().deleteRewardLimits(crate));
this.rewardLimitMap.keySet().removeIf(key -> key.crateId().equalsIgnoreCase(crateId));
}

public void deleteRewardLimits(@NotNull Reward reward) {
String crateId = reward.getCrate().getId();
String rewardId = reward.getId();

this.plugin.runTaskAsync(task -> this.plugin.getDataHandler().deleteRewardLimits(reward));
this.plugin.runTaskAsync(() -> this.plugin.getDataHandler().deleteRewardLimits(reward));
this.rewardLimitMap.keySet().removeIf(key -> key.crateId().equalsIgnoreCase(crateId) && key.rewardId().equalsIgnoreCase(rewardId));
}

public void deleteRewardLimits(@NotNull UUID playerId) {
String holder = playerId.toString();

this.plugin.runTaskAsync(task -> this.plugin.getDataHandler().deleteRewardLimits(playerId));
this.plugin.runTaskAsync(() -> this.plugin.getDataHandler().deleteRewardLimits(playerId));
this.rewardLimitMap.keySet().removeIf(key -> key.holder().equalsIgnoreCase(holder));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import su.nightexpress.nightcore.util.placeholder.Replacer;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

public class HologramManager extends AbstractManager<CratesPlugin> {

Expand All @@ -33,15 +34,16 @@ public class HologramManager extends AbstractManager<CratesPlugin> {

public HologramManager(@NotNull CratesPlugin plugin) {
super(plugin);
this.displayMap = new HashMap<>();
// Creation/rendering is dispatched per block location (Folia regions), so this map can be written concurrently.
this.displayMap = new ConcurrentHashMap<>();
}

@Override
protected void onLoad() {
if (this.detectHandler()) {
this.addListener(new HologramListener(this.plugin, this));

this.addAsyncTask(this::tickHolograms, Config.CRATE_HOLOGRAM_UPDATE_INTERVAL.get());
this.addTask(this::tickHolograms, Config.CRATE_HOLOGRAM_UPDATE_INTERVAL.get());
}
}

Expand Down Expand Up @@ -160,42 +162,50 @@ public void render(@NotNull Crate crate) {
if (group.isDisabled()) continue;

WorldPos blockPosition = group.getBlockPosition();
World world = blockPosition.getWorld();
Location location = blockPosition.toLocation();
Location dispatchLocation = blockPosition.toLocation();
if (dispatchLocation == null) continue;

if (!blockPosition.isChunkLoaded() || world == null || location == null) {
this.discard(group); // Remove all viewers and send entity destroy packet.
continue;
}

List<Player> players = new ArrayList<>(world.getPlayers());
players.removeIf(player -> {
if (CrateUtils.isInEffectRange(player, location)) return false;
// Dispatch onto the region/thread that owns this block before touching world/player state (Folia).
this.plugin.runTask(dispatchLocation, () -> this.renderGroup(group, blockPosition, text));
}
}

this.removeForViewer(player, group);
return true;
});
private void renderGroup(@NotNull FakeEntityGroup group, @NotNull WorldPos blockPosition, @NotNull List<String> text) {
World world = blockPosition.getWorld();
Location location = blockPosition.toLocation();

if (players.isEmpty()) {
this.discard(group); // Remove all viewers and send entity destroy packet.
continue;
}
if (!blockPosition.isChunkLoaded() || world == null || location == null) {
this.discard(group); // Remove all viewers and send entity destroy packet.
return;
}

players.forEach(player -> {
boolean needSpawn = !group.isViewer(player);
List<Player> players = new ArrayList<>(world.getPlayers());
players.removeIf(player -> {
if (CrateUtils.isInEffectRange(player, location)) return false;

List<String> hologramText = Replacer.create().replacePlaceholderAPI(player).apply(text);
List<FakeEntity> holograms = group.getEntities();
for (int index = 0; index < holograms.size(); index++) {
// Fix for fake entity's text not being updated/replaced when text size is less than holograms amount, so force it to empty string.
String line = index >= hologramText.size() ? "" : hologramText.get(index);
FakeEntity entity = holograms.get(index);
this.handler.sendHologramPackets(player, entity, needSpawn, line);
}
this.removeForViewer(player, group);
return true;
});

group.addViewer(player);
});
if (players.isEmpty()) {
this.discard(group); // Remove all viewers and send entity destroy packet.
return;
}

players.forEach(player -> {
boolean needSpawn = !group.isViewer(player);

List<String> hologramText = Replacer.create().replacePlaceholderAPI(player).apply(text);
List<FakeEntity> holograms = group.getEntities();
for (int index = 0; index < holograms.size(); index++) {
// Fix for fake entity's text not being updated/replaced when text size is less than holograms amount, so force it to empty string.
String line = index >= hologramText.size() ? "" : hologramText.get(index);
FakeEntity entity = holograms.get(index);
this.handler.sendHologramPackets(player, entity, needSpawn, line);
}

group.addViewer(player);
});
}

private void createIfAbsent(@NotNull Crate crate) {
Expand All @@ -206,28 +216,34 @@ private void createIfAbsent(@NotNull Crate crate) {
if (originText.isEmpty()) return;

FakeDisplay display = new FakeDisplay();
// Put it before dispatching, so the containsKey guard above prevents recreation while groups populate async.
this.displayMap.put(crate.getId(), display);

double yOffset = crate.getHologramYOffset() + 0.2;
double lineGap = Config.CRATE_HOLOGRAM_LINE_GAP.get();

crate.getBlockPositions().forEach(blockPos -> {
Block block = blockPos.toBlock();
if (block == null) return;
Location blockLocation = blockPos.toLocation();
if (blockLocation == null) return;

double height = block.getBoundingBox().getHeight() / 2D + yOffset;
// Dispatch onto the region/thread that owns this block before touching world state (Folia).
this.plugin.runTask(blockLocation, () -> {
Block block = blockPos.toBlock();
if (block == null) return;

// Allocate ID values for our fake entities, so there is no clash with new server entities.
double height = block.getBoundingBox().getHeight() / 2D + yOffset;

FakeEntityGroup group = display.getGroupOrCreate(blockPos);
// Allocate ID values for our fake entities, so there is no clash with new server entities.

for (int index = 0; index < originText.size(); index++) {
double gap = lineGap * index;
FakeEntityGroup group = display.getGroupOrCreate(blockPos);

Location location = LocationUtil.setCenter3D(block.getLocation()).add(0, height + gap, 0);
group.addEntity(FakeEntity.create(location));
}
});
for (int index = 0; index < originText.size(); index++) {
double gap = lineGap * index;

this.displayMap.put(crate.getId(), display);
Location location = LocationUtil.setCenter3D(block.getLocation()).add(0, height + gap, 0);
group.addEntity(FakeEntity.create(location));
}
});
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,18 @@
import org.jetbrains.annotations.Nullable;
import su.nightexpress.excellentcrates.util.pos.WorldPos;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

public class FakeDisplay {

private final Map<WorldPos, FakeEntityGroup> entityGroups;

public FakeDisplay() {
this.entityGroups = new HashMap<>();
// Groups for different block placements are created/rendered on different region threads (Folia).
this.entityGroups = new ConcurrentHashMap<>();
}

@NotNull
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ protected void onLoad() {
this.loadCost();
this.loadKeys();
this.loadDialogs();
this.plugin.runTask(task -> this.reportProblems()); // When everything is loaded.
this.plugin.runTask(() -> this.reportProblems()); // When everything is loaded.

this.addListener(new KeyListener(this.plugin, this));
this.addAsyncTask(this::saveKeys, Config.CRATE_SAVE_INTERVAL.get()); // TODO Config
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,8 @@ public Opening getOpening(@NotNull Player player) {
}

public void tickOpenings() {
this.getOpenings().forEach(Opening::tick);
// Each opening owns a player's inventory/menu state, so tick it on that player's own thread (Folia).
this.getOpenings().forEach(opening -> this.plugin.runTask(opening.getPlayer(), opening::tick));
}

public boolean isOpening(@NotNull Player player) {
Expand Down
3 changes: 2 additions & 1 deletion src/main/resources/plugin.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ softdepend:
- PlaceholderAPI
- ProtocolLib
- packetevents
api-version: 1.21
api-version: 1.21
folia-supported: true