commit 618aa33d8cfb5f04f437b7e3323464c593e60703 Author: 梦凌汐 Date: Mon Jan 6 19:55:28 2025 +0800 Finish the ring diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4788b4b --- /dev/null +++ b/.gitignore @@ -0,0 +1,113 @@ +# User-specific stuff +.idea/ + +*.iml +*.ipr +*.iws + +# IntelliJ +out/ + +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +target/ + +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next + +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties +.mvn/wrapper/maven-wrapper.jar +.flattened-pom.xml + +# Common working directory +run/ diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..1a49cbc --- /dev/null +++ b/pom.xml @@ -0,0 +1,72 @@ + + + 4.0.0 + + cn.meowdream + MeowBattle + 1.0-SNAPSHOT + jar + + MeowBattle + + + 21 + UTF-8 + + + + clean package + + + org.apache.maven.plugins + maven-compiler-plugin + 3.13.0 + + ${java.version} + ${java.version} + + + + org.apache.maven.plugins + maven-shade-plugin + 3.5.3 + + + package + + shade + + + + + + + + src/main/resources + true + + + + + + + spigotmc-repo + https://hub.spigotmc.org/nexus/content/repositories/snapshots/ + + + sonatype + https://oss.sonatype.org/content/groups/public/ + + + + + + org.spigotmc + spigot-api + 1.21.1-R0.1-SNAPSHOT + provided + + + diff --git a/src/main/java/cn/meowdream/meowBattle/MeowBattle.java b/src/main/java/cn/meowdream/meowBattle/MeowBattle.java new file mode 100644 index 0000000..8310e5b --- /dev/null +++ b/src/main/java/cn/meowdream/meowBattle/MeowBattle.java @@ -0,0 +1,83 @@ +package cn.meowdream.meowBattle; + +import cn.meowdream.meowBattle.battleWorld.BattleWorld; +import cn.meowdream.meowBattle.battleWorld.borderChangeState; +import cn.meowdream.meowBattle.battleWorld.borderChangeTask; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.command.TabCompleter; +import org.bukkit.entity.Player; +import org.bukkit.plugin.java.JavaPlugin; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +public final class MeowBattle extends JavaPlugin { + + @Override + public void onEnable() { + this.getCommand("battle").setExecutor(new CommandBattle()); + this.getCommand("battle").setTabCompleter(new TabCompleterBattle()); + } + + public class CommandBattle implements CommandExecutor { + BattleWorld battleWorld; + + @Override + public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { + if (sender instanceof Player) { + if(args.length == 1) { + if (Objects.equals(args[0], "start")) { + List borderChangeTasks = new ArrayList<>(); + borderChangeTasks.add(new borderChangeTask(10, 8, 0.75, 0.5)); + borderChangeTasks.add(new borderChangeTask(30, 8, 0.45, 1)); + borderChangeTasks.add(new borderChangeTask(50, 10, 0.15, 1.5)); + borderChangeTasks.add(new borderChangeTask(70, 15, 0, 2.5)); + World world = Objects.requireNonNull(Bukkit.getWorld("world")); + battleWorld = new BattleWorld(world, new Location(world,495, 0, 210), new Location(world, 595, 0, 310), 0, borderChangeTasks); + battleWorld.startBorderChange(); + } else if (Objects.equals(args[0], "stop")) { + battleWorld.stopAllActivities(); + } else if (Objects.equals(args[0], "state")) { + borderChangeState state = battleWorld.getState(); + if(state == borderChangeState.NOT_STARTED) { + sender.sendMessage("The border change is not started."); + } else if(state == borderChangeState.CLOSING) { + sender.sendMessage("The border is closing."); + } else if(state == borderChangeState.CLOSED) { + sender.sendMessage("The border is closed."); + } else if(state == borderChangeState.FINAL) { + sender.sendMessage("The border is final."); + } + } else if (Objects.equals(args[0], "destroy")) { + battleWorld.destroy(); + } + } + } + return true; + } + } + + public class TabCompleterBattle implements TabCompleter { + @Override + public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { + if (args.length == 1) { + List commands = new ArrayList<>(Arrays.asList("start", "stop", "state", "destroy")); + commands.removeIf(s -> !s.startsWith(args[0])); + return commands; + } + return null; + } + } + + @Override + public void onDisable() { + // Plugin shutdown logic + } +} diff --git a/src/main/java/cn/meowdream/meowBattle/battleWorld/BattleWorld.java b/src/main/java/cn/meowdream/meowBattle/battleWorld/BattleWorld.java new file mode 100644 index 0000000..918b522 --- /dev/null +++ b/src/main/java/cn/meowdream/meowBattle/battleWorld/BattleWorld.java @@ -0,0 +1,373 @@ +package cn.meowdream.meowBattle.battleWorld; + +import org.bukkit.*; +import org.bukkit.block.data.BlockData; +import org.bukkit.entity.BlockDisplay; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitRunnable; +import org.bukkit.scheduler.BukkitTask; +import org.bukkit.util.Transformation; +import org.joml.AxisAngle4f; +import org.joml.Vector3f; + +import java.util.*; +import java.util.concurrent.TimeUnit; + +public class BattleWorld { + private World world; + private double baseY; + private WorldBorder worldBorder; + private List borderChangeTasks, originTasks; + private List taskNum = new ArrayList<>(); + private int originSize = 0; + private double currentDamage; + BlockDisplay wall1, wall2, wall3, wall4; + BukkitTask renderTask, soundManagerTask; + private borderChangeState state = borderChangeState.NOT_STARTED; + + private boolean isRunning = true; + + public void createParticleWall(double x1, double z1, double x2, double z2) { + if(state == borderChangeState.FINAL) { + wall1.remove(); + wall2.remove(); + wall3.remove(); + wall4.remove(); + return; + } + +// Particle particle = Particle.BLOCK_MARKER; +// for(double y = baseY; y < world.getMaxHeight(); y += step) { +// for(double x = x1; x <= x2; x += step) { +// Location loc = new Location(world, x, y, z1); +// world.spawnParticle(particle, loc, 1, step / 2, step / 2, 0, 0, Material.BARRIER.createBlockData(), true); +// loc = new Location(world, x, y, z2); +// world.spawnParticle(particle, loc, 1, step / 2, step / 2, 0, 0, Material.BARRIER.createBlockData(), true); +// } +// for(double z = z1; z <= z2; z += step) { +// Location loc = new Location(world, x1, y, z); +// world.spawnParticle(particle, loc, 1, 0, step / 2, step / 2, 0, Material.BARRIER.createBlockData(), true); +// loc = new Location(world, x2, y, z); +// world.spawnParticle(particle, loc, 1, 0, step / 2, step / 2, 0, Material.BARRIER.createBlockData(), true); +// } +// } + + BlockData data = Material.RED_STAINED_GLASS.createBlockData(); + + Location bLocation = worldBorder.getCenter(); + + wall1.setViewRange(Float.MAX_VALUE); + wall1.setTransformation(new Transformation(new Vector3f(), new AxisAngle4f(), new Vector3f((float) worldBorder.getSize(), -(float) (world.getMaxHeight() - baseY), 0), new AxisAngle4f())); + wall1.teleport(new Location(world, bLocation.getX() - worldBorder.getSize() / 2, world.getMaxHeight(), bLocation.getZ() - worldBorder.getSize() / 2)); + wall1.setBlock(data); + + wall2.setViewRange(Float.MAX_VALUE); + wall2.setTransformation(new Transformation(new Vector3f(), new AxisAngle4f(), new Vector3f((float) worldBorder.getSize(), -(float) (world.getMaxHeight() - baseY), 0), new AxisAngle4f())); + wall2.teleport(new Location(world, bLocation.getX() - worldBorder.getSize() / 2, world.getMaxHeight(), bLocation.getZ() + worldBorder.getSize() / 2)); + wall2.setBlock(data); + + wall3.setViewRange(Float.MAX_VALUE); + wall3.setTransformation(new Transformation(new Vector3f(), new AxisAngle4f(), new Vector3f(0, -(float) (world.getMaxHeight() - baseY), (float) worldBorder.getSize()), new AxisAngle4f())); + wall3.teleport(new Location(world, bLocation.getX() - worldBorder.getSize() / 2, world.getMaxHeight(), bLocation.getZ() - worldBorder.getSize() / 2)); + wall3.setBlock(data); + + wall4.setViewRange(Float.MAX_VALUE); + wall4.setTransformation(new Transformation(new Vector3f(), new AxisAngle4f(), new Vector3f(0, -(float) (world.getMaxHeight() - baseY), (float) worldBorder.getSize()), new AxisAngle4f())); + wall4.teleport(new Location(world, bLocation.getX() + worldBorder.getSize() / 2, world.getMaxHeight(), bLocation.getZ() - worldBorder.getSize() / 2)); + wall4.setBlock(data); + +// for(Player player : world.getPlayers()) { +// Particle particle = Particle.FLAME; +// for(double y = player.getLocation().getY() - 20; y < player.getLocation().getY() + 20; y += step) { +// for(double x = x1; x <= x2; x += step) { +// Location loc = new Location(world, x, y, z1); +// player.spawnParticle(particle, loc, 6, step / 4, step / 2, 0, 0, null, true); +// loc = new Location(world, x, y, z2); +// player.spawnParticle(particle, loc, 6, step / 4, step / 2, 0, 0, null, true); +// } +// for(double z = z1; z <= z2; z += step) { +// Location loc = new Location(world, x1, y, z); +// player.spawnParticle(particle, loc, 6, 0, step / 2, step / 4, 0, null, true); +// loc = new Location(world, x2, y, z); +// player.spawnParticle(particle, loc, 6, 0, step / 2, step / 4, 0, null, true); +// } +// } +// } + } + + private final Map damageTasks = new HashMap<>(); + + public void applyDamageOverTime(Player player, int duration, double damage) { + BukkitRunnable task = new BukkitRunnable() { + @Override + public void run() { + if (!player.isOnline()) { + return; + } + + // 扣除0.1生命值 + player.damage(damage); + } + }; + + BukkitTask t = task.runTaskTimer(Objects.requireNonNull(Bukkit.getPluginManager().getPlugin("MeowBattle")), 0L, 20L); // 每秒运行一次任务 + damageTasks.put(player, t); + } + + public void cancelDamageOverTime(Player player) { + BukkitTask task = damageTasks.remove(player); // 从映射中获取并移除任务 + if (task != null) { + task.cancel(); // 取消任务 + } + } + + private final Map lastInRing = new HashMap<>(); + + BukkitRunnable renderer = new BukkitRunnable() { + @Override + public void run() { + if(!isRunning) { + return; + } + if(world != null) { + createParticleWall(worldBorder.getCenter().getX() - worldBorder.getSize() / 2, worldBorder.getCenter().getZ() - worldBorder.getSize() / 2, worldBorder.getCenter().getX() + worldBorder.getSize() / 2, worldBorder.getCenter().getZ() + worldBorder.getSize() / 2); + } + } + }; + + BukkitRunnable soundManager = new BukkitRunnable() { + @Override + public void run() { + if(!isRunning) { + return; + } + if(world != null) { + Sound sound = Sound.ENTITY_BLAZE_BURN; + for(Player player : world.getPlayers()) { + if(!worldBorder.isInside(player.getLocation()) || !isRunning) { + if(!lastInRing.containsKey(player) || lastInRing.get(player)) { +// player.stopSound(sound); +// Bukkit.broadcastMessage("Stopping sound"); + applyDamageOverTime(player, 1, currentDamage); + lastInRing.put(player, false); + } + player.playSound(player.getLocation(), sound, 0.7f, 0); + + Particle particle = Particle.FLAME; + player.spawnParticle(particle, new Location(world, player.getLocation().getX(), player.getLocation().getY() + 1, player.getLocation().getZ()), 4, 0.5, 0.5, 0.5, 0, null, true); + +// Bukkit.broadcastMessage("Playing OUT ring sound"); + } else { + // calculate the distance to the border + Location pLocation = player.getLocation(), bLocation = worldBorder.getCenter(); + double wbSize = worldBorder.getSize() / 2; + double distance = Math.min(Math.min(Math.abs(pLocation.getX() - bLocation.getX() + wbSize), Math.abs(pLocation.getX() - bLocation.getX() - wbSize)), Math.min(Math.abs(pLocation.getZ() - bLocation.getZ() + wbSize), Math.abs(pLocation.getZ() - bLocation.getZ() - wbSize))); +// Bukkit.broadcastMessage("Distance: " + distance + ";"); + if(!lastInRing.containsKey(player) || !lastInRing.get(player)) { + lastInRing.put(player, true); + cancelDamageOverTime(player); +// player.stopSound(sound); +// Bukkit.broadcastMessage("Stopping sound"); + } + if(distance < 14) { + player.playSound(new Location(world, pLocation.getX(), pLocation.getY() - distance, pLocation.getZ()), sound, Math.max((float) (0.7 - distance / 14 * 0.7), 0), 0); +// Bukkit.broadcastMessage("Playing IN ring sound"); + } else { + player.stopSound(sound); +// Bukkit.broadcastMessage("Stopping sound"); + } + } + } + } + } + }; + + public BattleWorld(World world, Location corner1, Location corner2, double baseY, List borderChangeTasks) { + this.world = world; + this.baseY = baseY; + worldBorder = Bukkit.createWorldBorder(); +// worldBorder = world.getWorldBorder(); + worldBorder.setCenter((corner1.getX() + corner2.getX()) / 2, (corner1.getZ() + corner2.getZ()) / 2); + originSize = (int) Math.min(Math.abs(corner1.getX() - corner2.getX()), Math.abs(corner1.getZ() - corner2.getZ())) / 2 + 1; + worldBorder.setSize(originSize * 2); + worldBorder.setDamageAmount(0f); + worldBorder.setWarningDistance(0); + worldBorder.setDamageBuffer(0); + + for(Player player : world.getPlayers()) { + player.setResourcePack("http://ys-n.ysepan.com/wap/meowlynxsea/A2BcPlIE3EGQ6JeAeQ/kDN3lgB,AB52M5yRa48aV7EQ3yge8/MeowBattle.zip"); + } + + wall1 = (BlockDisplay) world.spawnEntity(new Location(world, 0, 0, 0), EntityType.BLOCK_DISPLAY); + wall2 = (BlockDisplay) world.spawnEntity(new Location(world, 0, 0, 0), EntityType.BLOCK_DISPLAY); + wall3 = (BlockDisplay) world.spawnEntity(new Location(world, 0, 0, 0), EntityType.BLOCK_DISPLAY); + wall4 = (BlockDisplay) world.spawnEntity(new Location(world, 0, 0, 0), EntityType.BLOCK_DISPLAY); + + //borderChangeTasks中最后一个任务的size必须为0 + if(!borderChangeTasks.isEmpty()) { + borderChangeTasks.getLast().size = 0; + } else { + throw new IllegalArgumentException("borderChangeTasks is empty"); + } + + this.originTasks = borderChangeTasks; + this.borderChangeTasks = new ArrayList<>(); + // 将所有的task分割成0.1秒一个 + for (int i = 0; i < borderChangeTasks.size(); i++) { + int splitTaskNum = 0; + for(double j = 0; j < borderChangeTasks.get(i).duration; j += 0.1) { + if(i == 0) { + this.borderChangeTasks.add(new borderChangeTask(borderChangeTasks.get(i).startTime + j, 0.1, borderChangeTasks.get(i).size * (j / borderChangeTasks.get(i).duration) + 1 * (1 - j / borderChangeTasks.get(i).duration), borderChangeTasks.get(i).damagePerSecond)); + } else { + this.borderChangeTasks.add(new borderChangeTask(borderChangeTasks.get(i).startTime + j, 0.1, borderChangeTasks.get(i).size * (j / borderChangeTasks.get(i).duration) + borderChangeTasks.get(i - 1).size * (1 - j / borderChangeTasks.get(i).duration), borderChangeTasks.get(i).damagePerSecond)); + } + splitTaskNum++; + } + taskNum.add(splitTaskNum); + } + } + + private void startBorderChangeTask(int taskId, Location targetCenter) { + if(!isRunning) { + return; + } + + currentDamage = borderChangeTasks.get(taskId).damagePerSecond; + + // get current size + double size = worldBorder.getSize() / 2; + // get current center + Location center = worldBorder.getCenter(); + // calculate new size + double newSize = Math.max(originSize * borderChangeTasks.get(taskId).size, 1D); + + // 保证新的边界的四边都在老边界之内 + while(targetCenter.getX() - newSize < center.getX() - size) { + targetCenter.setX(targetCenter.getX() + 0.1); + } + while(targetCenter.getX() + newSize > center.getX() + size) { + targetCenter.setX(targetCenter.getX() - 0.1); + } + while(targetCenter.getZ() - newSize < center.getZ() - size) { + targetCenter.setZ(targetCenter.getZ() + 0.1); + } + while(targetCenter.getZ() + newSize > center.getZ() + size) { + targetCenter.setZ(targetCenter.getZ() - 0.1); + } + + double duration = borderChangeTasks.get(taskId).duration; + + worldBorder.setSize(newSize * 2, TimeUnit.MILLISECONDS, (long) (duration * 1000)); + worldBorder.setCenter(targetCenter); + } + + public void startBorderChange() { + renderTask = renderer.runTaskTimer(Objects.requireNonNull(Bukkit.getPluginManager().getPlugin("MeowBattle")), 0, 2); + soundManagerTask = soundManager.runTaskTimer(Objects.requireNonNull(Bukkit.getPluginManager().getPlugin("MeowBattle")), 0, 7); + + List targetLocations = new ArrayList<>(); + + double simulatedSize = worldBorder.getSize() / 2; + Location simulationCenter = worldBorder.getCenter(); + + for (int i = 0; i < borderChangeTasks.size(); i++) { + int finalI = i; + int currentTaskID = 0; + int j = i - taskNum.getFirst(); + while (j > 0) { + currentTaskID++; + j -= taskNum.get(currentTaskID); + } + j += taskNum.get(currentTaskID); + + if(targetLocations.size() <= currentTaskID) { + // calculate new size + double newSize = originSize * originTasks.get(currentTaskID).size; + + // calculate new center + Location tCenter = new Location(world, simulationCenter.getX() + (simulatedSize - newSize) * 2 * (Math.random() - 0.5), baseY, simulationCenter.getZ() + (simulatedSize - newSize) * 2 * (Math.random() - 0.5)); + + targetLocations.add(tCenter); + } + + Location tCenter = targetLocations.get(currentTaskID); + Location newCenter = new Location( + world, + tCenter.getX() * ((double) j / taskNum.get(currentTaskID)) + simulationCenter.getX() * (1 - (double) j / taskNum.get(currentTaskID)), + baseY, + tCenter.getZ() * ((double) j / taskNum.get(currentTaskID)) + simulationCenter.getZ() * (1 - (double) j / taskNum.get(currentTaskID)) + ); + + simulationCenter = newCenter; + simulatedSize = Math.max(originSize * borderChangeTasks.get(finalI).size, 1D); + + int finalJ = j; + int finalCurrentTaskID = currentTaskID; + Bukkit.getScheduler().runTaskLater(Objects.requireNonNull(Bukkit.getPluginManager().getPlugin("MeowBattle")), new Runnable() { + @Override + public void run() { + if(!isRunning) return; + if(finalJ == 1) { + Bukkit.broadcastMessage("Start ring closing..."); + state = borderChangeState.CLOSING; + } else if(finalJ == taskNum.get(finalCurrentTaskID) - 1) { + Bukkit.broadcastMessage("Ring closed!"); + state = borderChangeState.CLOSED; + } + if(finalCurrentTaskID == originTasks.size() - 1 && finalJ == taskNum.get(finalCurrentTaskID) - 1) { + Bukkit.broadcastMessage("Final ring closed!"); + state = borderChangeState.FINAL; + } + startBorderChangeTask(finalI, newCenter); + } + }, (long) ((borderChangeTasks.get(finalI).startTime) * 20)); + } + } + + public void stopAllActivities() { + for(Player player : world.getPlayers()) { + cancelDamageOverTime(player); + } + renderTask.cancel(); + soundManagerTask.cancel(); + isRunning = false; + state = borderChangeState.FINAL; + } + + public void destroy() { + stopAllActivities(); + worldBorder.reset(); + worldBorder.setDamageAmount(0); + worldBorder.setDamageBuffer(0); + worldBorder.setWarningDistance(0); + worldBorder.setWarningTime(0); + worldBorder.setDamageAmount(0); + wall1.remove(); + wall2.remove(); + wall3.remove(); + wall4.remove(); + } + + public borderChangeState getState() { + return state; + } + + public World getWorld() { + return world; + } + + public void setWorld(World world) { + this.world = world; + } + + public double getBaseY() { + return baseY; + } + + public void setBaseY(double baseY) { + this.baseY = baseY; + } + + +} diff --git a/src/main/java/cn/meowdream/meowBattle/battleWorld/borderChangeState.java b/src/main/java/cn/meowdream/meowBattle/battleWorld/borderChangeState.java new file mode 100644 index 0000000..5cccd85 --- /dev/null +++ b/src/main/java/cn/meowdream/meowBattle/battleWorld/borderChangeState.java @@ -0,0 +1,8 @@ +package cn.meowdream.meowBattle.battleWorld; + +public enum borderChangeState { + NOT_STARTED, + CLOSING, + CLOSED, + FINAL +} diff --git a/src/main/java/cn/meowdream/meowBattle/battleWorld/borderChangeTask.java b/src/main/java/cn/meowdream/meowBattle/battleWorld/borderChangeTask.java new file mode 100644 index 0000000..8b7de97 --- /dev/null +++ b/src/main/java/cn/meowdream/meowBattle/battleWorld/borderChangeTask.java @@ -0,0 +1,15 @@ +package cn.meowdream.meowBattle.battleWorld; + +public class borderChangeTask { + public double startTime; + public double duration; // in seconds + public double size; // in percentage + public double damagePerSecond; + + public borderChangeTask(double startTime, double duration, double size, double damagePerSecond) { + this.startTime = startTime; + this.duration = duration; + this.size = size; + this.damagePerSecond = damagePerSecond; + } +} diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml new file mode 100644 index 0000000..829e5b7 --- /dev/null +++ b/src/main/resources/plugin.yml @@ -0,0 +1,9 @@ +name: MeowBattle +version: '1.0-SNAPSHOT' +main: cn.meowdream.meowBattle.MeowBattle +api-version: '1.21' +commands: + battle: + description: control the battle game + usage: / ... + permission: battle.restart \ No newline at end of file