Merge branch 'feature/arena'

This commit is contained in:
MineTec committed 2026-10-15 23:40:25 +02:00
commit fb88856925
39 files changed
+3400 -7

No files matched your search

@@ -0,0 +1,17 @@
package eu.mhsl.craftattack.spawn.craftattack.appliances.metaGameplay.arenaPortal;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceCommand;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
class ArenaCommand extends ApplianceCommand.PlayerChecked<ArenaPortal> {
public ArenaCommand() {
super("arena");
}
@Override
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
this.getAppliance().sendToArena(this.getPlayer());
}
}
@@ -0,0 +1,122 @@
package eu.mhsl.craftattack.spawn.craftattack.appliances.metaGameplay.arenaPortal;
import eu.mhsl.craftattack.spawn.core.Main;
import eu.mhsl.craftattack.spawn.core.api.client.ReqResp;
import eu.mhsl.craftattack.spawn.core.appliance.Appliance;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceCommand;
import eu.mhsl.craftattack.spawn.core.config.ConfigUtil;
import eu.mhsl.craftattack.spawn.core.config.Configuration;
import eu.mhsl.craftattack.spawn.core.util.entity.DisplayVillager;
import eu.mhsl.craftattack.spawn.core.util.listener.DismissInventoryOpenFromHolder;
import eu.mhsl.craftattack.spawn.core.util.listener.PlayerInteractAtEntityEventListener;
import eu.mhsl.craftattack.spawn.core.util.server.PluginMessage;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Player;
import org.bukkit.entity.Villager;
import org.bukkit.event.Listener;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import java.util.Base64;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.logging.Level;
/**
* Entrance to the arena server: sends a copy of the player's inventory there and connects the player.
* The inventory on this server is never changed.
*/
public class ArenaPortal extends Appliance {
private static final Component PREFIX = Component.text("[Arena] ", NamedTextColor.GOLD);
private DisplayVillager.ConfigBound villager;
private final Set<UUID> pendingTransfers = new HashSet<>();
public ArenaPortal() {
super("arenaPortal");
}
@Override
public void onEnable() {
if(this.localConfig().getConfigurationSection("villagerLocation") == null) {
Main.logger().warning("ArenaPortal: no villagerLocation configured, villager disabled");
return;
}
this.villager = new DisplayVillager.ConfigBound(
this.localConfig(),
villager -> {
villager.customName(Component.text("Arena", NamedTextColor.RED));
villager.setProfession(Villager.Profession.WEAPONSMITH);
villager.setVillagerType(Villager.Type.SAVANNA);
}
);
}
/**
* @return false if the villager only appears after the next restart
*/
public boolean updateVillagerPosition(Location location) {
if(this.villager != null) {
this.villager.updateLocation(location);
return true;
}
ConfigUtil.Position.writeLocation(this.localConfig().createSection("villagerLocation"), location);
Configuration.saveChanges();
return false;
}
public void sendToArena(Player player) {
if(!this.pendingTransfers.add(player.getUniqueId())) return;
player.sendActionBar(Component.text("Du wirst zur Arena gebracht...", NamedTextColor.GRAY));
String items = Base64.getEncoder().encodeToString(ItemStack.serializeItemsAsBytes(player.getInventory().getContents()));
ArenaRepository.InventorySnapshot snapshot = new ArenaRepository.InventorySnapshot(player.getUniqueId(), items);
Bukkit.getScheduler().runTaskAsynchronously(Main.instance(), () -> {
boolean success;
try {
ReqResp<ArenaRepository.Response> response = this.queryRepository(ArenaRepository.class).sendInventory(snapshot);
success = response.status() == 200 && response.data() != null && "SUCCESS".equals(response.data().status());
} catch(Exception e) {
Main.logger().log(Level.WARNING, "ArenaPortal: arena server not reachable: " + e.getMessage());
success = false;
}
boolean finalSuccess = success;
Bukkit.getScheduler().runTask(Main.instance(), () -> {
this.pendingTransfers.remove(player.getUniqueId());
if(!player.isOnline()) return;
if(!finalSuccess) {
player.sendMessage(PREFIX.append(Component.text(
"Die Arena ist gerade nicht erreichbar. Versuche es später noch einmal.",
NamedTextColor.RED
)));
return;
}
Main.logger().info("Sending " + player.getName() + " to the arena");
PluginMessage.connect(player, this.localConfig().getString("connect-server-name", "arena"));
});
});
}
@Override
@NotNull
protected List<ApplianceCommand<?>> commands() {
return List.of(new ArenaCommand(), new MoveArenaVillagerCommand());
}
@Override
@NotNull
protected List<Listener> listeners() {
if(this.villager == null) return List.of();
return List.of(
new PlayerInteractAtEntityEventListener(this.villager.getUniqueId(), event -> this.sendToArena(event.getPlayer())),
new DismissInventoryOpenFromHolder(this.villager.getUniqueId())
);
}
}
@@ -0,0 +1,33 @@
package eu.mhsl.craftattack.spawn.craftattack.appliances.metaGameplay.arenaPortal;
import eu.mhsl.craftattack.spawn.core.api.client.HttpRepository;
import eu.mhsl.craftattack.spawn.core.api.client.ReqResp;
import eu.mhsl.craftattack.spawn.core.config.Configuration;
import java.net.URI;
import java.time.Duration;
import java.util.UUID;
public class ArenaRepository extends HttpRepository {
public ArenaRepository() {
super(URI.create(Configuration.cfg.getString("arenaPortal.api", "http://127.0.0.1:8081").replaceAll("/+$", "")));
}
/**
* @param items Base64 of {@link org.bukkit.inventory.ItemStack#serializeItemsAsBytes(java.util.Collection)}
*/
public record InventorySnapshot(UUID player, String items) {
}
public record Response(String status, String error) {
}
public ReqResp<Response> sendInventory(InventorySnapshot snapshot) {
return this.post("api/arena/inventory", Duration.ofSeconds(5), snapshot, Response.class);
}
@Override
protected boolean logRequestData() {
return false;
}
}
@@ -0,0 +1,26 @@
package eu.mhsl.craftattack.spawn.craftattack.appliances.metaGameplay.arenaPortal;
import eu.mhsl.craftattack.spawn.core.appliance.ApplianceCommand;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
class MoveArenaVillagerCommand extends ApplianceCommand.PlayerChecked<ArenaPortal> {
public MoveArenaVillagerCommand() {
super("moveArenaVillager");
}
@Override
public @Nullable String permission() {
return "admin";
}
@Override
protected void execute(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if(!this.getAppliance().updateVillagerPosition(this.getPlayer().getLocation()))
sender.sendMessage(Component.text("Position gespeichert. Der Villager erscheint nach dem nächsten Neustart.", NamedTextColor.GOLD));
}
}