43 Commits
1.21 ... sw-ui

Author SHA1 Message Date
ea0bb44416 Add memoization support and implement equals/hashCode methods
All checks were successful
SteamWarCI Build successful
2025-04-21 23:20:15 +02:00
4cb0ff292e Add support for player inventory integration in UI system
All checks were successful
SteamWarCI Build successful
2025-04-21 16:02:18 +02:00
ee9eb995f5 Add inventory UI framework with layout and state features
All checks were successful
SteamWarCI Build successful
2025-04-21 14:48:42 +02:00
147e34c0d6 Fix TNTClickListener
All checks were successful
SteamWarCI Build successful
2025-04-21 11:47:00 +02:00
313b22cb44 Optimize SimulatorStabGenerator
All checks were successful
SteamWarCI Build successful
2025-04-20 18:30:30 +02:00
230ac09b61 Hotfix for disabled things
All checks were successful
SteamWarCI Build successful
2025-04-20 08:12:22 +02:00
48ea88e1a7 Merge pull request 'BauSystem/SimulatorStabGenerator' (#47) from BauSystem/SimulatorStabGenerator into main
All checks were successful
SteamWarCI Build successful
Reviewed-on: #47
2025-04-19 23:21:45 +02:00
05dc42355d Update and improve SimulatorStabGenerator
All checks were successful
SteamWarCI Build successful
2025-04-19 23:04:19 +02:00
17704487c9 Add SimulatorStabGenerator 2025-04-19 17:58:40 +02:00
76bbfd0381 Merge pull request 'Add TNTClickListener.TNT_CLICK_DETAILS as a toggle' (#46) from BauSystem/TNTClickDetails into main
All checks were successful
SteamWarCI Build successful
Reviewed-on: #46
2025-04-18 21:52:29 +02:00
ae9166528d Add TNTClickListener.TNT_CLICK_DETAILS as a toggle
All checks were successful
SteamWarCI Build successful
2025-04-18 21:49:59 +02:00
119fae4b51 Merge pull request 'Add ActiveMonths to ArenaMode for rotating modes' (#45) from VelocityCore/ActiveMonthsForRotatingGameModes into main
All checks were successful
SteamWarCI Build successful
Reviewed-on: #45
2025-04-18 18:38:41 +02:00
43621b18b4 Fix TypeMappers.arenaMapTypeMapper
All checks were successful
SteamWarCI Build successful
2025-04-18 16:57:35 +02:00
e5bdbac3c7 Change isNotActive to isActive
All checks were successful
SteamWarCI Build successful
2025-04-18 16:50:16 +02:00
fd738f539a Add 3 new PluginMessage channel
All checks were successful
SteamWarCI Build successful
2025-04-18 14:17:59 +02:00
b4c7576433 Merge pull request 'TNTLeague/BiggerCoins' (#12) from TNTLeague/BiggerCoins into main
All checks were successful
SteamWarCI Build successful
Reviewed-on: #12
Reviewed-by: Chaoscaot <max@chaoscaot.de>
2025-04-18 12:36:29 +02:00
8204e2ad21 Fix MWTeam.leave not distributing items correctly
All checks were successful
SteamWarCI Build successful
2025-04-18 12:30:22 +02:00
684a74b60d Fix SmartPlaceListener not updating Comparator or Repeater
All checks were successful
SteamWarCI Build successful
2025-04-18 12:14:43 +02:00
1f58b51af6 Fix NoteBlock turning off on redstone destroy under or up to 2 blocks beside it
All checks were successful
SteamWarCI Build successful
2025-04-18 12:07:31 +02:00
c9cfb48c4e Final fix for GlobalListener
All checks were successful
SteamWarCI Build successful
2025-04-18 10:14:08 +02:00
ace567ba33 Add chat system to TowerRun
All checks were successful
SteamWarCI Build successful
2025-04-18 10:07:42 +02:00
2094120150 Remove prefix. Cannot merge them together because of 'ChatListener.sendChat'
All checks were successful
SteamWarCI Build successful
2025-04-18 09:32:35 +02:00
e143268caa Fix DiscordBot.serverTeamChat sending to discord if received from discord!
All checks were successful
SteamWarCI Build successful
2025-04-18 09:30:26 +02:00
7802fdd7d9 Add ActiveMonths to ArenaMode for rotating modes
All checks were successful
SteamWarCI Build successful
2025-04-18 09:13:23 +02:00
7fb3d3d137 Hotfix DiscordBot.ingameChat going to broadcast instead of globalChat
All checks were successful
SteamWarCI Build successful
2025-04-17 21:38:35 +02:00
61d84492dc Merge branch 'main' into TNTLeague/BiggerCoins
All checks were successful
SteamWarCI Build successful
2025-04-17 21:18:40 +02:00
f74780d395 Hotfix IngameListener
All checks were successful
SteamWarCI Build successful
2025-04-17 16:47:23 +02:00
62dac000d4 Merge pull request 'Add general Melting of Blocks' (#43) from TowerRun/GeneralMeltingOfBlocks into main
All checks were successful
SteamWarCI Build successful
Reviewed-on: #43
2025-04-17 16:32:28 +02:00
424c80ec81 Add general Melting of Blocks
All checks were successful
SteamWarCI Build successful
2025-04-17 16:09:29 +02:00
306444356c Update & Fix Spelling on Chaos Messages
All checks were successful
SteamWarCI Build successful
2025-04-17 01:28:06 +02:00
29dff8dce6 Fix PunishmentCommand sending double prefix
All checks were successful
SteamWarCI Build successful
2025-04-16 21:07:05 +02:00
cbaacd4e85 Merge pull request 'Improve and fix the discord rate limit warnings' (#37) from VelocityCore/ImproveDiscordChannel into main
All checks were successful
SteamWarCI Build successful
Reviewed-on: #37
Reviewed-by: Lixfel <lixfel@noreply.localhost>
2025-04-16 20:16:52 +02:00
e24dada435 Trigger rebuild
All checks were successful
SteamWarCI Build successful
2025-04-16 18:13:58 +02:00
d04ffd5cf6 Fix some stuff for 1.21 BauSystem
Some checks failed
SteamWarCI Build failed
2025-04-16 18:12:50 +02:00
9da1de9b6c Fix MsgCommand being able to /r to offline players
All checks were successful
SteamWarCI Build successful
2025-04-16 13:42:31 +02:00
866c376ee5 Fix DevCommand not working when version switching without trying to connect in between when no server is running
All checks were successful
SteamWarCI Build successful
2025-04-16 10:20:10 +02:00
5b3c3f36b4 Fix comment
All checks were successful
SteamWarCI Build successful
2025-04-14 09:16:06 +02:00
e1bcdd59ba Move 'maxNumberOfWebhooks' constructor parameter to end of list
All checks were successful
SteamWarCI Build successful
2025-04-14 09:14:04 +02:00
4a816696ec Update the message limits
All checks were successful
SteamWarCI Build successful
2025-04-09 09:10:30 +02:00
038f54c3b3 Make it buildable and reduce complexity
All checks were successful
SteamWarCI Build successful
2025-04-09 09:03:07 +02:00
4c6ab2c1a0 Improve and fix the discord rate limit warnings
Some checks failed
SteamWarCI Build failed
2025-04-08 20:52:54 +02:00
20c90d9af5 Remove randomness
All checks were successful
SteamWarCI Build successful
2025-01-30 13:23:46 +01:00
c799046f43 Add bigger coins and remove TNT from spawning 2025-01-30 13:23:43 +01:00
82 changed files with 2983 additions and 222 deletions

View File

@@ -109,7 +109,7 @@ public class FlatteningWrapper15 implements FlatteningWrapper {
@Override
public void setSelection(Player p, Point minPoint, Point maxPoint) {
WORLDEDIT_PLUGIN.getSession(p).setRegionSelector(BUKKITWORLD, new CuboidRegionSelector(BUKKITWORLD, toBlockVector3(minPoint), toBlockVector3(maxPoint)));
WORLDEDIT_PLUGIN.getSession(p).setRegionSelector(BUKKITWORLD, new CuboidRegionSelector(BUKKITWORLD, minPoint.toBlockVector3(), maxPoint.toBlockVector3()));
}
@Override
@@ -178,9 +178,9 @@ public class FlatteningWrapper15 implements FlatteningWrapper {
pastePoint.set(v);
if (pasteBuilder.isReset()) {
e.setBlocks(new CuboidRegion(toBlockVector3(pasteBuilder.getMinPoint()), toBlockVector3(pasteBuilder.getMaxPoint())), Objects.requireNonNull(BlockTypes.AIR).getDefaultState().toBaseBlock());
e.setBlocks(new CuboidRegion(pasteBuilder.getMinPoint().toBlockVector3(), pasteBuilder.getMaxPoint().toBlockVector3()), Objects.requireNonNull(BlockTypes.AIR).getDefaultState().toBaseBlock());
if (pasteBuilder.getWaterLevel() != 0) {
e.setBlocks(new CuboidRegion(toBlockVector3(pasteBuilder.getMinPoint()), toBlockVector3(pasteBuilder.getMaxPoint()).withY(pasteBuilder.getWaterLevel())), Objects.requireNonNull(BlockTypes.WATER).getDefaultState().toBaseBlock());
e.setBlocks(new CuboidRegion(pasteBuilder.getMinPoint().toBlockVector3(), pasteBuilder.getMaxPoint().toBlockVector3().withY(pasteBuilder.getWaterLevel())), Objects.requireNonNull(BlockTypes.WATER).getDefaultState().toBaseBlock());
}
}
Operations.completeBlindly(ch.createPaste(e).to(v).ignoreAirBlocks(pasteBuilder.isIgnoreAir()).build());
@@ -193,7 +193,7 @@ public class FlatteningWrapper15 implements FlatteningWrapper {
@Override
public Clipboard copy(Point minPoint, Point maxPoint, Point copyPoint) {
BukkitWorld bukkitWorld = new BukkitWorld(Bukkit.getWorlds().get(0));
CuboidRegion region = new CuboidRegion(bukkitWorld, toBlockVector3(minPoint), toBlockVector3(maxPoint));
CuboidRegion region = new CuboidRegion(bukkitWorld, minPoint.toBlockVector3(), maxPoint.toBlockVector3());
BlockArrayClipboard clipboard = new BlockArrayClipboard(region);
try (EditSession e = WorldEdit.getInstance().getEditSessionFactory().getEditSession(bukkitWorld, -1)) {
ForwardExtentCopy copy = new ForwardExtentCopy(
@@ -204,7 +204,7 @@ public class FlatteningWrapper15 implements FlatteningWrapper {
copy.setCopyingBiomes(false);
Operations.complete(copy);
clipboard.setOrigin(toBlockVector3(copyPoint));
clipboard.setOrigin(copyPoint.toBlockVector3());
return clipboard;
} catch (WorldEditException e) {
Bukkit.getLogger().log(Level.SEVERE, e.getMessage(), e);
@@ -224,10 +224,6 @@ public class FlatteningWrapper15 implements FlatteningWrapper {
}
}
private BlockVector3 toBlockVector3(Point point) {
return BlockVector3.at(point.getX(), point.getY(), point.getZ());
}
@Override
public boolean inWater(org.bukkit.World world, Vector tntPosition) {
Block block = world.getBlockAt(tntPosition.getBlockX(), tntPosition.getBlockY(), tntPosition.getBlockZ());

View File

@@ -947,6 +947,9 @@ SPEED_TAB_NAME=Input speed
WORLDEDIT_WAND=WorldEdit Wand
WORLDEDIT_LEFTCLICK=Left click: select pos #1
WORLDEDIT_RIGHTCLICK=Right click: select pos #2
TNT_DETAILS_COMMAND=§8/§etntdetails §8-§7 Toggle information printed after clicking on a TNT
TNT_DETAILS_ON = §eTNTDetails §aactivated
TNT_DETAILS_OFF = §eTNTDetails §cdeactivated
TNT_CLICK_HEADER=§8---=== §eTNT §8===---
TNT_CLICK_ORDER=§eEntity Order§8: §e{0}
TNT_CLICK_FUSE_TIME=§eFuseTime§8: §e{0}

View File

@@ -889,6 +889,9 @@ SPEED_TAB_NAME=Geschwindigkeit eingeben
WORLDEDIT_WAND=WorldEdit Wand
WORLDEDIT_LEFTCLICK=Left click: select pos #1
WORLDEDIT_RIGHTCLICK=Right click: select pos #2
TNT_DETAILS_COMMAND=§8/§etntdetails §8-§7 Aktiviert/Deaktiviert das senden von Details beim Klick auf TNT
TNT_DETAILS_ON = §eTNTDetails §aaktiviert
TNT_DETAILS_OFF = §eTNTDetails §cdeaktiviert
TNT_CLICK_HEADER=§8---=== §eTNT §8===---
TNT_CLICK_ORDER=§eEntity Order§8: §e{0}
TNT_CLICK_FUSE_TIME=§eFuseTime§8: §e{0}

View File

@@ -11,8 +11,9 @@ import de.steamwar.linkage.Linked;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.BlockState;
import org.bukkit.block.data.type.NoteBlock;
import org.bukkit.block.data.type.Switch;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
@@ -60,6 +61,24 @@ public class FreezeListener implements Listener, ScoreboardElement {
@EventHandler
public void onPhysicsEvent(BlockPhysicsEvent e) {
if (Region.getRegion(e.getBlock().getLocation()).get(Flag.FREEZE) == FreezeMode.ACTIVE) {
if (e.getSourceBlock().getType() == Material.NOTE_BLOCK) {
BlockState state = e.getSourceBlock().getState();
NoteBlock noteBlock = (NoteBlock) state.getBlockData();
if (noteBlock.isPowered()) {
Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> {
state.update(true, false);
}, 1);
}
}
if (e.getBlock().getType() == Material.NOTE_BLOCK) {
BlockState state = e.getBlock().getState();
NoteBlock noteBlock = (NoteBlock) state.getBlockData();
if (noteBlock.isPowered()) {
Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> {
state.update(true, false);
}, 1);
}
}
e.setCancelled(true);
}
}
@@ -71,6 +90,105 @@ public class FreezeListener implements Listener, ScoreboardElement {
}
}
@EventHandler
public void onNotePlay(NotePlayEvent event) {
//[STDOUT] CraftBlock{pos=BlockPosition{x=-106, y=45, z=98},type=CYAN_TERRACOTTA,data=Block{minecraft:cyan_terracotta},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831} -> CraftBlock{pos=BlockPosition{x=-107, y=45, z=98},type=SMOOTH_STONE,data=Block{minecraft:smooth_stone},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-106, y=45, z=98},type=CYAN_TERRACOTTA,data=Block{minecraft:cyan_terracotta},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831} -> CraftBlock{pos=BlockPosition{x=-105, y=45, z=98},type=CYAN_TERRACOTTA,data=Block{minecraft:cyan_terracotta},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-106, y=45, z=98},type=CYAN_TERRACOTTA,data=Block{minecraft:cyan_terracotta},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831} -> CraftBlock{pos=BlockPosition{x=-106, y=44, z=98},type=CYAN_TERRACOTTA,data=Block{minecraft:cyan_terracotta},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-106, y=45, z=98},type=CYAN_TERRACOTTA,data=Block{minecraft:cyan_terracotta},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831} -> CraftBlock{pos=BlockPosition{x=-106, y=46, z=98},type=BARRIER,data=Block{minecraft:barrier},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-106, y=45, z=98},type=CYAN_TERRACOTTA,data=Block{minecraft:cyan_terracotta},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831} -> CraftBlock{pos=BlockPosition{x=-106, y=45, z=97},type=CYAN_TERRACOTTA,data=Block{minecraft:cyan_terracotta},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-106, y=45, z=98},type=CYAN_TERRACOTTA,data=Block{minecraft:cyan_terracotta},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831} -> CraftBlock{pos=BlockPosition{x=-106, y=45, z=99},type=SMOOTH_STONE,data=Block{minecraft:smooth_stone},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-106, y=47, z=98},type=NOTE_BLOCK,data=Block{minecraft:note_block}[instrument=custom_head,note=9,powered=true],fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831} -> CraftBlock{pos=BlockPosition{x=-107, y=47, z=98},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-106, y=47, z=98},type=NOTE_BLOCK,data=Block{minecraft:note_block}[instrument=custom_head,note=9,powered=true],fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831} -> CraftBlock{pos=BlockPosition{x=-105, y=47, z=98},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-106, y=47, z=98},type=NOTE_BLOCK,data=Block{minecraft:note_block}[instrument=custom_head,note=9,powered=true],fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831} -> CraftBlock{pos=BlockPosition{x=-106, y=46, z=98},type=BARRIER,data=Block{minecraft:barrier},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-106, y=47, z=98},type=NOTE_BLOCK,data=Block{minecraft:note_block}[instrument=custom_head,note=9,powered=true],fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831} -> CraftBlock{pos=BlockPosition{x=-106, y=48, z=98},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-106, y=47, z=98},type=NOTE_BLOCK,data=Block{minecraft:note_block}[instrument=custom_head,note=9,powered=true],fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831} -> CraftBlock{pos=BlockPosition{x=-106, y=47, z=97},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-106, y=47, z=98},type=NOTE_BLOCK,data=Block{minecraft:note_block}[instrument=custom_head,note=9,powered=true],fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831} -> CraftBlock{pos=BlockPosition{x=-106, y=47, z=99},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-106, y=46, z=97},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831} -> CraftBlock{pos=BlockPosition{x=-107, y=46, z=97},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-106, y=46, z=97},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831} -> CraftBlock{pos=BlockPosition{x=-105, y=46, z=97},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-106, y=46, z=97},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831} -> CraftBlock{pos=BlockPosition{x=-106, y=45, z=97},type=CYAN_TERRACOTTA,data=Block{minecraft:cyan_terracotta},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-106, y=46, z=97},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831} -> CraftBlock{pos=BlockPosition{x=-106, y=47, z=97},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-106, y=46, z=97},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831} -> CraftBlock{pos=BlockPosition{x=-106, y=46, z=96},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-106, y=46, z=97},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831} -> CraftBlock{pos=BlockPosition{x=-106, y=46, z=98},type=BARRIER,data=Block{minecraft:barrier},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-106, y=46, z=99},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831} -> CraftBlock{pos=BlockPosition{x=-107, y=46, z=99},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-106, y=46, z=99},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831} -> CraftBlock{pos=BlockPosition{x=-105, y=46, z=99},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-106, y=46, z=99},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831} -> CraftBlock{pos=BlockPosition{x=-106, y=45, z=99},type=SMOOTH_STONE,data=Block{minecraft:smooth_stone},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-106, y=46, z=99},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831} -> CraftBlock{pos=BlockPosition{x=-106, y=47, z=99},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-106, y=46, z=99},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831} -> CraftBlock{pos=BlockPosition{x=-106, y=46, z=98},type=BARRIER,data=Block{minecraft:barrier},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-106, y=46, z=99},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831} -> CraftBlock{pos=BlockPosition{x=-106, y=46, z=100},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-107, y=46, z=98},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831} -> CraftBlock{pos=BlockPosition{x=-108, y=46, z=98},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-107, y=46, z=98},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831} -> CraftBlock{pos=BlockPosition{x=-106, y=46, z=98},type=BARRIER,data=Block{minecraft:barrier},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-107, y=46, z=98},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831} -> CraftBlock{pos=BlockPosition{x=-107, y=45, z=98},type=SMOOTH_STONE,data=Block{minecraft:smooth_stone},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-107, y=46, z=98},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831} -> CraftBlock{pos=BlockPosition{x=-107, y=47, z=98},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-107, y=46, z=98},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831} -> CraftBlock{pos=BlockPosition{x=-107, y=46, z=97},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-107, y=46, z=98},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831} -> CraftBlock{pos=BlockPosition{x=-107, y=46, z=99},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-105, y=46, z=98},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831} -> CraftBlock{pos=BlockPosition{x=-106, y=46, z=98},type=BARRIER,data=Block{minecraft:barrier},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-105, y=46, z=98},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831} -> CraftBlock{pos=BlockPosition{x=-104, y=46, z=98},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-105, y=46, z=98},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831} -> CraftBlock{pos=BlockPosition{x=-105, y=45, z=98},type=CYAN_TERRACOTTA,data=Block{minecraft:cyan_terracotta},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-105, y=46, z=98},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831} -> CraftBlock{pos=BlockPosition{x=-105, y=47, z=98},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-105, y=46, z=98},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831} -> CraftBlock{pos=BlockPosition{x=-105, y=46, z=97},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-105, y=46, z=98},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831} -> CraftBlock{pos=BlockPosition{x=-105, y=46, z=99},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-106, y=47, z=98},type=NOTE_BLOCK,data=Block{minecraft:note_block}[instrument=harp,note=9,powered=true],fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831} -> CraftBlock{pos=BlockPosition{x=-106, y=47, z=98},type=NOTE_BLOCK,data=Block{minecraft:note_block}[instrument=harp,note=9,powered=true],fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-106, y=47, z=98},type=NOTE_BLOCK,data=Block{minecraft:note_block}[instrument=harp,note=9,powered=false],fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831} -> CraftBlock{pos=BlockPosition{x=-107, y=47, z=98},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-106, y=47, z=98},type=NOTE_BLOCK,data=Block{minecraft:note_block}[instrument=harp,note=9,powered=false],fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831} -> CraftBlock{pos=BlockPosition{x=-105, y=47, z=98},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-106, y=47, z=98},type=NOTE_BLOCK,data=Block{minecraft:note_block}[instrument=harp,note=9,powered=false],fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831} -> CraftBlock{pos=BlockPosition{x=-106, y=46, z=98},type=BARRIER,data=Block{minecraft:barrier},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-106, y=47, z=98},type=NOTE_BLOCK,data=Block{minecraft:note_block}[instrument=harp,note=9,powered=false],fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831} -> CraftBlock{pos=BlockPosition{x=-106, y=48, z=98},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-106, y=47, z=98},type=NOTE_BLOCK,data=Block{minecraft:note_block}[instrument=harp,note=9,powered=false],fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831} -> CraftBlock{pos=BlockPosition{x=-106, y=47, z=97},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-106, y=47, z=98},type=NOTE_BLOCK,data=Block{minecraft:note_block}[instrument=harp,note=9,powered=false],fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831} -> CraftBlock{pos=BlockPosition{x=-106, y=47, z=99},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-106, y=47, z=98},type=NOTE_BLOCK,data=Block{minecraft:note_block}[instrument=harp,note=9,powered=false],fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831} -> CraftBlock{pos=BlockPosition{x=-106, y=47, z=98},type=NOTE_BLOCK,data=Block{minecraft:note_block}[instrument=harp,note=9,powered=false],fluid=net.minecraft.world.level.material.FluidTypeEmpty@78078831}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-106, y=46, z=100},type=SMOOTH_STONE,data=Block{minecraft:smooth_stone},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b} -> CraftBlock{pos=BlockPosition{x=-107, y=46, z=100},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-106, y=46, z=100},type=SMOOTH_STONE,data=Block{minecraft:smooth_stone},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b} -> CraftBlock{pos=BlockPosition{x=-105, y=46, z=100},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-106, y=46, z=100},type=SMOOTH_STONE,data=Block{minecraft:smooth_stone},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b} -> CraftBlock{pos=BlockPosition{x=-106, y=45, z=100},type=SMOOTH_STONE,data=Block{minecraft:smooth_stone},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-106, y=46, z=100},type=SMOOTH_STONE,data=Block{minecraft:smooth_stone},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b} -> CraftBlock{pos=BlockPosition{x=-106, y=47, z=100},type=STONE_SLAB,data=Block{minecraft:stone_slab}[type=bottom,waterlogged=false],fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-106, y=46, z=100},type=SMOOTH_STONE,data=Block{minecraft:smooth_stone},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b} -> CraftBlock{pos=BlockPosition{x=-106, y=46, z=99},type=SMOOTH_STONE,data=Block{minecraft:smooth_stone},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-106, y=46, z=100},type=SMOOTH_STONE,data=Block{minecraft:smooth_stone},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b} -> CraftBlock{pos=BlockPosition{x=-106, y=46, z=101},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-106, y=48, z=100},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b} -> CraftBlock{pos=BlockPosition{x=-107, y=48, z=100},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-106, y=48, z=100},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b} -> CraftBlock{pos=BlockPosition{x=-105, y=48, z=100},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-106, y=48, z=100},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b} -> CraftBlock{pos=BlockPosition{x=-106, y=47, z=100},type=STONE_SLAB,data=Block{minecraft:stone_slab}[type=bottom,waterlogged=false],fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-106, y=48, z=100},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b} -> CraftBlock{pos=BlockPosition{x=-106, y=49, z=100},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-106, y=48, z=100},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b} -> CraftBlock{pos=BlockPosition{x=-106, y=48, z=99},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-106, y=48, z=100},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b} -> CraftBlock{pos=BlockPosition{x=-106, y=48, z=101},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-106, y=47, z=99},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b} -> CraftBlock{pos=BlockPosition{x=-107, y=47, z=99},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-106, y=47, z=99},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b} -> CraftBlock{pos=BlockPosition{x=-105, y=47, z=99},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-106, y=47, z=99},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b} -> CraftBlock{pos=BlockPosition{x=-106, y=46, z=99},type=SMOOTH_STONE,data=Block{minecraft:smooth_stone},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-106, y=47, z=99},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b} -> CraftBlock{pos=BlockPosition{x=-106, y=48, z=99},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b}
////[STDOUT] CraftBlock{pos=BlockPosition{x=-106, y=47, z=99},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b} -> CraftBlock{pos=BlockPosition{x=-106, y=47, z=98},type=NOTE_BLOCK,data=Block{minecraft:note_block}[instrument=basedrum,note=9,powered=true],fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-106, y=47, z=99},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b} -> CraftBlock{pos=BlockPosition{x=-106, y=47, z=100},type=STONE_SLAB,data=Block{minecraft:stone_slab}[type=bottom,waterlogged=false],fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-106, y=47, z=101},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b} -> CraftBlock{pos=BlockPosition{x=-107, y=47, z=101},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-106, y=47, z=101},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b} -> CraftBlock{pos=BlockPosition{x=-105, y=47, z=101},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-106, y=47, z=101},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b} -> CraftBlock{pos=BlockPosition{x=-106, y=46, z=101},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-106, y=47, z=101},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b} -> CraftBlock{pos=BlockPosition{x=-106, y=48, z=101},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-106, y=47, z=101},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b} -> CraftBlock{pos=BlockPosition{x=-106, y=47, z=100},type=STONE_SLAB,data=Block{minecraft:stone_slab}[type=bottom,waterlogged=false],fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-106, y=47, z=101},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b} -> CraftBlock{pos=BlockPosition{x=-106, y=47, z=102},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-107, y=47, z=100},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b} -> CraftBlock{pos=BlockPosition{x=-108, y=47, z=100},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-107, y=47, z=100},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b} -> CraftBlock{pos=BlockPosition{x=-106, y=47, z=100},type=STONE_SLAB,data=Block{minecraft:stone_slab}[type=bottom,waterlogged=false],fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-107, y=47, z=100},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b} -> CraftBlock{pos=BlockPosition{x=-107, y=46, z=100},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-107, y=47, z=100},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b} -> CraftBlock{pos=BlockPosition{x=-107, y=48, z=100},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-107, y=47, z=100},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b} -> CraftBlock{pos=BlockPosition{x=-107, y=47, z=99},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-107, y=47, z=100},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b} -> CraftBlock{pos=BlockPosition{x=-107, y=47, z=101},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-105, y=47, z=100},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b} -> CraftBlock{pos=BlockPosition{x=-106, y=47, z=100},type=STONE_SLAB,data=Block{minecraft:stone_slab}[type=bottom,waterlogged=false],fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-105, y=47, z=100},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b} -> CraftBlock{pos=BlockPosition{x=-104, y=47, z=100},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-105, y=47, z=100},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b} -> CraftBlock{pos=BlockPosition{x=-105, y=46, z=100},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-105, y=47, z=100},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b} -> CraftBlock{pos=BlockPosition{x=-105, y=48, z=100},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-105, y=47, z=100},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b} -> CraftBlock{pos=BlockPosition{x=-105, y=47, z=99},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-105, y=47, z=100},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b} -> CraftBlock{pos=BlockPosition{x=-105, y=47, z=101},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-106, y=47, z=98},type=NOTE_BLOCK,data=Block{minecraft:note_block}[instrument=basedrum,note=9,powered=false],fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b} -> CraftBlock{pos=BlockPosition{x=-107, y=47, z=98},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-106, y=47, z=98},type=NOTE_BLOCK,data=Block{minecraft:note_block}[instrument=basedrum,note=9,powered=false],fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b} -> CraftBlock{pos=BlockPosition{x=-105, y=47, z=98},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-106, y=47, z=98},type=NOTE_BLOCK,data=Block{minecraft:note_block}[instrument=basedrum,note=9,powered=false],fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b} -> CraftBlock{pos=BlockPosition{x=-106, y=46, z=98},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-106, y=47, z=98},type=NOTE_BLOCK,data=Block{minecraft:note_block}[instrument=basedrum,note=9,powered=false],fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b} -> CraftBlock{pos=BlockPosition{x=-106, y=48, z=98},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-106, y=47, z=98},type=NOTE_BLOCK,data=Block{minecraft:note_block}[instrument=basedrum,note=9,powered=false],fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b} -> CraftBlock{pos=BlockPosition{x=-106, y=47, z=97},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-106, y=47, z=98},type=NOTE_BLOCK,data=Block{minecraft:note_block}[instrument=basedrum,note=9,powered=false],fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b} -> CraftBlock{pos=BlockPosition{x=-106, y=47, z=99},type=AIR,data=Block{minecraft:air},fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b}
//[STDOUT] CraftBlock{pos=BlockPosition{x=-106, y=47, z=98},type=NOTE_BLOCK,data=Block{minecraft:note_block}[instrument=basedrum,note=9,powered=false],fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b} -> CraftBlock{pos=BlockPosition{x=-106, y=47, z=98},type=NOTE_BLOCK,data=Block{minecraft:note_block}[instrument=basedrum,note=9,powered=false],fluid=net.minecraft.world.level.material.FluidTypeEmpty@1531ed7b}
}
@EventHandler
public void onPistonRetract(BlockPistonRetractEvent e) {
if (Region.getRegion(e.getBlock().getLocation()).get(Flag.FREEZE) == FreezeMode.ACTIVE) {

View File

@@ -51,7 +51,7 @@ public class TpsLib implements LuaLib {
tpsLib.set("fiveMinute", getter(() -> TPSWatcher.getTPS(TPSWatcher.TPSType.FIVE_MINUTES)));
tpsLib.set("tenMinute", getter(() -> TPSWatcher.getTPS(TPSWatcher.TPSType.TEN_MINUTES)));
tpsLib.set("current", getter(TPSWatcher::getTPS));
tpsLib.set("limit", getter(tpsSystem::getCurrentTPSLimit));
tpsLib.set("limit", getter(TPSSystem::getCurrentTPSLimit));
return tpsLib;
}
}

View File

@@ -76,7 +76,7 @@ public class SimulatorCommand extends SWCommand {
@Register(value = "start", description = "SIMULATOR_START_HELP")
public void start(@Validator Player p, @ErrorMessage("SIMULATOR_NOT_EXISTS") Simulator simulator) {
SimulatorExecutor.run(simulator);
SimulatorExecutor.run(simulator, () -> {});
}
@Register(value = "rename", description = "SIMULATOR_RENAME_HELP")

View File

@@ -168,9 +168,19 @@ public class SimulatorCursor implements Listener {
}
return;
}
Simulator simulator = SimulatorStorage.getSimulator(player);
SimulatorWatcher.show(simulator, player);
Simulator simulator = SimulatorStorage.getSimulator(player);
if (simulator != null && simulator.getStabGenerator() != null) {
removeCursor(player);
SimulatorWatcher.show(null, player);
SWUtils.sendToActionbar(player, "§cGenerating Stab");
synchronized (calculating) {
calculating.remove(player);
}
return;
}
SimulatorWatcher.show(simulator, player);
List<REntity> entities = SimulatorWatcher.getEntitiesOfSimulator(simulator);
RayTraceUtils.RRayTraceResult rayTraceResult = RayTraceUtils.traceREntity(player, player.getLocation(), entities);
if (rayTraceResult == null) {
@@ -357,7 +367,7 @@ public class SimulatorCursor implements Listener {
if (simulator == null) {
return;
}
SimulatorExecutor.run(simulator);
SimulatorExecutor.run(simulator, () -> {});
return;
}

View File

@@ -20,6 +20,7 @@
package de.steamwar.bausystem.features.simulator.data;
import de.steamwar.bausystem.features.simulator.execute.SimulatorAction;
import de.steamwar.bausystem.features.simulator.execute.SimulatorStabGenerator;
import de.steamwar.inventory.InvCallback;
import de.steamwar.inventory.SWItem;
import lombok.Getter;
@@ -30,13 +31,13 @@ import org.bukkit.entity.Player;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
@Getter
@Setter
@RequiredArgsConstructor
public final class Simulator {
private SimulatorStabGenerator stabGenerator = null;
private Material material = Material.BARREL;
private final String name;
private boolean autoTrace = false;

View File

@@ -60,6 +60,7 @@ public final class TNTPhase extends SimulatorPhase {
@Override
public void toSimulatorActions(Vector position, BiConsumer<Integer, SimulatorAction> tickStart, BiConsumer<Integer, SimulatorAction> tickEnd) {
if (count <= 0) return;
tickStart.accept(tickOffset, new SimulatorAction(order, count) {
@Override
public void accept(World world) {

View File

@@ -0,0 +1,34 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2020 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.features.simulator.execute;
import lombok.AllArgsConstructor;
import org.bukkit.util.Vector;
import java.util.function.Function;
@AllArgsConstructor
public enum Direction {
X(Vector::getBlockX),
Y(Vector::getBlockY),
Z(Vector::getBlockZ);
public final Function<Vector, Integer> component;
}

View File

@@ -46,7 +46,7 @@ public class SimulatorExecutor implements Listener {
private static Map<Long, Map<Integer, List<SimulatorAction>>> tickStartActions = new HashMap<>();
private static Map<Long, List<SimulatorAction>> tickEndActions = new HashMap<>();
public static boolean run(Simulator simulator) {
public static boolean run(Simulator simulator, Runnable onEnd) {
if (currentlyRunning.contains(simulator)) return false;
currentlyRunning.add(simulator);
@@ -69,7 +69,7 @@ public class SimulatorExecutor implements Listener {
public void accept(World world) {
currentlyRunning.remove(simulator);
if (simulator.isAutoTrace()) {
if (simulator.isAutoTrace() && onEnd == null) {
simulator.getGroups()
.stream()
.map(SimulatorGroup::getElements)
@@ -82,10 +82,12 @@ public class SimulatorExecutor implements Listener {
TraceRecorder.instance.stopRecording(region);
});
}
onEnd.run();
}
});
if (simulator.isAutoTrace()) {
if (simulator.isAutoTrace() && onEnd == null) {
simulator.getGroups()
.stream()
.map(SimulatorGroup::getElements)

View File

@@ -0,0 +1,45 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2020 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.features.simulator.execute;
import de.steamwar.bausystem.features.simulator.data.Simulator;
import de.steamwar.bausystem.features.simulator.data.tnt.TNTElement;
import de.steamwar.bausystem.region.Region;
import de.steamwar.bausystem.utils.bossbar.BossBarService;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
public class SimulatorStabGenerator {
private final StabData stabData;
public SimulatorStabGenerator(Region region, Simulator simulator, TNTElement tntElement, int depthLimit) {
stabData = new StabData(region, simulator, tntElement, tntElement.getPhases(), depthLimit);
new StabSetup(stabData);
}
public void cancel() {
stabData.cancel = true;
stabData.simulator.setStabGenerator(null);
for (Player player : Bukkit.getOnlinePlayers()) {
BossBarService.instance.remove(player, stabData.region, "simulator_stab_generator");
}
}
}

View File

@@ -0,0 +1,52 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2020 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.features.simulator.execute;
import com.sk89q.worldedit.extent.clipboard.Clipboard;
import de.steamwar.bausystem.features.simulator.data.Simulator;
import de.steamwar.bausystem.features.simulator.data.tnt.TNTElement;
import de.steamwar.bausystem.features.simulator.data.tnt.TNTPhase;
import de.steamwar.bausystem.region.Region;
import lombok.RequiredArgsConstructor;
import java.util.List;
import java.util.logging.Level;
@RequiredArgsConstructor
public class StabData {
protected static final int MAX_RECORDINGS = 5;
protected static final int MAX_TICK_DIFFERENCE = 3;
protected static final Level LEVEL = Level.INFO;
protected static final int TNT_INCREASE = 10;
protected static final int MIN_BLOCK_TO_COUNT_AS_DEPTH = 20;
protected final Region region;
protected final Simulator simulator;
protected final TNTElement tntElement;
protected final List<TNTPhase> phases;
protected final int depthLimit;
protected Clipboard clipboard;
protected boolean cancel = false;
protected Direction direction = null;
protected int currentDepth = 0;
}

View File

@@ -0,0 +1,103 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2020 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.features.simulator.execute;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.bukkit.BukkitWorld;
import com.sk89q.worldedit.regions.CuboidRegion;
import com.sk89q.worldedit.world.block.BlockTypes;
import de.steamwar.bausystem.features.simulator.data.SimulatorPhase;
import de.steamwar.bausystem.features.tracer.TNTPoint;
import de.steamwar.bausystem.features.tracer.Trace;
import de.steamwar.bausystem.features.tracer.TraceManager;
import de.steamwar.bausystem.features.tracer.TraceRecorder;
import de.steamwar.bausystem.utils.bossbar.BauSystemBossbar;
import org.bukkit.Bukkit;
import org.bukkit.util.Vector;
import java.util.List;
import java.util.Objects;
public class StabDirection extends StabStep {
public StabDirection(StabData data) {
super(data);
}
@Override
protected void start() {
try (EditSession e = WorldEdit.getInstance().getEditSessionFactory().getEditSession(new BukkitWorld(Bukkit.getWorlds().get(0)), -1)) {
e.setBlocks((com.sk89q.worldedit.regions.Region) new CuboidRegion(data.region.getMinPointTestblockExtension().toBlockVector3(), data.region.getMaxPointTestblockExtension().toBlockVector3()), Objects.requireNonNull(BlockTypes.AIR).getDefaultState().toBaseBlock());
}
Trace trace = TraceRecorder.instance.startRecording(data.region);
runSimulator(() -> {
TraceRecorder.instance.stopRecording(data.region);
calculateDirection(trace);
});
}
private void calculateDirection(Trace trace) {
long tickSinceStart = -1;
List<TNTPoint> points = null;
for (List<TNTPoint> current : trace.getHistories()) {
long ticks = current.get(0).getTicksSinceStart();
if (points == null || ticks > tickSinceStart) {
tickSinceStart = ticks;
points = current;
} else if (ticks == tickSinceStart && points.get(0).getTntId() < current.get(0).getTntId()) {
points = current;
}
}
TraceManager.instance.remove(trace);
if (points == null) {
stop();
return;
}
TNTPoint current = points.getLast();
Vector velocity = current.getVelocity();
if (velocity.getX() < 0) velocity.setX(-velocity.getX());
if (velocity.getY() < 0) velocity.setY(-velocity.getY());
if (velocity.getZ() < 0) velocity.setZ(-velocity.getZ());
if (velocity.getX() > velocity.getY() && velocity.getX() > velocity.getZ()) {
data.direction = Direction.X;
} else if (velocity.getY() > velocity.getX() && velocity.getY() > velocity.getZ()) {
data.direction = Direction.Y;
} else if (velocity.getZ() > velocity.getX() && velocity.getZ() > velocity.getY()) {
data.direction = Direction.Z;
} else {
stop();
return;
}
Bukkit.getLogger().log(StabData.LEVEL, "Direction: {0}", data.direction);
data.phases.getFirst().setOrder(SimulatorPhase.ORDER_LIMIT);
data.phases.getFirst().setCount(StabData.TNT_INCREASE);
new StabGenerator(data);
}
@Override
protected void bossbar(BauSystemBossbar bossbar, boolean finished) {
bossbar.setProgress(0);
bossbar.setTitle("§eCalculating Stab Direction");
}
}

View File

@@ -0,0 +1,61 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2020 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.features.simulator.execute;
import de.steamwar.bausystem.features.tracer.Trace;
import de.steamwar.bausystem.features.tracer.TraceRecorder;
import de.steamwar.bausystem.region.flags.Flag;
import de.steamwar.bausystem.region.flags.flagvalues.ColorMode;
import de.steamwar.bausystem.region.utils.RegionExtensionType;
import de.steamwar.bausystem.region.utils.RegionType;
import de.steamwar.bausystem.utils.PasteBuilder;
import de.steamwar.bausystem.utils.bossbar.BauSystemBossbar;
public class StabFinalizer extends StabStep {
public StabFinalizer(StabData data) {
super(data);
}
@Override
protected void start() {
try {
PasteBuilder.ClipboardProvider clipboardProvider = new PasteBuilder.ClipboardProviderImpl(data.clipboard);
PasteBuilder pasteBuilder = new PasteBuilder(clipboardProvider)
.color(data.region.getPlain(Flag.COLOR, ColorMode.class).getColor());
data.region.reset(pasteBuilder, RegionType.TESTBLOCK, RegionExtensionType.EXTENSION);
} catch (SecurityException e) {
stop();
throw e;
}
TraceRecorder.instance.startRecording(data.region);
runSimulator(() -> {
TraceRecorder.instance.stopRecording(data.region);
stop();
});
}
@Override
protected void bossbar(BauSystemBossbar bossbar, boolean stopped) {
bossbar.setProgress(Math.min(data.currentDepth / (double) data.depthLimit, 1.0));
bossbar.setTitle("§e" + data.currentDepth + "§8/§7" + data.depthLimit);
}
}

View File

@@ -0,0 +1,250 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2020 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.features.simulator.execute;
import de.steamwar.bausystem.features.simulator.data.tnt.TNTPhase;
import de.steamwar.bausystem.region.Region;
import de.steamwar.bausystem.region.flags.Flag;
import de.steamwar.bausystem.region.flags.flagvalues.ColorMode;
import de.steamwar.bausystem.region.utils.RegionExtensionType;
import de.steamwar.bausystem.region.utils.RegionType;
import de.steamwar.bausystem.utils.PasteBuilder;
import de.steamwar.bausystem.utils.bossbar.BauSystemBossbar;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.event.EventHandler;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityExplodeEvent;
import java.util.*;
import java.util.stream.Collectors;
import static de.steamwar.bausystem.features.simulator.execute.Direction.Y;
public class StabGenerator extends StabStep implements Listener {
private int recordings = 0;
private List<Integer> currentDepths = new ArrayList<>();
private int lastDepth = 0;
private int retries = 0;
private final Map<Integer, Set<Location>> destroyedBlocksPerSlice = new HashMap<>();
private Set<Integer> gabStart = new HashSet<>();
private Set<TNTPhase> failedAtLeastOnce = new HashSet<>();
@EventHandler
public void onEntityExplode(EntityExplodeEvent event) {
if (Region.getRegion(event.getEntity().getLocation()) == data.region) {
event.blockList().forEach(block -> {
if (!data.region.inRegion(block.getLocation(), RegionType.TESTBLOCK, RegionExtensionType.EXTENSION)) return;
int component = data.direction.component.apply(block.getLocation().toVector());
destroyedBlocksPerSlice.computeIfAbsent(component, __ -> new HashSet<>())
.add(block.getLocation());
});
}
}
public StabGenerator(StabData data) {
super(data);
}
@Override
protected void start() {
try {
PasteBuilder.ClipboardProvider clipboardProvider = new PasteBuilder.ClipboardProviderImpl(data.clipboard);
PasteBuilder pasteBuilder = new PasteBuilder(clipboardProvider)
.color(data.region.getPlain(Flag.COLOR, ColorMode.class).getColor());
data.region.reset(pasteBuilder, RegionType.TESTBLOCK, RegionExtensionType.EXTENSION);
} catch (SecurityException e) {
stop();
throw e;
}
if (data.cancel) {
HandlerList.unregisterAll(this);
return;
}
runSimulator(this::calculateStab);
}
private void calculateStab() {
TNTPhase lastPhase = data.phases.getLast();
List<Map.Entry<Integer, Set<Location>>> locations = destroyedBlocksPerSlice.entrySet()
.stream()
.sorted(Comparator.comparingInt(Map.Entry::getKey))
.collect(Collectors.toList());
int depth = 0;
for (int i = 0; i < locations.size(); i++) {
if (data.direction != Y && i > 0 && Math.abs(locations.get(i - 1).getKey() - locations.get(i).getKey()) > 3) {
if (gabStart.add(locations.get(i).getKey())) {
Bukkit.getLogger().log(StabData.LEVEL, "Increasing tnt count by {0} because of gap", StabData.TNT_INCREASE);
lastPhase.setCount(lastPhase.getCount() + StabData.TNT_INCREASE);
recordings = 0;
currentDepths.clear();
run();
return;
}
}
if (i == 0 || i == locations.size() - 1) {
if (locations.get(i).getValue().size() > StabData.MIN_BLOCK_TO_COUNT_AS_DEPTH) {
depth++;
}
} else {
depth++;
}
}
if (depth > 0) {
Bukkit.getLogger().log(StabData.LEVEL, "{0} {1} {2}", new Object[]{depth, destroyedBlocksPerSlice.size(), destroyedBlocksPerSlice.values().stream().map(Set::size).collect(Collectors.toList())});
destroyedBlocksPerSlice.clear();
currentDepths.add(depth);
} else {
destroyedBlocksPerSlice.clear();
lastPhase.setTickOffset(lastPhase.getTickOffset() + 1);
Bukkit.getLogger().log(StabData.LEVEL, "No dimension - Increasing tickOffset to: {0}", lastPhase.getTickOffset());
lastPhase.setOrder(0);
if (lastPhase.getTickOffset() > 80) {
stop();
} else {
run();
}
return;
}
int minDepth = currentDepths.stream().min(Integer::compareTo).orElse(0);
int maxDepth = currentDepths.stream().max(Integer::compareTo).orElse(0);
data.currentDepth = maxDepth;
int countWithoutLast = 0;
for (int i = 0; i < data.phases.size() - 1; i++) {
countWithoutLast += data.phases.get(i).getCount();
}
countWithoutLast -= gabStart.size();
boolean moreTNTNeeded = maxDepth - countWithoutLast >= lastPhase.getCount() - 5;
if (moreTNTNeeded) {
Bukkit.getLogger().log(StabData.LEVEL, "Increasing tnt count by {0}", StabData.TNT_INCREASE);
lastPhase.setCount(lastPhase.getCount() + StabData.TNT_INCREASE);
recordings = 0;
currentDepths.clear();
run();
return;
}
if (recordings++ < StabData.MAX_RECORDINGS) {
run();
return;
}
recordings = 0;
currentDepths.clear();
if (maxDepth - minDepth > lastPhase.getCount()) {
Bukkit.getLogger().log(StabData.LEVEL, "Stab failed at least once. Adding one tnt to {0}", minDepth - 3);
int current = 0;
TNTPhase last = null;
for (TNTPhase phase : data.phases) {
if (current < minDepth - 3) {
current += phase.getCount();
last = phase;
continue;
}
if (failedAtLeastOnce.add(last)) {
last = null;
break;
}
if (last != null) {
last.setCount(last.getCount() + 1);
Bukkit.getLogger().log(StabData.LEVEL, "Added to phase {0} now has {1} tnt", new Object[]{data.phases.indexOf(last), last.getCount()});
}
break;
}
if (last != null) {
run();
return;
}
}
Bukkit.getLogger().log(StabData.LEVEL, "No more TNT needed on phase adjusting - {0} new count; {1} current count", new Object[]{maxDepth - countWithoutLast, lastPhase.getCount()});
lastPhase.setCount(maxDepth - countWithoutLast);
if (lastPhase.getCount() <= 0) {
Bukkit.getLogger().log(StabData.LEVEL, "Count was 0 or negative - removing last phase");
data.phases.removeLast();
}
if (maxDepth > data.depthLimit) {
Bukkit.getLogger().log(StabData.LEVEL, "Depth is greater than {0} - finished", data.depthLimit);
new StabFinalizer(data);
return;
}
if (maxDepth <= lastDepth) {
if (lastPhase.getCount() <= 0) {
retries++;
}
if (lastPhase.getCount() > 0 || retries > StabData.MAX_TICK_DIFFERENCE) {
Bukkit.getLogger().log(StabData.LEVEL, "Depth is equal to last depth recorded {0} - finished", maxDepth);
new StabFinalizer(data);
return;
}
}
lastDepth = maxDepth;
newPhase(data, lastPhase);
run();
}
public static void newPhase(StabData data, TNTPhase lastPhase) {
Bukkit.getLogger().log(StabData.LEVEL, "Adding new phase in next tick");
TNTPhase nextPhase = new TNTPhase();
nextPhase.setCount(StabData.TNT_INCREASE);
nextPhase.setTickOffset(lastPhase.getTickOffset() + 1);
nextPhase.setXJump(lastPhase.isXJump());
nextPhase.setYJump(lastPhase.isYJump());
nextPhase.setZJump(lastPhase.isZJump());
data.phases.add(nextPhase);
}
@Override
protected void bossbar(BauSystemBossbar bossbar, boolean finished) {
bossbar.setProgress(Math.min(data.currentDepth / (double) data.depthLimit, 1.0));
if (finished) {
bossbar.setTitle("§e" + data.currentDepth + "§8/§7" + data.depthLimit);
return;
}
StringBuilder st = new StringBuilder();
st.append("§7Direction §e").append(data.direction);
st.append(" §e").append(data.currentDepth).append("§8/§7").append(data.depthLimit);
if (recordings > 0) {
st.append(" §7Retries§8:§e ").append(recordings).append("§8/§7").append(StabData.MAX_RECORDINGS);
}
bossbar.setTitle(st.toString());
}
}

View File

@@ -0,0 +1,85 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2020 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.features.simulator.execute;
import de.steamwar.bausystem.features.simulator.data.SimulatorGroup;
import de.steamwar.bausystem.features.simulator.data.tnt.TNTElement;
import de.steamwar.bausystem.features.simulator.data.tnt.TNTPhase;
import de.steamwar.bausystem.features.tracer.TraceRecorder;
import de.steamwar.bausystem.utils.FlatteningWrapper;
import de.steamwar.bausystem.utils.bossbar.BauSystemBossbar;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
public class StabSetup extends StabStep {
public StabSetup(StabData data) {
super(data);
}
@Override
protected void start() {
TNTPhase tntPhase = data.simulator.getGroups().stream()
.filter(simulatorGroup -> !simulatorGroup.isDisabled())
.map(SimulatorGroup::getElements)
.flatMap(List::stream)
.filter(TNTElement.class::isInstance)
.map(TNTElement.class::cast)
.filter(tntElement -> !tntElement.isDisabled())
.filter(tntElement -> data.tntElement != tntElement)
.map(tntElement -> tntElement.getPhases().stream().max(Comparator.comparingInt(TNTPhase::getTickOffset)))
.filter(Optional::isPresent)
.map(Optional::get)
.peek(phase -> {
if (phase.getOrder() > TNTPhase.ORDER_LIMIT) {
phase.setOrder(TNTPhase.ORDER_LIMIT);
}
})
.filter(phase -> phase != data.phases.get(0))
.max(Comparator.comparingInt(TNTPhase::getTickOffset))
.orElse(null);
if (tntPhase == null) {
throw new SecurityException("");
}
TNTPhase phase = data.phases.get(0);
data.phases.clear();
data.phases.add(phase);
phase.setCount(1);
phase.setTickOffset(tntPhase.getTickOffset());
phase.setOrder(100);
TraceRecorder.instance.stopRecording(data.region);
if (TraceRecorder.instance.isAutoTraceEnabledInRegion(data.region)) {
TraceRecorder.instance.removeAutoTraceRegion(data.region);
}
data.clipboard = FlatteningWrapper.impl.copy(data.region.getMinPointTestblockExtension(), data.region.getMaxPointTestblockExtension(), data.region.getTestBlockPoint());
new StabDirection(data);
}
@Override
protected void bossbar(BauSystemBossbar bossbar, boolean finished) {
bossbar.setProgress(0);
bossbar.setTitle("§eSetting up Simulator");
}
}

View File

@@ -0,0 +1,95 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2020 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.bausystem.features.simulator.execute;
import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.features.simulator.SimulatorWatcher;
import de.steamwar.bausystem.utils.bossbar.BauSystemBossbar;
import de.steamwar.bausystem.utils.bossbar.BossBarService;
import org.bukkit.Bukkit;
import org.bukkit.boss.BarColor;
import org.bukkit.boss.BarStyle;
import org.bukkit.entity.Player;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
public abstract class StabStep {
protected final StabData data;
protected StabStep(StabData data) {
this.data = data;
run();
}
protected final void run() {
Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> {
for (Player player : Bukkit.getOnlinePlayers()) {
BauSystemBossbar bossbar = BossBarService.instance.get(player, data.region, "simulator_stab_generator");
bossbar.setColor(BarColor.GREEN);
bossbar.setStyle(BarStyle.SEGMENTED_10);
bossbar(bossbar, false);
}
}, 1);
if (this instanceof Listener) {
Bukkit.getPluginManager().registerEvents((Listener) this, BauSystem.getInstance());
}
Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), this::start, 20);
}
protected abstract void start();
protected final void runSimulator(Runnable onFinish) {
SimulatorExecutor.run(data.simulator, () -> {
Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> {
if (this instanceof Listener) {
HandlerList.unregisterAll((Listener) this);
}
onFinish.run();
}, 20);
});
}
protected abstract void bossbar(BauSystemBossbar bossbar, boolean stopped);
protected final void stop() {
data.simulator.setStabGenerator(null);
SimulatorWatcher.update(data.simulator);
for (Player player : Bukkit.getOnlinePlayers()) {
BauSystemBossbar bossbar = BossBarService.instance.get(player, data.region, "simulator_stab_generator");
bossbar.setColor(BarColor.GREEN);
bossbar.setStyle(BarStyle.SEGMENTED_10);
bossbar(bossbar, true);
}
new Thread(() -> {
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
for (Player player : Bukkit.getOnlinePlayers()) {
BossBarService.instance.remove(player, data.region, "simulator_stab_generator");
}
}
}).start();
}
}

View File

@@ -24,8 +24,11 @@ import de.steamwar.bausystem.features.simulator.data.Simulator;
import de.steamwar.bausystem.features.simulator.data.SimulatorGroup;
import de.steamwar.bausystem.features.simulator.data.tnt.TNTElement;
import de.steamwar.bausystem.features.simulator.data.tnt.TNTPhase;
import de.steamwar.bausystem.features.simulator.execute.SimulatorStabGenerator;
import de.steamwar.bausystem.features.simulator.gui.base.SimulatorAnvilGui;
import de.steamwar.bausystem.features.simulator.gui.base.SimulatorBaseGui;
import de.steamwar.bausystem.features.simulator.gui.base.SimulatorScrollGui;
import de.steamwar.bausystem.region.Region;
import de.steamwar.inventory.SWItem;
import org.bukkit.Material;
import org.bukkit.entity.Player;
@@ -97,6 +100,14 @@ public class SimulatorTNTGui extends SimulatorScrollGui<TNTPhase> {
tnt.setDisabled(!tnt.isDisabled());
SimulatorWatcher.update(simulator);
}));
inventory.setItem(49, new SWItem(Material.CALIBRATED_SCULK_SENSOR, "§eCreate Stab", click -> {
new SimulatorAnvilGui<>(player, "Depth Limit", "", Integer::parseInt, depthLimit -> {
if (depthLimit <= 0) return false;
simulator.setStabGenerator(new SimulatorStabGenerator(Region.getRegion(player.getLocation()), simulator, tnt, depthLimit));
SimulatorWatcher.update(simulator);
return true;
}, null).open();
}));
inventory.setItem(50, new SWItem(Material.CHEST, parent.getElements().size() == 1 ? "§eMake Group" : "§eAdd another TNT to Group", clickType -> {
TNTElement tntElement = new TNTElement(tnt.getPosition().clone());
tntElement.add(new TNTPhase());

View File

@@ -44,7 +44,7 @@ public class SimulatorAnvilGui<T extends Number> {
if (error.get()) {
anvilInv.open();
} else {
back.open();
if (back != null) back.open();
}
error.set(false);
}, 0);

View File

@@ -22,7 +22,6 @@ package de.steamwar.bausystem.features.simulator.gui.base;
import de.steamwar.bausystem.features.simulator.SimulatorWatcher;
import de.steamwar.bausystem.features.simulator.data.Simulator;
import de.steamwar.core.Core;
import de.steamwar.core.TrickyTrialsWrapper;
import de.steamwar.inventory.SWInventory;
import de.steamwar.inventory.SWItem;
import org.bukkit.Bukkit;
@@ -64,7 +63,11 @@ public abstract class SimulatorBaseGui {
if (Core.getVersion() > 19) {
player.getOpenInventory().setTitle(title());
}
populate();
if (simulator != null && simulator.getStabGenerator() != null) {
populateStabGenerator();
} else {
populate();
}
if (player.getOpenInventory().getTopInventory() == inv) {
inventory.open();
SimulatorWatcher.watch(player, simulator, this::open);
@@ -84,10 +87,21 @@ public abstract class SimulatorBaseGui {
});
SimulatorWatcher.watch(player, simulator, this::open);
populate();
if (simulator != null && simulator.getStabGenerator() != null) {
populateStabGenerator();
} else {
populate();
}
inventory.open();
}
private void populateStabGenerator() {
inventory.setItem(22, new SWItem(Material.BARRIER, "§cCancel Stab Generator", click -> {
simulator.getStabGenerator().cancel();
SimulatorWatcher.update(simulator);
}));
}
public boolean shouldOpen() {
return true;
}

View File

@@ -19,8 +19,8 @@
package de.steamwar.bausystem.features.smartplace;
import de.steamwar.Reflection;
import com.comphenix.tinyprotocol.TinyProtocol;
import de.steamwar.Reflection;
import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.Permission;
import de.steamwar.bausystem.configplayer.Config;
@@ -223,7 +223,7 @@ public class SmartPlaceListener implements Listener {
Block block = event.getBlock().getRelative(BlockFace.DOWN);
BlockState old = block.getState();
Bukkit.getScheduler().runTaskLater(BauSystem.getInstance(), () -> {
block.setType(Material.GLASS, false);
block.setType(Material.GLASS, true);
old.update(true, false);
}, 1);
}

View File

@@ -42,16 +42,16 @@ public class TPSCommand extends SWCommand {
public void genericCommand(Player p, String... args) {
BauSystem.MESSAGE.sendPrefixless("OTHER_TPS_HEAD", p);
BauSystem.MESSAGE.sendPrefixless("OTHER_TPS_MESSAGE", p,
TPSWatcher.getTPS(TPSWatcher.TPSType.ONE_SECOND),
TPSWatcher.getTPS(TPSWatcher.TPSType.TEN_SECONDS),
TPSWatcher.getTPS(TPSWatcher.TPSType.ONE_MINUTE),
TPSWatcher.getTPS(TPSWatcher.TPSType.FIVE_MINUTES),
TPSWatcher.getTPS(TPSWatcher.TPSType.TEN_MINUTES)
TPSWatcher.getTPSUnlimited(TPSWatcher.TPSType.ONE_SECOND),
TPSWatcher.getTPSUnlimited(TPSWatcher.TPSType.TEN_SECONDS),
TPSWatcher.getTPSUnlimited(TPSWatcher.TPSType.ONE_MINUTE),
TPSWatcher.getTPSUnlimited(TPSWatcher.TPSType.FIVE_MINUTES),
TPSWatcher.getTPSUnlimited(TPSWatcher.TPSType.TEN_MINUTES)
);
}
@Register
public void genericCommand(Player p, TPSWatcher.TPSType type) {
BauSystem.MESSAGE.sendPrefixless("OTHER_TPS_SINGLE", p, tpsSystem.getTPS(type));
BauSystem.MESSAGE.sendPrefixless("OTHER_TPS_SINGLE", p, TPSWatcher.getTPSUnlimited(type));
}
}

View File

@@ -56,11 +56,7 @@ import java.util.Arrays;
public class TPSSystem implements Listener {
@Getter
private double currentTPSLimit = 20;
public double getTPS(TPSWatcher.TPSType tpsType) {
return TPSWatcher.getTPSUnlimited(tpsType);
}
private static double currentTPSLimit = 20;
public TPSSystem() {
if (TPSFreezeUtils.isCanFreeze()) {
@@ -338,26 +334,26 @@ public class TPSSystem implements Listener {
} else if (TPSFreezeUtils.frozen()) {
return "§e" + BauSystem.MESSAGE.parse("SCOREBOARD_TPS", p) + "§8: " + BauSystem.MESSAGE.parse("SCOREBOARD_TPS_FROZEN", p);
} else {
return "§e" + BauSystem.MESSAGE.parse("SCOREBOARD_TPS", p) + "§8: " + tpsColor() + tpsSystem.getTPS(TPSWatcher.TPSType.ONE_SECOND) + tpsLimit();
return "§e" + BauSystem.MESSAGE.parse("SCOREBOARD_TPS", p) + "§8: " + tpsColor() + TPSWatcher.getTPSUnlimited(TPSWatcher.TPSType.ONE_SECOND) + tpsLimit();
}
}
private String tpsColor() {
double tps = tpsSystem.getTPS(TPSWatcher.TPSType.ONE_SECOND);
if (tps > tpsSystem.getCurrentTPSLimit() * 0.9) {
double tps = TPSWatcher.getTPSUnlimited(TPSWatcher.TPSType.ONE_SECOND);
if (tps > TPSSystem.getCurrentTPSLimit() * 0.9) {
return "§a";
}
if (tps > tpsSystem.getCurrentTPSLimit() * 0.5) {
if (tps > TPSSystem.getCurrentTPSLimit() * 0.5) {
return "§e";
}
return "§c";
}
private String tpsLimit() {
if (tpsSystem.getCurrentTPSLimit() == 20) {
if (TPSSystem.getCurrentTPSLimit() == 20) {
return "";
}
return "§8/§7" + tpsSystem.getCurrentTPSLimit();
return "§8/§7" + TPSSystem.getCurrentTPSLimit();
}
}

View File

@@ -102,11 +102,14 @@ public class TraceRecorder implements Listener {
*
* @param region region to be recorded
*/
public void startRecording(Region region) {
if (activeTraces.containsKey(region)) return;
public Trace startRecording(Region region) {
if (activeTraces.containsKey(region)) {
return activeTraces.get(region).getTrace();
}
TraceRecordingWrapper wrappedTrace = new TraceRecordingWrapper(region);
activeTraces.put(region, wrappedTrace);
return wrappedTrace.getTrace();
}
/**

View File

@@ -20,6 +20,7 @@
package de.steamwar.bausystem.features.tracer.rendering;
import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.configplayer.Config;
import de.steamwar.bausystem.features.tracer.TNTPoint;
import de.steamwar.bausystem.features.tracer.Trace;
import de.steamwar.bausystem.features.tracer.TraceManager;
@@ -30,10 +31,13 @@ import net.md_5.bungee.api.chat.ClickEvent;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import yapion.hierarchy.types.YAPIONValue;
import java.util.List;
import java.util.stream.Collectors;
import static de.steamwar.bausystem.features.util.TNTClickListener.TNT_CLICK_DETAILS;
/**
* Wrapper for the rendering of a record bundle
*/
@@ -66,6 +70,7 @@ public class TraceEntity extends RFallingBlockEntity {
* @param player the player the message should be printed for
*/
public void printIntoChat(Player player) {
if (!Config.getInstance().get(player).getOrSetDefault(TNT_CLICK_DETAILS, new YAPIONValue<>(true)).asBoolean().orElse(true)) return;
TNTPoint representative = records.get(0);
BauSystem.MESSAGE.sendPrefixless("TNT_CLICK_HEADER", player);

View File

@@ -21,22 +21,46 @@ package de.steamwar.bausystem.features.util;
import de.steamwar.bausystem.BauSystem;
import de.steamwar.bausystem.Permission;
import de.steamwar.bausystem.configplayer.Config;
import de.steamwar.command.SWCommand;
import de.steamwar.linkage.Linked;
import org.bukkit.Bukkit;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.entity.TNTPrimed;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerInteractEntityEvent;
import org.bukkit.inventory.EquipmentSlot;
import yapion.hierarchy.types.YAPIONObject;
import yapion.hierarchy.types.YAPIONValue;
@Linked
public class TNTClickListener implements Listener {
public class TNTClickListener extends SWCommand implements Listener {
public static final String TNT_CLICK_DETAILS = "tnt_click_details";
public TNTClickListener() {
super("tntdetails");
}
@Register(description = "TNT_DETAILS_COMMAND")
public void toggle(Player player) {
YAPIONObject yapionObject = Config.getInstance().get(player);
if (yapionObject.getOrSetDefault(TNT_CLICK_DETAILS, new YAPIONValue<>(true)).asBoolean().orElse(true)) {
yapionObject.put(TNT_CLICK_DETAILS, false);
BauSystem.MESSAGE.send("TNT_DETAILS_OFF", player);
} else {
yapionObject.put(TNT_CLICK_DETAILS, true);
BauSystem.MESSAGE.send("TNT_DETAILS_ON", player);
}
}
@EventHandler
public void onPlayerInteractEntity(PlayerInteractEntityEvent event) {
if (!Permission.BUILD.hasPermission(event.getPlayer())) return;
if (event.getHand() != EquipmentSlot.HAND) return;
if (!Config.getInstance().get(event.getPlayer()).getOrSetDefault(TNT_CLICK_DETAILS, new YAPIONValue<>(true)).asBoolean().orElse(true)) return;
Entity entity = event.getRightClicked();
if (event.getRightClicked() instanceof TNTPrimed) {

View File

@@ -73,4 +73,8 @@ public class Point {
public Location toLocation(Player player, double dx, double dy, double dz) {
return new Location(player.getWorld(), x + dx, y + dy, z + dz, player.getLocation().getYaw(), player.getLocation().getPitch());
}
public BlockVector3 toBlockVector3() {
return BlockVector3.at(this.x, this.y, this.z);
}
}

View File

@@ -184,6 +184,7 @@ public class MWTeam {
public void leave(Player p) {
if (!players.contains(p)) return;
players.remove(p);
for (ItemStack stack : p.getInventory().getContents()) {
if (stack == null) continue;
@@ -198,7 +199,6 @@ public class MWTeam {
if (players.isEmpty() && MissileWars.getFightState() == FightState.FIGHTING)
MissileWars.end(WinReasons.NO_ENEMY, enemy());
players.remove(p);
sbteam.removePlayer(p);
}

View File

@@ -33,7 +33,7 @@ public class BountifulWrapper21 extends BountifulWrapper9 {
return (packet, x, y, z, pitch, yaw) -> {
PositionMoveRotation pos = field.get(packet);
field.set(packet, new PositionMoveRotation(new Vec3D(x, y, z), pos == null ? new Vec3D(0, 0, 0) : pos.b(), yaw, pitch));
field.set(packet, new PositionMoveRotation(new Vec3D(x, y, z), pos.b(), yaw, pitch));
};
} catch (IllegalArgumentException e) {
return super.getPositionSetter(packetClass, fieldOffset);

View File

@@ -177,30 +177,9 @@ public final class Reflection {
}
public void set(Object target, Object value) {
// This now works for Fields in records!
try {
long offset = Unsafe.getUnsafe().objectFieldOffset(f);
Class<?> type = f.getType();
if (type == int.class) {
Unsafe.getUnsafe().putInt(target, offset, (Integer) value);
} else if (type == float.class) {
Unsafe.getUnsafe().putFloat(target, offset, (Float) value);
} else if (type == double.class) {
Unsafe.getUnsafe().putDouble(target, offset, (Double) value);
} else if (type == boolean.class) {
Unsafe.getUnsafe().putBoolean(target, offset, (Boolean) value);
} else if (type == byte.class) {
Unsafe.getUnsafe().putByte(target, offset, (Byte) value);
} else if (type == char.class) {
Unsafe.getUnsafe().putChar(target, offset, (Character) value);
} else if (type == short.class) {
Unsafe.getUnsafe().putShort(target, offset, (Short) value);
} else if (type == long.class) {
Unsafe.getUnsafe().putLong(target, offset, (Long) value);
} else {
Unsafe.getUnsafe().putReference(target, offset, value);
}
} catch (Exception e) {
f.set(target, value);
} catch (IllegalAccessException e) {
throw new IllegalArgumentException("Cannot write field", e);
}
}

View File

@@ -24,6 +24,7 @@ import de.steamwar.Reflection;
import de.steamwar.command.*;
import de.steamwar.core.authlib.AuthlibInjector;
import de.steamwar.core.events.*;
import de.steamwar.inventory.ui.test.TestInvCommand;
import de.steamwar.message.Message;
import de.steamwar.network.NetworkReceiver;
import de.steamwar.network.handlers.ServerDataHandler;
@@ -67,6 +68,7 @@ public class Core extends JavaPlugin{
@Override
public void onEnable() {
new TestInvCommand();
errorHandler = new ErrorHandler();
crashDetector = new CrashDetector();

View File

@@ -396,26 +396,10 @@ public class REntity {
public static final Class<?> teleportPacket = Reflection.getClass("net.minecraft.network.protocol.game.ClientboundTeleportEntityPacket");
public static final Reflection.Field<Integer> teleportEntity = Reflection.getField(teleportPacket, int.class, 0);
public static final BountifulWrapper.PositionSetter teleportPosition = BountifulWrapper.impl.getPositionSetter(teleportPacket, Core.getVersion() == 8 ? 1 : 0);
public static final Class<?> relative;
public static final Reflection.Field<Set> relatives;
static {
if (Core.getVersion() >= 21) {
relative = Reflection.getClass("net.minecraft.world.entity.Relative");
relatives = Reflection.getField(teleportPacket, Set.class, 0);
} else {
relative = null;
relatives = null;
}
}
private Object getTeleportPacket(){
Object packet = Reflection.newInstance(teleportPacket);
teleportEntity.set(packet, entityId);
teleportPosition.set(packet, x, y, z, pitch, yaw);
if (Core.getVersion() >= 21) {
// https://mappings.dev/1.21.3/net/minecraft/world/entity/Relative.html
Object[] constants = relative.getEnumConstants();
relatives.set(packet, new HashSet<>(Arrays.asList(constants[0], constants[1], constants[2], constants[3], constants[4], constants[5])));
}
return packet;
}

View File

@@ -0,0 +1,76 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.inventory.ui;
import de.steamwar.inventory.ui.util.UpdateStream;
import org.bukkit.entity.Player;
public abstract class StatefulUIInventory<T> extends UIInventory implements UpdateStream.Subscriber<T> {
private T state;
private final UpdateStream<T> stream;
private UpdateStream.Unsubscriber unsubscriber;
protected abstract UIComponent render(T state);
protected abstract void update(T state);
public StatefulUIInventory(UpdateStream<T> stateObservable, int playerSlots) {
super(playerSlots);
this.stream = stateObservable;
}
public StatefulUIInventory(UpdateStream<T> stateObservable) {
super();
this.stream = stateObservable;
}
public void dispose() {
unsubscriber.unsubscribe();
this.unsubscriber = null;
}
@Override
public UIComponent render() {
return render(state);
}
@Override
public void onUpdate(T current) {
state = current;
update(current);
updateUI();
}
@Override
void onClose() {
if (viewers.isEmpty()) {
dispose();
}
}
@Override
public void addViewer(Player... players) {
if (viewers.isEmpty()) {
unsubscriber = stream.subscribe(this);
}
super.addViewer(players);
}
}

View File

@@ -0,0 +1,82 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.inventory.ui;
import de.steamwar.inventory.ui.item.Enchanted;
import de.steamwar.inventory.ui.item.OnClick;
import de.steamwar.inventory.ui.layout.FixedSizeContainer;
import de.steamwar.inventory.ui.util.*;
import org.bukkit.event.inventory.InventoryClickEvent;
import java.util.function.Consumer;
import java.util.function.Function;
public interface UIComponent {
ComponentRender render(RenderContext context);
default UIComponent onClick(Consumer<InventoryClickEvent> event) {
return onClick(false, event);
}
default UIComponent onClick(boolean allowDoubleClick, Consumer<InventoryClickEvent> event) {
return new OnClick(this, allowDoubleClick, inventoryClickEvent -> {
event.accept(inventoryClickEvent);
return true;
});
}
default UIComponent onClickWithUpdate(Function<InventoryClickEvent, Boolean> event) {
return onClickWithUpdate(false, event);
}
default UIComponent onClickWithUpdate(boolean allowDoubleClick, Function<InventoryClickEvent, Boolean> event) {
return new OnClick(this, allowDoubleClick, event);
}
default UIComponent onClickNoUpdate(Consumer<InventoryClickEvent> event) {
return onClickNoUpdate(false, event);
}
default UIComponent onClickNoUpdate(boolean allowDoubleClick, Consumer<InventoryClickEvent> event) {
return new OnClick(this, allowDoubleClick, inventoryClickEvent -> {
event.accept(inventoryClickEvent);
return false;
});
}
default UIComponent cancelOnClick() {
return new OnClick(this, true, inventoryClickEvent -> false);
}
default UIComponent enchanted() {
return new Enchanted(this);
}
default UIComponent enchanted(boolean enchanted) {
if (enchanted)
return enchanted();
else
return this;
}
default UIComponent size(Size size) {
return new FixedSizeContainer(size, this);
}
}

View File

@@ -0,0 +1,33 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.inventory.ui;
import de.steamwar.inventory.ui.util.ComponentRender;
import de.steamwar.inventory.ui.util.RenderContext;
public abstract class UIFragment implements UIComponent {
abstract public UIComponent build(RenderContext context);
@Override
public ComponentRender render(RenderContext context) {
return build(context).render(context);
}
}

View File

@@ -0,0 +1,242 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.inventory.ui;
import de.steamwar.core.Core;
import de.steamwar.inventory.ui.util.ComponentRender;
import de.steamwar.inventory.ui.util.RenderContext;
import de.steamwar.inventory.ui.util.RenderItem;
import de.steamwar.inventory.ui.util.Size;
import de.steamwar.message.Message;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryCloseEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryView;
import org.bukkit.inventory.ItemStack;
import java.util.*;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
public abstract class UIInventory {
protected final List<Player> viewers = new ArrayList<>();
private final Map<Player, OpenPlayerInventory> callbacks = new HashMap<>();
private final Map<Player, ItemStack[]> originalItems = new HashMap<>();
private UIInventory parent;
private Player currentRender;
private final int playerSlots;
private List<MemoHookMeta<?>> memoHooks = new ArrayList<>();
private int buildHookCount = 0;
protected UIInventory() {
this(0);
}
protected UIInventory(int playerSlots) {
this.playerSlots = playerSlots;
}
public abstract UIComponent render();
public abstract Inventory createInventory(Player player);
public abstract Message getMessages();
void onClose() { }
public <T> T useMemo(Supplier<T> supplier, List<Object> dependencies) {
int currentHook = buildHookCount++;
if (currentHook >= memoHooks.size()) {
T value = supplier.get();
memoHooks.add(new MemoHookMeta<>(dependencies, value));
return value;
}
MemoHookMeta<T> hook = (MemoHookMeta<T>) memoHooks.get(currentHook);
if (!hook.getDependencies().equals(dependencies)) {
hook.setDependencies(dependencies);
T value = supplier.get();
hook.setValue(value);
return value;
}
return hook.getValue();
}
public <T> T useMemo(Supplier<T> supplier) {
return useMemo(supplier, Collections.emptyList());
}
public void addViewer(Player... players) {
viewers.addAll(Arrays.asList(players));
for (Player player : players) {
openInventory(player);
}
}
private void openInventory(Player player) {
if (playerSlots > 0) {
originalItems.put(player, player.getInventory().getContents());
}
Inventory inventory = createInventory(player);
renderPlayer(player, inventory);
InventoryView view = player.openInventory(inventory);
Core.getInstance().getLogger().info("[UIINV] Opened " + view.getTitle() + " for " + player.getName());
}
private void renderPlayer(Player player, Inventory inventory) {
int width = Math.min(inventory.getSize(), 9);
if (width < 9 && playerSlots > 0) {
throw new IllegalArgumentException("Player Inventory is only supported in Chests");
}
int inventoryHeight = Math.max(inventory.getSize() / 9, 1);
int height = inventoryHeight + playerSlots;
int invSize = inventoryHeight * width;
currentRender = player;
buildHookCount = 0;
ComponentRender render = render().render(new RenderContext(new Size(width, height), new Size(width, height), this));
buildHookCount = -1;
currentRender = null;
if (render.getSize().getWidth() > width || render.getSize().getHeight() > height) {
throw new IllegalArgumentException("The UIComponent does not fit in the inventory");
}
Message message = getMessages();
ItemStack[] itemStacks = new ItemStack[inventory.getSize()];
ItemStack[] playerStacks = new ItemStack[playerSlots > 0 ? 4 * 9 : 0];
for (RenderItem item : render.getItems()) {
int slot = item.getX() + item.getY() * 9;
if (slot < invSize) {
itemStacks[item.getX() + item.getY() * 9] = item.getItemStack().message(message).build(player);
} else {
playerStacks[(item.getX() + item.getY() * 9) - invSize] = item.getItemStack().message(message).build(player);
}
}
inventory.setContents(itemStacks);
if (playerSlots > 0) {
ItemStack[] lastRow = new ItemStack[9];
System.arraycopy(playerStacks, playerStacks.length - 9, lastRow, 0, 9);
System.arraycopy(playerStacks, 0, playerStacks, 9, playerStacks.length - 9);
System.arraycopy(lastRow, 0, playerStacks, 0, 9);
// TODO: Update to Client-Side Only Update
player.getInventory().setContents(playerStacks);
}
Map<Integer, Function<InventoryClickEvent, Boolean>> clickEventMap = render.getItems().stream()
.filter(item -> item.getClickEvent() != null)
.collect(Collectors.toMap(renderItem -> renderItem.getX() + renderItem.getY() * 9, RenderItem::getClickEvent));
callbacks.computeIfAbsent(player, p -> new OpenPlayerInventory(p, clickEventMap).register()).setCallbacks(clickEventMap);
}
protected void updateTitle(String title, Object... params) {
if (currentRender != null) {
currentRender.getOpenInventory().setTitle(getMessages().parse(title, currentRender, params));
}
}
protected void updateUI() {
viewers.forEach(player -> renderPlayer(player, player.getOpenInventory().getTopInventory()));
}
protected void push(Player player, UIInventory inventory) {
inventory.parent = this;
inventory.addViewer(player);
viewers.remove(player);
}
protected void pop(Player player) {
if(parent == null)
return;
parent.addViewer(player);
}
@AllArgsConstructor
@Setter
private class OpenPlayerInventory implements Listener {
private final Player player;
private Map<Integer, Function<InventoryClickEvent, Boolean>> callbacks;
OpenPlayerInventory register() {
Core.getInstance().getServer().getPluginManager().registerEvents(this, Core.getInstance());
return this;
}
@EventHandler
public void onInventoryClick(InventoryClickEvent e) {
if (!player.equals(e.getWhoClicked()))
return;
if (callbacks.containsKey(e.getRawSlot()) && callbacks.get(e.getRawSlot()) != null) {
e.setCancelled(true);
Core.getInstance().getLogger().info("[UIINV] " + e.getWhoClicked().getName() + " " + e.getClick().name() + " clicked " + e.getRawSlot() + " on " + (e.getCurrentItem() != null ? e.getCurrentItem().getItemMeta().getDisplayName() : "[EMPTY]") + " in " + e.getView().getTitle());
if (callbacks.get(e.getRawSlot()).apply(e)) {
updateUI();
}
}
}
@EventHandler
public void onInventoryClose(InventoryCloseEvent e){
if(!player.equals(e.getPlayer()))
return;
InventoryClickEvent.getHandlerList().unregister(this);
InventoryCloseEvent.getHandlerList().unregister(this);
Core.getInstance().getLogger().info("[UIINV] " + player.getName() + " closed " + e.getView().getTitle());
if(callbacks.containsKey(-1))
callbacks.get(-1).apply(null);
viewers.remove(player);
UIInventory.this.callbacks.remove(player);
if (playerSlots > 0) {
player.getInventory().setContents(originalItems.remove(player));
}
onClose();
}
}
@Getter
@Setter
@AllArgsConstructor
private class MemoHookMeta<T> {
private List<Object> dependencies;
private T value;
}
}

View File

@@ -0,0 +1,66 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.inventory.ui.fragments;
import de.steamwar.inventory.ui.UIComponent;
import de.steamwar.inventory.ui.UIFragment;
import de.steamwar.inventory.ui.item.ItemBuilder;
import de.steamwar.inventory.ui.item.UIItem;
import de.steamwar.inventory.ui.layout.Row;
import de.steamwar.inventory.ui.util.RenderContext;
import de.steamwar.inventory.ui.util.Size;
import lombok.Getter;
import lombok.Setter;
import org.bukkit.Material;
import java.util.Arrays;
import java.util.Map;
@Getter
@Setter
public class ActionBar extends UIFragment {
private static final UIComponent DEFAULT_FILLER = new UIItem(ItemBuilder.of(Material.GRAY_STAINED_GLASS_PANE).displayName("§e")).cancelOnClick();
private final Map<Integer, UIComponent> actionBar;
private UIComponent filler = DEFAULT_FILLER;
public ActionBar(Map<Integer, UIComponent> actionBar) {
this.actionBar = actionBar;
}
public ActionBar filler(UIComponent filler) {
this.filler = filler;
return this;
}
@Override
public UIComponent build(RenderContext context) {
UIComponent[] components = context.useMemo(() -> {
UIComponent[] cmp = new UIComponent[context.getContainerSize().getWidth()];
for (int i = 0; i < cmp.length; i++) {
cmp[i] = actionBar.getOrDefault(i, filler);
}
return cmp;
}, Arrays.asList(actionBar));
return new Row(Arrays.asList(components)).size(new Size(context.getContainerSize().getWidth(), 1));
}
}

View File

@@ -0,0 +1,97 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.inventory.ui.fragments;
import de.steamwar.core.Core;
import de.steamwar.inventory.ui.UIComponent;
import de.steamwar.inventory.ui.UIFragment;
import de.steamwar.inventory.ui.item.ItemBuilder;
import de.steamwar.inventory.ui.item.UIItem;
import de.steamwar.inventory.ui.layout.Column;
import de.steamwar.inventory.ui.layout.FixedSizeContainer;
import de.steamwar.inventory.ui.layout.Row;
import de.steamwar.inventory.ui.util.RenderContext;
import lombok.Setter;
import org.bukkit.DyeColor;
import java.util.*;
@Setter
public class HorizontalScrollLayout extends UIFragment {
private List<UIComponent> data;
private int scroll = 0;
private final Map<Integer, UIComponent> actionBar = new HashMap<>();
public HorizontalScrollLayout(List<UIComponent> data) {
this.data = data;
}
private int maxScroll() {
return Math.max(0, Math.min(scroll, data.size() - 9 + 1));
}
public HorizontalScrollLayout actionBar(Map<Integer, UIComponent> actionBar) {
this.actionBar.clear();
this.actionBar.putAll(actionBar);
return this;
}
public HorizontalScrollLayout data(List<UIComponent> data) {
this.data = data;
return this;
}
@Override
public UIComponent build(RenderContext context) {
scroll = maxScroll();
boolean hasPrev = scroll > 0;
boolean hasNext = data.size() - scroll > 9;
Map<Integer, UIComponent> actionBar = context.useMemo(() -> {
Map<Integer, UIComponent> content = new HashMap<>(this.actionBar);
content.put(0, new UIItem(ItemBuilder.color(hasPrev ? DyeColor.LIME : DyeColor.GRAY).message(Core.MESSAGE).translateName(hasPrev ? "SWLISINV_PREVIOUS_PAGE_ACTIVE" : "SWLISINV_PREVIOUS_PAGE_INACTIVE")).onClickWithUpdate(inventoryClickEvent -> {
if (scroll > 0) {
scroll = Math.max(0, scroll - 9);
return true;
} else {
return false;
}
}));
content.put(8, new UIItem(ItemBuilder.color(hasNext ? DyeColor.LIME : DyeColor.GRAY).message(Core.MESSAGE).translateName(hasNext ? "SWLISINV_NEXT_PAGE_ACTIVE" : "SWLISINV_NEXT_PAGE_INACTIVE")).onClickWithUpdate(inventoryClickEvent -> {
if (hasNext) {
scroll = Math.min(scroll + 9, data.size() - 9);
return true;
} else {
return false;
}
}));
return content;
}, Arrays.asList(hasPrev, hasNext));
return new Column(
new FixedSizeContainer(context.getContainerSize().shrink(0, 1),
new Row(data.subList(scroll, Math.min(data.size(), scroll + 9)))
),
new ActionBar(actionBar)
);
}
}

View File

@@ -0,0 +1,114 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.inventory.ui.fragments;
import de.steamwar.core.Core;
import de.steamwar.inventory.ui.UIComponent;
import de.steamwar.inventory.ui.UIFragment;
import de.steamwar.inventory.ui.item.ItemBuilder;
import de.steamwar.inventory.ui.item.UIItem;
import de.steamwar.inventory.ui.layout.Column;
import de.steamwar.inventory.ui.layout.FixedSizeContainer;
import de.steamwar.inventory.ui.layout.Grid;
import de.steamwar.inventory.ui.util.RenderContext;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import org.bukkit.DyeColor;
import org.bukkit.event.inventory.ClickType;
import java.util.*;
import java.util.function.Consumer;
@Getter
@Setter
public class PageLayout<T> extends UIFragment {
private final List<UIListEntry<T>> items;
private final UIListCallback<T> callback;
private int page = 0;
private Consumer<Integer> onPageChange = null;
private Map<Integer, UIItem> itemsOnBar = Collections.emptyMap();
public PageLayout(List<UIListEntry<T>> items, UIListCallback<T> callback) {
System.out.println(items.size());
this.items = items;
this.callback = callback;
}
@Override
public UIComponent build(RenderContext context) {
int itemsOnPage = context.getContainerSize().getWidth() * (context.getContainerSize().getHeight() - 1);
List<UIComponent> uiItems = context.useMemo(() -> {
int i = page * itemsOnPage;
int ipageLimit = items.size() - page * itemsOnPage;
if (ipageLimit > itemsOnPage) {
ipageLimit = itemsOnPage;
}
List<UIComponent> pageItems = new ArrayList<>();
for (int ipage = 0; ipage < ipageLimit; ipage++) {
UIListEntry<T> e = items.get(i);
pageItems.add(new UIItem(e.item).onClickNoUpdate(inventoryClickEvent -> callback.clicked(inventoryClickEvent.getClick(), e.object)));
i++;
}
return pageItems;
}, Arrays.asList(page, items.size(), itemsOnPage));
Map<Integer, UIComponent> uiBar = context.useMemo(() -> {
Map<Integer, UIComponent> content = new HashMap<>(itemsOnBar);
if (page != 0) {
content.put(0, new UIItem(ItemBuilder.color(DyeColor.LIME).message(Core.MESSAGE).translateName("SWLISINV_PREVIOUS_PAGE_ACTIVE")).onClick(inventoryClickEvent -> page--));
} else {
content.put(0, new UIItem(ItemBuilder.color(DyeColor.GRAY).message(Core.MESSAGE).translateName("SWLISINV_PREVIOUS_PAGE_INACTIVE")).cancelOnClick());
}
if (page < items.size() / itemsOnPage - (items.size() % itemsOnPage == 0 ? 1 : 0)) {
content.put(8, new UIItem(ItemBuilder.color(DyeColor.LIME).message(Core.MESSAGE).translateName("SWLISINV_NEXT_PAGE_ACTIVE")).onClick(inventoryClickEvent -> page++));
} else {
content.put(8, new UIItem(ItemBuilder.color(DyeColor.GRAY).message(Core.MESSAGE).translateName("SWLISINV_NEXT_PAGE_INACTIVE")).cancelOnClick());
}
return content;
}, Arrays.asList(page, items.size(), itemsOnPage));
return new Column(
new FixedSizeContainer(context.getContainerSize().shrink(0, 1),
new Grid(uiItems)),
new ActionBar(uiBar)
);
}
public interface UIListCallback<T>{
void clicked(ClickType click, T element);
}
@Data
public static class UIListEntry<T>{
final ItemBuilder item;
final T object;
}
}

View File

@@ -0,0 +1,44 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.inventory.ui.item;
import de.steamwar.inventory.ui.UIComponent;
import de.steamwar.inventory.ui.util.ComponentRender;
import de.steamwar.inventory.ui.util.RenderContext;
import de.steamwar.inventory.ui.util.RenderItem;
public class Enchanted implements UIComponent {
private final UIComponent children;
public Enchanted(UIComponent children) {
this.children = children;
}
@Override
public ComponentRender render(RenderContext context) {
ComponentRender render = children.render(context);
for (RenderItem item : render.getItems()) {
item.getItemStack().enchanted();
}
return render;
}
}

View File

@@ -0,0 +1,205 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.inventory.ui.item;
import de.steamwar.core.FlatteningWrapper;
import de.steamwar.core.TrickyTrialsWrapper;
import de.steamwar.inventory.SWItem;
import de.steamwar.message.Message;
import de.steamwar.message.SubMessage;
import org.bukkit.DyeColor;
import org.bukkit.Material;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemFlag;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import java.util.*;
import java.util.stream.Collectors;
public class ItemBuilder {
private static final Map<DyeColor, Integer> DYE_COLOR_INTEGER_MAP = new EnumMap<>(DyeColor.class);
static {
DYE_COLOR_INTEGER_MAP.put(DyeColor.BLACK, 16);
DYE_COLOR_INTEGER_MAP.put(DyeColor.RED, 1);
DYE_COLOR_INTEGER_MAP.put(DyeColor.GREEN, 2);
DYE_COLOR_INTEGER_MAP.put(DyeColor.BROWN, 3);
DYE_COLOR_INTEGER_MAP.put(DyeColor.BLUE, 4);
DYE_COLOR_INTEGER_MAP.put(DyeColor.PURPLE, 5);
DYE_COLOR_INTEGER_MAP.put(DyeColor.CYAN, 6);
DYE_COLOR_INTEGER_MAP.put(DyeColor.LIGHT_GRAY, 7);
DYE_COLOR_INTEGER_MAP.put(DyeColor.GRAY, 8);
DYE_COLOR_INTEGER_MAP.put(DyeColor.PINK, 9);
DYE_COLOR_INTEGER_MAP.put(DyeColor.LIME, 10);
DYE_COLOR_INTEGER_MAP.put(DyeColor.YELLOW, 11);
DYE_COLOR_INTEGER_MAP.put(DyeColor.LIGHT_BLUE, 12);
DYE_COLOR_INTEGER_MAP.put(DyeColor.MAGENTA, 13);
DYE_COLOR_INTEGER_MAP.put(DyeColor.ORANGE, 14);
DYE_COLOR_INTEGER_MAP.put(DyeColor.WHITE, 15);
}
private final ItemStack itemStack;
private final ItemMeta itemMeta;
private Message message = null;
private SubMessage translateName = null;
private List<SubMessage> translateLore = null;
private ItemBuilder(Material material) {
this(new ItemStack(material));
}
private ItemBuilder(ItemStack item) {
this.itemStack = item;
this.itemMeta = itemStack.getItemMeta();
}
public static ItemBuilder of(Material material) {
return new ItemBuilder(material);
}
public static ItemBuilder of(ItemStack itemStack) {
return new ItemBuilder(itemStack.getType()).amount(itemStack.getAmount());
}
private static ItemBuilder of(Material material, Byte meta) {
ItemStack itemStack;
try {
itemStack = new ItemStack(material, 1, (short)0, meta);
} catch (IllegalArgumentException e) {
itemStack = new ItemStack(material, 1);
}
return new ItemBuilder(itemStack);
}
public static ItemBuilder color(DyeColor color) {
return ItemBuilder.of(SWItem.getDye(DYE_COLOR_INTEGER_MAP.get(color)), DYE_COLOR_INTEGER_MAP.get(color).byteValue());
}
public static ItemBuilder skull(OfflinePlayer player) {
return skull(player.getName());
}
public static ItemBuilder skull(String name) {
return ItemBuilder.of(FlatteningWrapper.impl.setSkullOwner(name));
}
public ItemBuilder message(Message message) {
if (this.message == null) {
this.message = message;
}
return this;
}
public ItemStack build(Player player) {
if (message != null) {
if (translateName != null) {
itemMeta.setDisplayName(message.parse(translateName.getMessage(), player, translateName.getParams()));
}
if (translateLore != null) {
itemMeta.setLore(translateLore.stream().map(subMessage -> message.parse(subMessage.getMessage(), player, subMessage.getParams())).collect(Collectors.toList()));
}
}
itemStack.setItemMeta(itemMeta);
return itemStack;
}
public ItemBuilder amount(int amount) {
itemStack.setAmount(amount);
return this;
}
public ItemBuilder displayName(String name) {
itemMeta.setDisplayName(name);
return this;
}
public ItemBuilder lore(String... lore) {
if (itemMeta.hasLore()) {
List<String> loreList = itemMeta.getLore();
loreList.addAll(Arrays.asList(lore));
itemMeta.setLore(loreList);
} else {
itemMeta.setLore(Arrays.asList(lore));
}
return this;
}
public ItemBuilder translateName(String name, Object... params) {
this.translateName = new SubMessage(name, params);
return this;
}
public ItemBuilder addTranslateLore(SubMessage... lore) {
if (this.translateLore == null) {
this.translateLore = Arrays.asList(lore);
} else {
this.translateLore.addAll(Arrays.asList(lore));
}
return this;
}
public ItemBuilder enchanted() {
itemMeta.addEnchant(TrickyTrialsWrapper.impl.getUnbreakingEnchantment(), 10, true);
return this;
}
public ItemBuilder enchanted(boolean enchanted) {
if(enchanted) {
return enchanted();
} else {
return this;
}
}
public ItemBuilder hideFlags() {
for (ItemFlag flag : EnumSet.allOf(ItemFlag.class)) {
itemMeta.addItemFlags(flag);
}
return this;
}
public ItemBuilder customModel(int modelData) {
itemMeta.setCustomModelData(modelData);
return this;
}
@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
ItemBuilder that = (ItemBuilder) o;
return Objects.equals(itemStack, that.itemStack) && Objects.equals(itemMeta, that.itemMeta) && Objects.equals(message, that.message) && Objects.equals(translateName, that.translateName) && Objects.equals(translateLore, that.translateLore);
}
@Override
public String toString() {
return "ItemBuilder{" +
"itemStack=" + itemStack +
", itemMeta=" + itemMeta +
", message=" + message +
", translateName=" + translateName +
", translateLore=" + translateLore +
'}';
}
}

View File

@@ -0,0 +1,74 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.inventory.ui.item;
import de.steamwar.inventory.ui.UIComponent;
import de.steamwar.inventory.ui.util.ComponentRender;
import de.steamwar.inventory.ui.util.RenderContext;
import de.steamwar.inventory.ui.util.RenderItem;
import org.bukkit.event.inventory.ClickType;
import org.bukkit.event.inventory.InventoryClickEvent;
import java.util.function.Function;
public class OnClick implements UIComponent {
private final UIComponent children;
private final boolean allowDoubleClick;
private final Function<InventoryClickEvent, Boolean> event;
public OnClick(UIComponent children, boolean allowDoubleClick, Function<InventoryClickEvent, Boolean> event) {
this.children = children;
this.allowDoubleClick = allowDoubleClick;
this.event = event;
}
@Override
public ComponentRender render(RenderContext context) {
ComponentRender render = children.render(context);
for (RenderItem item : render.getItems()) {
item.setClickEvent(ev -> {
if (ev.getClick() != ClickType.DOUBLE_CLICK || allowDoubleClick) {
return event.apply(ev);
}
return false;
});
}
return render;
}
@Override
public String toString() {
return "OnClick{" +
"children=" + children +
", allowDoubleClick=" + allowDoubleClick +
'}';
}
@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
OnClick onClick = (OnClick) o;
return allowDoubleClick == onClick.allowDoubleClick && children.equals(onClick.children);
}
}

View File

@@ -0,0 +1,42 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.inventory.ui.item;
import de.steamwar.inventory.ui.UIComponent;
import de.steamwar.inventory.ui.util.ComponentRender;
import de.steamwar.inventory.ui.util.RenderContext;
import de.steamwar.inventory.ui.util.Size;
import java.util.ArrayList;
public class Space implements UIComponent {
@Override
public ComponentRender render(RenderContext context) {
return new ComponentRender(
new ArrayList<>(),
new Size(1, 1)
);
}
@Override
public boolean equals(Object obj) {
return obj instanceof Space;
}
}

View File

@@ -0,0 +1,64 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.inventory.ui.item;
import de.steamwar.inventory.ui.UIComponent;
import de.steamwar.inventory.ui.util.*;
import lombok.EqualsAndHashCode;
import org.bukkit.inventory.ItemStack;
import java.util.Arrays;
import java.util.Objects;
public class UIItem implements UIComponent {
private final ItemBuilder itemStack;
public UIItem(ItemStack itemStack) {
this(ItemBuilder.of(itemStack));
}
public UIItem(ItemBuilder itemStack) {
this.itemStack = itemStack;
}
@Override
public ComponentRender render(RenderContext context) {
return new ComponentRender(
Arrays.asList(new RenderItem(0, 0, itemStack, null)),
new Size(1, 1)
);
}
@Override
public String toString() {
return "UIItem{" +
"itemStack=" + itemStack +
'}';
}
@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
UIItem uiItem = (UIItem) o;
return Objects.equals(itemStack, uiItem.itemStack);
}
}

View File

@@ -0,0 +1,27 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.inventory.ui.layout;
public enum Alignment {
START,
CENTER,
END,
BETWEEN
}

View File

@@ -0,0 +1,93 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.inventory.ui.layout;
import de.steamwar.inventory.ui.UIComponent;
import de.steamwar.inventory.ui.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Column implements UIComponent {
private final List<UIComponent> components;
private Alignment alignment;
private boolean shrinkWrap = false;
public Column(UIComponent... components) {
this(Arrays.asList(components));
}
public Column(List<UIComponent> components) {
this.components = components;
}
public Column centered() {
this.alignment = Alignment.CENTER;
return this;
}
public Column setShrinkWrap(boolean shrinkWrap) {
this.shrinkWrap = shrinkWrap;
return this;
}
@Override
public ComponentRender render(RenderContext context) {
List<RenderItem> renderItems = new ArrayList<>();
int height = 0;
int maxWidth = 0;
for (UIComponent component : components) {
ComponentRender render = component.render(context.withContainerSize(context.getContainerSize().withHeight(context.getContainerSize().getHeight() - height)));
for(RenderItem item : render.getItems()){
renderItems.add(item.shift(0, height));
}
maxWidth = Math.max(maxWidth, render.getSize().getWidth());
height += render.getSize().getHeight();
}
if (alignment == Alignment.CENTER) {
int shiftAmount = (context.getContainerSize().getHeight() - height) / 2;
for (RenderItem item : renderItems) {
item.shift(0, shiftAmount);
}
} else if (alignment == Alignment.BETWEEN) {
int shiftAmount = (context.getContainerSize().getHeight() - height) / (components.size() - 1);
for (int i = 0; i < components.size() - 1; i++) {
RenderItem item = renderItems.get(i);
item.shift(0, shiftAmount * i);
}
} else if (alignment == Alignment.END) {
int shiftAmount = (context.getContainerSize().getHeight() - height);
for (RenderItem item : renderItems) {
item.shift(0, shiftAmount);
}
}
return new ComponentRender(
renderItems,
new Size(maxWidth, shrinkWrap ? height : context.getContainerSize().getHeight())
);
}
}

View File

@@ -0,0 +1,40 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.inventory.ui.layout;
import de.steamwar.inventory.ui.UIComponent;
import de.steamwar.inventory.ui.util.ComponentRender;
import de.steamwar.inventory.ui.util.RenderContext;
import de.steamwar.inventory.ui.util.Size;
public class FixedSizeContainer implements UIComponent {
private final UIComponent children;
private final Size size;
public FixedSizeContainer(Size size, UIComponent children) {
this.children = children;
this.size = size;
}
@Override
public ComponentRender render(RenderContext context) {
return children.render(context.withContainerSize(size)).withSize(size);
}
}

View File

@@ -0,0 +1,68 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.inventory.ui.layout;
import de.steamwar.inventory.ui.UIComponent;
import de.steamwar.inventory.ui.util.ComponentRender;
import de.steamwar.inventory.ui.util.RenderContext;
import de.steamwar.inventory.ui.util.RenderItem;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Grid implements UIComponent {
private final List<UIComponent> children;
public Grid(UIComponent... children) {
this(Arrays.asList(children));
}
public Grid(List<UIComponent> children) {
this.children = children;
}
@Override
public ComponentRender render(RenderContext context) {
List<RenderItem> renderItems = new ArrayList<>();
int x = 0;
int y = 0;
int currentRowMaxHeight = 0;
for (UIComponent child : children) {
ComponentRender render = child.render(context.withContainerSize(
context.getContainerSize()
.withWidth(context.getContainerSize().getWidth() - x)
.withHeight(context.getContainerSize().getHeight() - y)));
currentRowMaxHeight = Math.max(currentRowMaxHeight, render.getSize().getHeight());
for (RenderItem item : render.getItems()) {
renderItems.add(item.shift(x, y));
}
x += render.getSize().getWidth();
if(x >= context.getContainerSize().getWidth()){
x = 0;
y += currentRowMaxHeight;
currentRowMaxHeight = 0;
}
}
return new ComponentRender(renderItems, context.getContainerSize());
}
}

View File

@@ -0,0 +1,98 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.inventory.ui.layout;
import de.steamwar.inventory.ui.UIComponent;
import de.steamwar.inventory.ui.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Row implements UIComponent {
private final List<UIComponent> components;
private Alignment alignment;
private boolean shrinkWrap = false;
public Row(UIComponent... components) {
this(Arrays.asList(components));
}
public Row(List<UIComponent> components) {
this.components = components;
}
public Row centered() {
this.alignment = Alignment.CENTER;
return this;
}
public Row alignment(Alignment alignment) {
this.alignment = alignment;
return this;
}
public Row setShrinkWrap(boolean shrinkWrap) {
this.shrinkWrap = shrinkWrap;
return this;
}
@Override
public ComponentRender render(RenderContext context) {
List<RenderItem> renderItems = new ArrayList<>();
int width = 0;
int maxHeight = 0;
for (UIComponent component : components) {
ComponentRender render = component.render(context.withContainerSize(context.getContainerSize().withWidth(context.getContainerSize().getWidth() - width)));
for(RenderItem item : render.getItems()){
renderItems.add(item.shift(width, 0));
}
maxHeight = Math.max(maxHeight, render.getSize().getHeight());
width += render.getSize().getWidth();
}
if (alignment == Alignment.CENTER) {
int shiftAmount = (context.getContainerSize().getWidth() - width) / 2;
for (RenderItem item : renderItems) {
item.shift(shiftAmount, 0);
}
} else if (alignment == Alignment.BETWEEN) {
int shiftAmount = (context.getContainerSize().getWidth() - width) / (components.size() - 1);
for (int i = 0; i < components.size() - 1; i++) {
RenderItem item = renderItems.get(i);
item.shift(shiftAmount * i, 0);
}
} else if (alignment == Alignment.END) {
int shiftAmount = (context.getContainerSize().getWidth() - width);
for (RenderItem item : renderItems) {
item.shift(shiftAmount, 0);
}
}
return new ComponentRender(
renderItems,
new Size(shrinkWrap ? width : context.getContainerSize().getWidth(), maxHeight)
);
}
}

View File

@@ -0,0 +1,41 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.inventory.ui.test;
import de.steamwar.command.SWCommand;
import de.steamwar.inventory.ui.util.UpdateStream;
import org.bukkit.entity.Player;
import java.util.ArrayList;
import java.util.List;
public class TestInvCommand extends SWCommand {
public static final UpdateStream<List<Integer>> STREAM = new UpdateStream<>(new ArrayList<>());
public TestInvCommand() {
super("testinv");
}
@Register
public void run(Player player) {
new TestInventory(STREAM).addViewer(player);
}
}

View File

@@ -0,0 +1,79 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.inventory.ui.test;
import de.steamwar.core.Core;
import de.steamwar.inventory.ui.StatefulUIInventory;
import de.steamwar.inventory.ui.UIComponent;
import de.steamwar.inventory.ui.fragments.HorizontalScrollLayout;
import de.steamwar.inventory.ui.item.ItemBuilder;
import de.steamwar.inventory.ui.item.UIItem;
import de.steamwar.inventory.ui.util.UpdateStream;
import de.steamwar.message.Message;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import static de.steamwar.inventory.ui.test.TestInvCommand.STREAM;
public class TestInventory extends StatefulUIInventory<List<Integer>> {
private HorizontalScrollLayout scrollLayout;
private static int counter = 1;
public TestInventory(UpdateStream<List<Integer>> stateObservable) {
super(stateObservable);
scrollLayout = new HorizontalScrollLayout(new ArrayList<>());
}
@Override
protected UIComponent render(List<Integer> state) {
return scrollLayout;
}
@Override
protected void update(List<Integer> state) {
System.out.println("Update");
List<UIComponent> items = state.stream().map(integer -> new UIItem(ItemBuilder.of(Material.ACACIA_PLANKS).displayName(String.valueOf(integer))).onClickNoUpdate(inventoryClickEvent -> STREAM.update(current -> {
current.remove(integer);
return current;
}))).collect(Collectors.toList());
items.add(new UIItem(ItemBuilder.of(Material.BUCKET).displayName("Add")).onClickNoUpdate(inventoryClickEvent -> STREAM.update(current -> {
current.add(counter++);
return current;
})));
scrollLayout = scrollLayout.data(items);
}
@Override
public Inventory createInventory(Player player) {
return Bukkit.createInventory(null, 9 * 6, "Test Inventory");
}
@Override
public Message getMessages() {
return Core.MESSAGE;
}
}

View File

@@ -0,0 +1,32 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.inventory.ui.util;
import lombok.Data;
import lombok.With;
import java.util.List;
@Data
@With
public class ComponentRender {
private final List<RenderItem> items;
private final Size size;
}

View File

@@ -0,0 +1,43 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.inventory.ui.util;
import de.steamwar.inventory.ui.UIInventory;
import lombok.Data;
import lombok.With;
import java.util.List;
import java.util.function.Supplier;
@Data
@With
public class RenderContext {
private final Size inventorySize;
private final Size containerSize;
private final UIInventory inventory;
public <T> T useMemo(Supplier<T> supplier, List<Object> dependencies) {
return inventory.useMemo(supplier, dependencies);
}
public <T> T useMemo(Supplier<T> supplier) {
return inventory.useMemo(supplier);
}
}

View File

@@ -0,0 +1,42 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.inventory.ui.util;
import de.steamwar.inventory.ui.item.ItemBuilder;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.With;
import org.bukkit.event.inventory.InventoryClickEvent;
import java.util.function.Function;
@Data
@With
@AllArgsConstructor
public class RenderItem {
private int x;
private int y;
private ItemBuilder itemStack;
private Function<InventoryClickEvent, Boolean> clickEvent;
public RenderItem shift(int deltaX, int deltaY) {
return withX(x + deltaX).withY(y + deltaY);
}
}

View File

@@ -0,0 +1,34 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.inventory.ui.util;
import lombok.Data;
import lombok.With;
@Data
@With
public class Size {
private final int width;
private final int height;
public Size shrink(int width, int height) {
return new Size(this.width - width, this.height - height);
}
}

View File

@@ -0,0 +1,66 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.inventory.ui.util;
import java.util.ArrayList;
import java.util.List;
public class UpdateStream<T> {
private T current;
private final List<Subscriber<T>> subscribers = new ArrayList<>();
public UpdateStream(T current) {
this.current = current;
}
public Unsubscriber subscribe(Subscriber<T> subscriber) {
subscribers.add(subscriber);
subscriber.onUpdate(current);
return () -> subscribers.remove(subscriber);
}
public T get() {
return current;
}
public void emit(T newCurrent) {
current = newCurrent;
subscribers.forEach(subscriber -> subscriber.onUpdate(newCurrent));
}
public void update(Updater<T> updater) {
current = updater.update(current);
subscribers.forEach(subscriber -> subscriber.onUpdate(current));
}
public interface Subscriber<T> {
void onUpdate(T current);
}
public interface Unsubscriber {
void unsubscribe();
}
public interface Updater<T> {
T update(T current);
}
}

View File

@@ -35,6 +35,8 @@ import org.bukkit.event.player.PlayerAttemptPickupItemEvent
import org.bukkit.event.player.PlayerInteractEntityEvent
import org.bukkit.event.player.PlayerJoinEvent
import org.bukkit.event.player.PlayerMoveEvent
import org.bukkit.event.player.*
import org.bukkit.persistence.PersistentDataType
object IngameListener : Listener {
@@ -75,9 +77,9 @@ object IngameListener : Listener {
@EventHandler
fun onPickupCoins(e: PlayerAttemptPickupItemEvent) {
if (e.item.itemStack.isSimilar(DealerInventory.coins)) {
TNTLeagueGame.getTeam(e.player)?.coins =
e.item.itemStack.amount + (TNTLeagueGame.getTeam(e.player)?.coins ?: 0)
if (e.item.itemStack.persistentDataContainer.has(DealerInventory.coinKey)) {
val numberOfCoins = e.item.itemStack.persistentDataContainer[DealerInventory.coinKey, PersistentDataType.INTEGER] ?: 0
TNTLeagueGame.getTeam(e.player)?.coins = (e.item.itemStack.amount * numberOfCoins) + (TNTLeagueGame.getTeam(e.player)?.coins ?: 0)
e.item.itemStack.amount = 0
e.isCancelled = true

View File

@@ -24,8 +24,6 @@ import de.steamwar.message.SubMessage
import de.steamwar.network.NetworkSender
import de.steamwar.network.packets.common.FightInfoPacket
import de.steamwar.scoreboard.SWScoreboard
import de.steamwar.sql.Fight
import de.steamwar.sql.FightPlayer
import de.steamwar.sql.SteamwarUser
import de.steamwar.tntleague.colorByTeam
import de.steamwar.tntleague.config.TNTLeagueConfig
@@ -37,10 +35,9 @@ import de.steamwar.tntleague.events.LobbyListener
import de.steamwar.tntleague.inventory.DealerInventory
import de.steamwar.tntleague.message
import de.steamwar.tntleague.plugin
import de.steamwar.tntleague.util.*
import de.steamwar.tntleague.util.TNTLeagueScoreboard
import org.bukkit.GameMode
import org.bukkit.Location
import org.bukkit.Material
import org.bukkit.Sound
import org.bukkit.entity.Item
import org.bukkit.entity.Player
@@ -51,6 +48,7 @@ import org.bukkit.inventory.ItemStack
import org.bukkit.scheduler.BukkitTask
import java.sql.Timestamp
import java.time.Instant
import kotlin.random.Random
object TNTLeagueGame {
var state: GameState = GameState.LOBBY
@@ -87,18 +85,24 @@ object TNTLeagueGame {
message.broadcast("GAME_STARTED")
val tnt = ItemStack(Material.TNT)
start = Timestamp.from(Instant.now())
var spawnCount = 0
spawnerTask = plugin.server.scheduler.runTaskTimer(plugin, bukkit {
val coinsToSpawn = if (spawnCount % 28 == 0) {
DealerInventory.huge_coins
} else if (spawnCount % 7 == 0) {
DealerInventory.big_coins
} else {
DealerInventory.coins
}
spawnCount++
if (world.getNearbyEntitiesByType(Item::class.java, TNTLeagueWorldConfig.blueTeam.itemSpawn, 3.0).sumOf { it.itemStack.amount } <= 256) {
spawnItems(TNTLeagueWorldConfig.blueTeam.itemSpawn, tnt)
spawnItems(TNTLeagueWorldConfig.blueTeam.itemSpawn, DealerInventory.coins)
spawnItems(TNTLeagueWorldConfig.blueTeam.itemSpawn, coinsToSpawn)
}
if (world.getNearbyEntitiesByType(Item::class.java, TNTLeagueWorldConfig.redTeam.itemSpawn, 3.0).sumOf { it.itemStack.amount } <= 256) {
spawnItems(TNTLeagueWorldConfig.redTeam.itemSpawn, tnt)
spawnItems(TNTLeagueWorldConfig.redTeam.itemSpawn, DealerInventory.coins)
spawnItems(TNTLeagueWorldConfig.redTeam.itemSpawn, coinsToSpawn)
}
}, 5, 10)

View File

@@ -59,13 +59,31 @@ class DealerInventory(player: Player): KotlinInventory(player) {
companion object {
private val priceKey = NamespacedKey(plugin, "price")
private val amountKey = NamespacedKey(plugin, "amount")
private val coinKey = NamespacedKey(plugin, "coin")
val coinKey = NamespacedKey(plugin, "coin")
val coins = ItemStack(Material.RAW_GOLD).apply {
itemMeta = itemMeta.apply {
displayName(Component.text("Coins").color(NamedTextColor.GOLD))
persistentDataContainer.apply {
set(coinKey, PersistentDataType.BOOLEAN, true)
set(coinKey, PersistentDataType.INTEGER, 1)
}
}
}
val big_coins = ItemStack(Material.GOLD_INGOT).apply {
itemMeta = itemMeta.apply {
displayName(Component.text("Big Coins").color(NamedTextColor.GOLD))
persistentDataContainer.apply {
set(coinKey, PersistentDataType.INTEGER, 3)
}
}
}
val huge_coins = ItemStack(Material.GOLD_BLOCK).apply {
itemMeta = itemMeta.apply {
displayName(Component.text("Huge Coins").color(NamedTextColor.GOLD))
persistentDataContainer.apply {
set(coinKey, PersistentDataType.INTEGER, 9)
}
}
}

View File

@@ -17,20 +17,23 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
PREFIX=<EFBFBD>eTowerRun<EFBFBD>8<EFBFBD><EFBFBD>r
PREFIX=§eTowerRun§8§§r
PLAYER_DIED=<EFBFBD>c{0} <EFBFBD>7died<EFBFBD>8!
PLAYER_ESCAPE=<EFBFBD>a{0} <EFBFBD>7escaped<EFBFBD>8!
GAME_START=<EFBFBD>aThe game has started<EFBFBD>8!
GAME_WIN=<EFBFBD>a{0} <EFBFBD>7has won the game<EFBFBD>8!
GAME_STARTING=<EFBFBD>7Starting: <EFBFBD>e{0}s<EFBFBD>8!
GAME_WAITING=<EFBFBD>7Waiting for players<EFBFBD>8...
SERVER_STOPPING=<EFBFBD>cThe server stops in <EFBFBD>e{0}s<EFBFBD>8!
SERVER_RESET=<EFBFBD>cThe server will be reset in <EFBFBD>e{0}s<EFBFBD>8!
KEY_NAME=<EFBFBD>eKey
KEY_FOUND=<EFBFBD>a{0} <EFBFBD>7found a key<EFBFBD>8!
GAME_TIE=<EFBFBD>aThe game ended in a tie<EFBFBD>8!
GAME_TIME=<EFBFBD>a{0}:{1}
CATCH_UP_WARNING=<EFBFBD>4!! <EFBFBD>cYou should catch up <EFBFBD>4!!
PLAYER_DIED=§c{0} §7died§8!
PLAYER_ESCAPE=§a{0} §7escaped§8!
GAME_START=§aThe game has started§8!
GAME_WIN=§a{0} §7has won the game§8!
GAME_STARTING=§7Starting: §e{0}s§8!
GAME_WAITING=§7Waiting for players§8...
SERVER_STOPPING=§cThe server stops in §e{0}s§8!
SERVER_RESET=§cThe server will be reset in §e{0}s§8!
KEY_NAME=§eKey
KEY_FOUND=§a{0} §7found a key§8!
GAME_TIE=§aThe game ended in a tie§8!
GAME_TIME=§a{0}:{1}
CATCH_UP_WARNING=§4!! §cYou should catch up §4!!
COMMAND_START=<EFBFBD>aThe game will start soon<EFBFBD>8!
COMMAND_START=§aThe game will start soon§8!
PARTICIPANT_CHAT={0} {1}§8» §7{2}
SPECTATOR_CHAT=§7{0}§8» §7{1}

View File

@@ -17,19 +17,19 @@
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
PLAYER_DIED=<EFBFBD>c{0} <EFBFBD>7ist gestorben<EFBFBD>8!
PLAYER_ESCAPE=<EFBFBD>a{0} <EFBFBD>7ist entkommen<EFBFBD>8!
GAME_START=<EFBFBD>aDas Spiel beginnt<EFBFBD>8!
GAME_WIN=<EFBFBD>a{0} <EFBFBD>7hat das Spiel gewonnen<EFBFBD>8!
GAME_STARTING=<EFBFBD>7Das Spiel startet in <EFBFBD>e{0}s<EFBFBD>8!
GAME_WAITING=<EFBFBD>7Warte auf weitere Spieler<EFBFBD>8...
SERVER_STOPPING=<EFBFBD>cDer Server stoppt in <EFBFBD>e{0}s<EFBFBD>8!
SERVER_RESET=<EFBFBD>cDer Server wird in <EFBFBD>e{0}s <EFBFBD>czur<EFBFBD>ckgesetzt<EFBFBD>8!
GAME_TIE=<EFBFBD>7Keiner hat gewonnen<EFBFBD>8!
PLAYER_DIED=§c{0} §7ist gestorben§8!
PLAYER_ESCAPE=§a{0} §7ist entkommen§8!
GAME_START=§aDas Spiel beginnt§8!
GAME_WIN=§a{0} §7hat das Spiel gewonnen§8!
GAME_STARTING=§7Das Spiel startet in §e{0}s§8!
GAME_WAITING=§7Warte auf weitere Spieler§8...
SERVER_STOPPING=§cDer Server stoppt in §e{0}s§8!
SERVER_RESET=§cDer Server wird in §e{0}s §czur§ckgesetzt§8!
GAME_TIE=§7Keiner hat gewonnen§8!
KEY_NAME=<EFBFBD>eSchl<EFBFBD>ssel
KEY_FOUND=<EFBFBD>a{0} <EFBFBD>7hat einen Schl<EFBFBD>ssel gefunden<EFBFBD>8!
KEY_NAME=§eSchl§ssel
KEY_FOUND=§a{0} §7hat einen Schl§ssel gefunden§8!
CATCH_UP_WARNING=<EFBFBD>4!! <EFBFBD>cDu solltest aufholen <EFBFBD>4!!
CATCH_UP_WARNING=§4!! §cDu solltest aufholen §4!!
COMMAND_START=<EFBFBD>7Das Spiel startet bald<EFBFBD>8!
COMMAND_START=§7Das Spiel startet bald§8!

View File

@@ -35,7 +35,8 @@ import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.util.Vector;
import java.io.File;
import java.util.*;
import java.util.ArrayList;
import java.util.List;
@UtilityClass
public class WorldConfig {
@@ -54,7 +55,7 @@ public class WorldConfig {
public static final int MAP_MIN_Z;
public static final int MAP_MAX_X;
public static final int MAP_MAX_Z;
public static final Map<Material, Integer> MELTING_TIMES;
public static final boolean MELTING;
public static final TowerGeneratorConfig TOWER_GENERATOR_CONFIG;
public static final List<WinCondition> WINCONDITIONS = new ArrayList<>();
@@ -161,16 +162,7 @@ public class WorldConfig {
MAP_MAX_X = config.getInt("maxX");
MAP_MAX_Z = config.getInt("maxZ");
ConfigurationSection meltingBlocksSection = tower.getConfigurationSection("meltingBlocks");
if (meltingBlocksSection != null) {
Map<Material, Integer> meltingTimes = new HashMap<>();
meltingBlocksSection.getKeys(false).forEach(s -> {
meltingTimes.put(Material.valueOf(s), meltingBlocksSection.getInt(s));
});
MELTING_TIMES = Collections.unmodifiableMap(meltingTimes);
} else {
MELTING_TIMES = Collections.emptyMap();
}
MELTING = tower.getBoolean("melting") || tower.contains("meltingBlocks"); // Backwards compatibility with meltingBlocks key!
ACTIVE_WINCONDITIONS = config.getStringList("winconditions");
WINCONDITIONS.stream().filter(winCondition -> ACTIVE_WINCONDITIONS.contains(winCondition.getName())).forEach(winCondition -> winCondition.setActive(true));

View File

@@ -46,6 +46,10 @@ public class TowerRunGame {
public static final List<TowerRunPlayer> PLAYERS_ESCAPED = new ArrayList<>();
private static final World world = Bukkit.getWorlds().get(0);
public static boolean isEscaped(TowerRunPlayer player) {
return PLAYERS_ESCAPED.contains(player);
}
public static boolean isAlive(TowerRunPlayer player) {
return PLAYERS_ALIVE.contains(player);
}

View File

@@ -19,20 +19,20 @@
package de.steamwar.towerrun.listener;
import de.steamwar.towerrun.TowerRun;
import de.steamwar.towerrun.config.WorldConfig;
import de.steamwar.towerrun.game.TowerRunGame;
import de.steamwar.towerrun.game.TowerRunPlayer;
import de.steamwar.towerrun.state.GameState;
import de.steamwar.towerrun.state.GameStateBukkitListener;
import de.steamwar.towerrun.state.GameStates;
import lombok.val;
import org.bukkit.Bukkit;
import org.bukkit.GameMode;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerMoveEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.player.PlayerRespawnEvent;
import org.bukkit.event.player.*;
import java.util.EnumSet;
@@ -67,4 +67,25 @@ public class GlobalListener extends GameStateBukkitListener implements Listener
public void onPlayerRespawn(PlayerRespawnEvent event) {
event.setRespawnLocation(WorldConfig.SPAWN);
}
@EventHandler
public void onAsyncPlayerChat(AsyncPlayerChatEvent event) {
TowerRunPlayer player = TowerRunPlayer.get(event.getPlayer());
if (GameState.getCurrentState() == GameStates.RUNNING && (TowerRunGame.isAlive(player) || TowerRunGame.isEscaped(player))) {
String prefix;
if (TowerRunGame.isAlive(player)) {
if (event.getPlayer().getGameMode() == GameMode.SPECTATOR) {
prefix = "§c☠";
} else {
prefix = "§6❤";
}
} else {
prefix = "§a✔";
}
TowerRun.getMessage().broadcastPrefixless("PARTICIPANT_CHAT", prefix, event.getPlayer().getName(), event.getMessage());
} else {
TowerRun.getMessage().broadcastPrefixless("SPECTATOR_CHAT", event.getPlayer().getName(), event.getMessage());
}
event.setCancelled(true);
}
}

View File

@@ -62,13 +62,13 @@ public class IngameListener extends GameStateBukkitListener {
blocksToMeltRunnable = new BukkitRunnable() {
@Override
public void run() {
List<Block> blocks = blocksToMelt.get(time);
List<Block> blocks = blocksToMelt.remove(time);
time++;
if (blocks == null) {
return;
}
blocks.forEach(block -> {
if (!WorldConfig.MELTING_TIMES.containsKey(block.getType())) return;
if (block.getType() == Material.AIR || block.getType() == Material.LAVA) return;
block.setType(Material.AIR);
block.getWorld().playSound(block.getLocation(), Sound.BLOCK_FIRE_EXTINGUISH, 0.1F, 1);
});
@@ -189,6 +189,9 @@ public class IngameListener extends GameStateBukkitListener {
@EventHandler
public void onBlockPhysics(BlockPhysicsEvent event) {
if (!WorldConfig.MELTING) {
return;
}
if (event.getSourceBlock().getType() != Material.LAVA) {
return;
}
@@ -202,11 +205,9 @@ public class IngameListener extends GameStateBukkitListener {
}
private void shouldMelt(Block block) {
int meltingTime = WorldConfig.MELTING_TIMES.getOrDefault(block.getType(), -1);
if (meltingTime == -1) {
return;
}
blocksToMelt.computeIfAbsent(time + meltingTime * 20, integer -> new ArrayList<>()).add(block);
if (block.getType().isBurnable()) return;
int meltingTime = (int) (block.getType().getHardness() * 48 * 20);
blocksToMelt.computeIfAbsent(time + meltingTime, integer -> new ArrayList<>()).add(block);
}
@EventHandler

View File

@@ -154,7 +154,7 @@ UNPUNISHMENT_USAGE=§8/§7{0} §8[§eplayer§8]
PUNISHMENT_UNTIL=until {0}
PUNISHMENT_PERMA=permanent
BAN_TEAM={0} §e{1} §7was §e§lbanned§7 by §e{2} {3}§8: §f{4}
BAN_TEAM=§e{0} §7was §e§lbanned§7 by §e{1} {2}§8: §f{3}
BAN_PERMA=§7You are §e§lbanned §epermanently§8: §e{0}
BAN_UNTIL=§7You are §e§lbanned §euntil {0}§8: §e{1}
UNBAN_ERROR=§cThe player isn't banned.
@@ -164,49 +164,49 @@ BAN_AVOIDING_ALERT=§cPotential ban bypass by §r{0}§c: {1}
BAN_AVOIDING_LIST=§c{0} §e{1}
BAN_AVOIDING_BAN_HOVER=§cBan player because of bann bypass.
MUTE_TEAM={0} §e{1} §7was §e§lmuted§7 by §e{2} {3}§8: §f{4}
MUTE_TEAM=§e{0} §7was §e§lmuted§7 by §e{1} {2}§8: §f{3}
MUTE_PERMA=§7You are §epermanently §e§lmuted§8: §e{0}
MUTE_UNTIL=§7You are §e§lmuted §euntil {0}§8: §e{1}
UNMUTE_ERROR=§cThe player isn't muted.
UNMUTE=§7You have §e§lunmuted §e{0}.
NOSCHEMRECEIVING_TEAM={0} §e{1} §7was excluded from §e{2} {3} §7from §e§lrecieving schematics§8: §f{4}
NOSCHEMRECEIVING_TEAM=§e{0} §7was excluded from §e{1} {2} §7from §e§lrecieving schematics§8: §f{3}
NOSCHEMRECEIVING_PERMA=§7You are §epermanently§7 excluded from receiving §e§lschematics§8: §e{0}
NOSCHEMRECEIVING_UNTIL=§7You are excluded from receiving §e§lschematics §euntil {0}§8: §e{1}
UNNOSCHEMRECEIVING_ERROR=§cThe player is not excluded from receiving schematics.
UNNOSCHEMRECEIVING=§e{0} §7may now receive §e§lschematics§7 again§8.
NOSCHEMSHARING_TEAM={0} §e{1} §7was excluded from §e{2} {3} §7from §e§lsharing schematics§8: §f{4}
NOSCHEMSHARING_TEAM=§e{0} §7was excluded from §e{1} {2} §7from §e§lsharing schematics§8: §f{3}
NOSCHEMSHARING_PERMA=§7You are §epermanently§7 excluded from sharing §e§lschematics§8: §e{0}
NOSCHEMSHARING_UNTIL=§7You are excluded from sharing §e§lschematics §euntil {0}§8: §e{1}
UNNOSCHEMSHARING_ERROR=§cThe player is not excluded from sharing schematics.
UNNOSCHEMSHARING=§e{0} §7may now share §e§lschematics§7 again§8.
NOSCHEMSUBMITTING_TEAM={0} §e{1} §7was excluded from §e{2} {3} §7from §e§lsubmitting schematics§8: §f{4}
NOSCHEMSUBMITTING_TEAM=§e{0} §7was excluded from §e{1} {2} §7from §e§lsubmitting schematics§8: §f{3}
NOSCHEMSUBMITTING_PERMA=§7You are §epermanently§7 excluded from submitting §e§lschematics§8: §e{0}
NOSCHEMSUBMITTING_UNTIL=§7You are excluded from submitting §e§lschematics §euntil {0}§8: §e{1}
UNNOSCHEMSUBMITTING_ERROR=§cThe player is not excluded from submitting schematics.
UNNOSCHEMSUBMITTING=§e{0} §7may now submit §e§lschematics§7 again§8.
NODEVSERVER_TEAM={0} §e{1} §7has annoyed §e{2} §7with reason §f{4}§7 and therefore has received §e§ldev server prohibition§7§8, §f{3}
NODEVSERVER_TEAM=§e{0} §7has annoyed §e{1} §7with reason §f{3}§7 and therefore has received §e§ldev server prohibition§7§8, §f{2}
NODEVSERVER_PERMA=§7You are §epermanently§7 excluded from §e§ldev servers§8: §e{0}
NODEVSERVER_UNTIL=§7You are excluded from §e§ldev servers§7 §euntil {0}§8: §e{1}
UNNODEVSERVER_ERROR=§cThe player is not excluded from dev servers.
UNNODEVSERVER=§e{0} §7may now join §e§ldev servers§7 again§8.
NOFIGHTSERVER_TEAM={0} §e{1} §7was excluded from §e{2} {3} §7from §e§lfighting§8: §f{4}
NOFIGHTSERVER_TEAM=§e{0} §7was excluded from §e{1} {2} §7from §e§lfighting§8: §f{3}
NOFIGHTSERVER_PERMA=§7You are §epermanently§7 excluded from §e§lfighting§8: §e{0}
NOFIGHTSERVER_UNTIL=§7You are excluded from §e§lfighting§7 §euntil {0}§8: §e{1}
UNNOFIGHTSERVER_ERROR=§cThe player is not excluded from fighting.
UNNOFIGHTSERVER=§e{0} §7may now join §e§lfights§7 again§8.
NOTEAMSERVER_TEAM={0} §e{1} §7was excluded from §e{2} {3} §7from §e§lteam servers§8: §f{4}
NOTEAMSERVER_TEAM=§e{0} §7was excluded from §e{1} {2} §7from §e§lteam servers§8: §f{3}
NOTEAMSERVER_PERMA=§7You are §epermanently§7 excluded from §e§lteam servers§8: §e{0}
NOTEAMSERVER_UNTIL=§7You are excluded from §e§lteam servers§7 §euntil {0}§8: §e{1}
UNNOTEAMSERVER_ERROR=§cThe player is not excluded from team servers.
UNNOTEAMSERVER=§e{0} §7may now set §e§lteam servers§7 again§8.
NOTE_TEAM={0} §e{1} §7received a §e§lnote§7 from §e{2} {3}: §f{4}
NOTE_TEAM=§e{0} §7received a §e§lnote§7 from §e{1} {2}: §f{3}
#BugCommand
BUG_MESSAGE=§7Please describe the issue in a Discord ticket with the bug ID §e{0} §7further§8.
@@ -572,6 +572,7 @@ CHAT_NO_RECEIVER=§cNobody receives your message
CHAT_EMPTY=§cDon\'t write meaningless empty messages.
CHAT_SERVERTEAM=§8STC §e{0}§8» §f{2}
CHAT_DISCORD_SERVERTEAM=§8STC §e{0}§8» §f{2}
CHAT_GLOBAL={3}{4}{5}{6}{0}§8» {7}{2}
CHAT_DISCORD_GLOBAL=§8Dc {5}{6}{0}§8» {7}{2}
CHAT_TEAM=§8TC §e{0}§8» §f{2}

View File

@@ -138,7 +138,7 @@ UNPUNISHMENT_USAGE=§8/§7{0} §8[§eSpieler§8]
PUNISHMENT_UNTIL=bis zum {0}
PUNISHMENT_PERMA=permanent
BAN_TEAM={0} §e{1} §7wurde von §e{2} {3} §e§lgebannt§8. §7Grund§8: §f{4}
BAN_TEAM=§e{1} §7wurde von §e{2} {3} §e§lgebannt§8. §7Grund§8: §f{4}
BAN_PERMA=§7Du bist §epermanent §e§lgebannt§8. §7Grund§8: §e{0}
BAN_UNTIL=§7Du bist §ebis zum {0} §e§lgebannt§8. §7Grund§8: §e{1}
UNBAN_ERROR=§cDer Spieler ist nicht gebannt.
@@ -148,49 +148,49 @@ BAN_AVOIDING_ALERT=§cMögliche Bannumgehung durch §r{0}§c: {1}
BAN_AVOIDING_LIST=§c{0} §e{1}
BAN_AVOIDING_BAN_HOVER=§cBanne Spieler wegen Bannumgehung
MUTE_TEAM={0} §e{1} §7wurde von §e{2} {3} §e§lgemuted§8. §7Grund§8: §f{4}
MUTE_TEAM=§e{0} §7wurde von §e{1} {2} §e§lgemuted§8. §7Grund§8: §f{3}
MUTE_PERMA=§7Du bist §epermanent §e§lgemuted§8. §7Grund§8: §e{0}
MUTE_UNTIL=§7Du bist §ebis zum {0} §e§lgemuted§8. §7Grund§8: §e{1}
UNMUTE_ERROR=§cDer Spieler ist nicht gemuted.
UNMUTE=§7Du hast §e{0} §e§lentmuted.
NOSCHEMRECEIVING_TEAM={0} §e{1} §7wurde von §e{2} {3} §7vom §e§lSchematicerhalten§7 ausgeschlossen§8: §f{4}
NOSCHEMRECEIVING_TEAM=§e{0} §7wurde von §e{1} {2} §7vom §e§lSchematicerhalten§7 ausgeschlossen§8: §f{3}
NOSCHEMRECEIVING_PERMA=§7Du bist §epermanent §7vom Erhalten von §e§lSchematics§7 ausgeschlossen§8: §e{0}
NOSCHEMRECEIVING_UNTIL=§7Du bist §ebis zum {0} §7vom Erhalten von §e§lSchematics§7 ausgeschlossen§8: §e{1}
UNNOSCHEMRECEIVING_ERROR=§cDer Spieler ist nicht vom Erhalten von Schematics ausgeschlossen.
UNNOSCHEMRECEIVING=§e{0} §7darf nun wieder §e§lSchematics§7 erhalten§8.
NOSCHEMSHARING_TEAM={0} §e{1} §7wurde von §e{2} {3} §7vom §e§lSchematicverteilen§7 ausgeschlossen§8: §f{4}
NOSCHEMSHARING_TEAM=§e{0} §7wurde von §e{1} {2} §7vom §e§lSchematicverteilen§7 ausgeschlossen§8: §f{3}
NOSCHEMSHARING_PERMA=§7Du bist §epermanent §7vom §e§lVerteilen von Schematics§7 ausgeschlossen§8: §e{0}
NOSCHEMSHARING_UNTIL=§7Du bist §ebis zum {0} §7vom §e§lVerteilen von Schematics§7 ausgeschlossen§8: §e{1}
UNNOSCHEMSHARING_ERROR=§cDer Spieler ist nicht vom Verteilen von Schematics ausgeschlossen.
UNNOSCHEMSHARING=§e{0} §7darf nun wieder §e§lSchematics§7 verteilen§8.
NOSCHEMSUBMITTING_TEAM={0} §e{1} §7wurde von §e{2} {3} §7vom §e§lSchematiceinsenden§7 ausgeschlossen§8: §f{4}
NOSCHEMSUBMITTING_TEAM=§e{0} §7wurde von §e{1} {2} §7vom §e§lSchematiceinsenden§7 ausgeschlossen§8: §f{3}
NOSCHEMSUBMITTING_PERMA=§7Du bist §epermanent §7vom §e§lEinsenden von Schematics§7 ausgeschlossen§8: §e{0}
NOSCHEMSUBMITTING_UNTIL=§7Du bist §ebis zum {0} §7vom §e§lEinsenden von Schematics§7 ausgeschlossen§8: §e{1}
UNNOSCHEMSUBMITTING_ERROR=§cDer Spieler ist nicht vom Einsenden von Schematics ausgeschlossen.
UNNOSCHEMSUBMITTING=§e{0} §7darf nun wieder §e§lSchematis§7 einsenden§8.
NODEVSERVER_TEAM={0} §e{1} §7hat §e{2} §7mit Grund §f{4}§7 zu genervt und hat daher §e§lDevserververbot§7 erhalten§8, §f{3}
NODEVSERVER_TEAM=§e{0} §7hat §e{1} §7mit Grund §f{3}§7 zu genervt und hat daher §e§lDevserververbot§7 erhalten§8, §f{2}
NODEVSERVER_PERMA=§7Du bist §epermanent §7vom §e§lDevserver§7 ausgeschlossen§8: §e{0}
NODEVSERVER_UNTIL=§7Du bist §ebis zum {0} §7vom §e§lDevserver§7 ausgeschlossen§8: §e{1}
UNNODEVSERVER_ERROR=§cDer Spieler ist nicht vom Devserver ausgeschlossen.
UNNODEVSERVER=§e{0} §7darf nun wieder dem §e§lDevserver§7 beitreten§8.
NOFIGHTSERVER_TEAM={0} §e{1} §7wurde von §e{2} {3} §7vom §e§lKämpfen§7 ausgeschlossen§8: §f{4}
NOFIGHTSERVER_TEAM=§e{0} §7wurde von §e{1} {2} §7vom §e§lKämpfen§7 ausgeschlossen§8: §f{3}
NOFIGHTSERVER_PERMA=§7Du bist §epermanent §7vom §e§lKämpfen§7 ausgeschlossen§8: §e{0}
NOFIGHTSERVER_UNTIL=§7Du bist §ebis zum {0} §7vom §e§lKämpfen§7 ausgeschlossen§8: §e{1}
UNNOFIGHTSERVER_ERROR=§cDer Spieler ist nicht vom Kämpfen ausgeschlossen.
UNNOFIGHTSERVER=§e{0} §7darf nun wieder §e§lKämpfen§7 beitreten§8.
NOTEAMSERVER_TEAM={0} §e{1} §7wurde von §e{2} {3} §7vom §e§lTeamserver§7 setzen ausgeschlossen§8: §f{4}
NOTEAMSERVER_TEAM=§e{0} §7wurde von §e{1} {2} §7vom §e§lTeamserver§7 setzen ausgeschlossen§8: §f{3}
NOTEAMSERVER_PERMA=§7Du bist §epermanent §7vom §e§lTeamserver§7 setzen ausgeschlossen§8: §e{0}
NOTEAMSERVER_UNTIL=§7Du bist §ebis zum {0} §7vom §e§lTeamserver§7 setzen ausgeschlossen§8: §e{1}
UNNOTEAMSERVER_ERROR=§cDer Spieler ist nicht vom Teamserver setzten ausgeschlossen.
UNNOTEAMSERVER=§e{0} §7darf nun wieder §e§lTeamserver§7 setzen§8.
NOTE_TEAM={0} §e{1} §7erhielt von §e{2} {3} §7die §e§lNotiz§7§8: §f{4}
NOTE_TEAM=§e{0} §7erhielt von §e{1} {2} §7die §e§lNotiz§7§8: §f{3}
#BugCommand
BUG_MESSAGE=§7Bitte beschreibe das Problem in einem Discordticket genauer und gebe dabei die Bug-ID §e{0} §7an§8.
@@ -536,9 +536,9 @@ CHAT_YOYONOW_3=Vielen Dank.
CHAT_YOYONOW_4=Ich wünsche dir noch weiterhin ein reibungsloses Spielerlebnis.
CHAT_CHAOSCAOT_1=Du hast mich gerufen!
CHAT_CHAOSCAOT_2=Wenn etwas nicht funktioniert, dann nen es einfach ein Feature.
CHAT_CHAOSCAOT_3=Und wenn es ein Feature ist, dann kann es nicht kaputt.
CHAT_CHAOSCAOT_4=Kaputt ist nur eine Definition. Wenn du es als Feature definiert, dann kann es nicht kaputt sein.
CHAT_CHAOSCAOT_5=Und wenn du es als kaputt definiert, dann sag uns bescheid mit dem Befehl "/bug <MESSAGE>".
CHAT_CHAOSCAOT_3=Und wenn es ein Feature ist, dann kann es nicht kaputt sein.
CHAT_CHAOSCAOT_4=Kaputt ist nur eine Definition. Wenn du alles als Feature definierst, dann kann es auch keine Bugs geben.
CHAT_CHAOSCAOT_5=Solltest du es aber doch als Bug definieren, dann sag uns bescheid mit dem Befehl "/bug <MESSAGE>".
CHAT_CHAOSCAOT_6=Vielen Dank.
CHAT_RECEIVE=§cUm Chatnachrichten versenden zu können, musst du auch welche empfangen!
CHAT_NO_LINKS=§cDu darfst keine Links versenden.
@@ -547,6 +547,7 @@ CHAT_NO_RECEIVER=§cNiemand empfängt deine Nachricht
CHAT_EMPTY=§cSchreibe keine inhaltslosen Nachrichten.
CHAT_SERVERTEAM=§8STC §e{0}§8» §f{2}
CHAT_DISCORD_SERVERTEAM=§8STC §e{0}§8» §f{2}
CHAT_GLOBAL={3}{4}{5}{6}{0}§8» {7}{2}
CHAT_DISCORD_GLOBAL=§8Dc {5}{6}{0}§8» {7}{2}
CHAT_TEAM=§8TC §e{0}§8» §f{2}

View File

@@ -22,6 +22,7 @@ package de.steamwar.velocitycore;
import de.steamwar.sql.SchematicType;
import lombok.Getter;
import java.time.LocalDateTime;
import java.util.*;
public class ArenaMode extends GameModeConfig {
@@ -76,7 +77,7 @@ public class ArenaMode extends GameModeConfig {
public static List<String> getAllChatNames(boolean historic) {
List<String> chatNames = new LinkedList<>();
for(ArenaMode mode : byInternal.values()){
if(historic == mode.isHistoric())
if(mode.isActive() && historic == mode.isHistoric())
chatNames.addAll(mode.getServer().getChatNames());
}
return chatNames;
@@ -94,6 +95,9 @@ public class ArenaMode extends GameModeConfig {
@Getter
private String config;
@Getter
private List<Integer> ActiveMonths = Collections.emptyList();
public String hasMap(String map){
for(String m : getMaps()) {
if(m.equalsIgnoreCase(map))
@@ -114,8 +118,10 @@ public class ArenaMode extends GameModeConfig {
return getServer().getChatNames().get(0);
}
public boolean withoutChatName(){
return getServer().getChatNames().isEmpty();
public boolean isActive() {
if (getServer().getChatNames().isEmpty()) return false;
if (ActiveMonths.isEmpty()) return true;
return ActiveMonths.contains(LocalDateTime.now().getMonth().getValue());
}
public String getSchemTypeOrInternalName() {

View File

@@ -135,12 +135,12 @@ public class DevCommand extends SWCommand {
});
devServerPorts.forEach((username, value) -> {
if (devServers.containsKey(username))
return;
SteamwarUser user = SteamwarUser.get(username);
String name = "Dev " + user.getUserName();
((VelocityViaConfig) Via.getConfig()).getVelocityServerProtocols().put(name, ServerVersion.get(devServerVersions.get(username)).getProtocolVersion().getProtocol());
if (devServers.containsKey(username))
return;
devServers.put(user.getUserName().toLowerCase(), VelocityCore.getProxy().registerServer(new ServerInfo(name, new InetSocketAddress("127.0.0.1", value))));
});
}

View File

@@ -45,7 +45,7 @@ public class FightCommand extends SWCommand {
private static void getModes(Chatter sender, String precommand, boolean historic){
Component start = Component.empty();
for(ArenaMode mode : ArenaMode.getAllModes()){
if(mode.withoutChatName() || mode.isHistoric() != historic)
if (!mode.isActive() || mode.isHistoric() != historic)
continue;
String command = precommand + mode.getChatName();

View File

@@ -20,6 +20,8 @@
package de.steamwar.velocitycore.commands;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.proxy.Velocity;
import de.steamwar.velocitycore.VelocityCore;
import de.steamwar.velocitycore.listeners.ChatListener;
import de.steamwar.command.SWCommand;
import de.steamwar.messages.Chatter;
@@ -41,7 +43,7 @@ public class MsgCommand extends SWCommand {
}
public static void msg(PlayerChatter sender, Player target, String[] args) {
if(target == null || target.getCurrentServer().isEmpty()) {
if(target == null || VelocityCore.getProxy().getPlayer(target.getUniqueId()).orElse(null) == null) {
sender.system("MSG_OFFLINE");
return;
}

View File

@@ -210,7 +210,7 @@ public class PunishmentCommand {
target.punish(punishmentType, banTime, msg, punisher.getId(), isPerma);
if(punishmentType == Punishment.PunishmentType.Ban)
ban(target, banTime, msg, punisher, isPerma);
Chatter.serverteam().system(punishmentType.getTeamMessage(), new Message("PREFIX"), target, sender, new Message((isPerma ? "PUNISHMENT_PERMA" : "PUNISHMENT_UNTIL"), banTime), msg);
Chatter.serverteam().system(punishmentType.getTeamMessage(), target, sender, new Message((isPerma ? "PUNISHMENT_PERMA" : "PUNISHMENT_UNTIL"), banTime), msg);
}
@Register

View File

@@ -49,7 +49,11 @@ public class TypeMappers {
return new TypeMapper<>() {
@Override
public ArenaMode map(Chatter sender, PreviousArguments previousArguments, String s) {
return ArenaMode.getByChat(s);
ArenaMode arenaMode = ArenaMode.getByChat(s);
if (arenaMode == null) return null;
if (arenaMode.isHistoric() != historic) return null;
if (!arenaMode.isActive()) return null;
return arenaMode;
}
@Override

View File

@@ -167,14 +167,19 @@ public class DiscordBot {
checklistChannel = new ChecklistChannel(config.channel("checklist"));
config.getCouncilThread().forEach((roleId, threadId) -> new CouncilChannel(DiscordBot.getGuild().getRoleById(roleId), DiscordBot.getGuild().getThreadChannelById(threadId)));
announcementChannel = new DiscordChannel(config.channel("announcement")) {
announcementChannel = new DiscordChannel(config.channel("announcement"), 0) {
@Override
public void received(MessageReceivedEvent event) {
Chatter.broadcast().system("ALERT", event.getMessage().getContentDisplay());
}
};
ingameChat = new DiscordChatRoom(config.channel("ingame"), "CHAT_DISCORD_GLOBAL", Chatter::broadcast);
serverTeamChat = new DiscordChatRoom(config.channel("serverteam"), "CHAT_SERVERTEAM", Chatter::serverteam);
// There is a hard limit of 30 messages per minute to send as a webhook, thus with 8 webhooks we can send
// 240 messages per minute. Which means 4 every second. I looked at the WGS fights and there were around
// ~70 in a short burst and then rather long no new message.
ingameChat = new DiscordChatRoom(config.channel("ingame"), "CHAT_DISCORD_GLOBAL", Chatter::globalChat, 8);
// 60 messages per minute should be enough for the server team!
serverTeamChat = new DiscordChatRoom(config.channel("serverteam"), "CHAT_DISCORD_SERVERTEAM", Chatter::serverteam, 2);
VelocityCore.schedule(() -> {
try {

View File

@@ -32,7 +32,7 @@ public class ChecklistChannel extends DiscordChannel {
private final List<Integer> lastSchematics = new ArrayList<>();
public ChecklistChannel(String channel) {
super(channel);
super(channel, 0);
}
public void update() {

View File

@@ -26,17 +26,21 @@ import de.steamwar.velocitycore.discord.DiscordBot;
import de.steamwar.velocitycore.discord.listeners.ChannelListener;
import lombok.AllArgsConstructor;
import lombok.Getter;
import net.dv8tion.jda.api.entities.Icon;
import net.dv8tion.jda.api.entities.User;
import net.dv8tion.jda.api.entities.Webhook;
import net.dv8tion.jda.api.entities.WebhookClient;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import net.dv8tion.jda.api.entities.channel.middleman.MessageChannel;
import net.dv8tion.jda.api.events.interaction.component.GenericComponentInteractionCreateEvent;
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
import net.dv8tion.jda.api.utils.ImageProxy;
import net.dv8tion.jda.api.utils.messages.MessageCreateBuilder;
import net.dv8tion.jda.internal.requests.IncomingWebhookClientImpl;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import java.util.ArrayDeque;
import java.util.Queue;
@AllArgsConstructor
public class DiscordChannel extends Chatter.PlayerlessChatter {
@@ -46,21 +50,36 @@ public class DiscordChannel extends Chatter.PlayerlessChatter {
return user != null ? user : SteamwarUser.get(0);
}
private final Queue<Webhook> webhooks = new ArrayDeque<>();
private final SteamwarUser user;
@Getter
private final MessageChannel channel;
private final int maxNumberOfWebhooks;
public DiscordChannel(User user) {
this(userOrPublic(user), user.openPrivateChannel().complete());
this(userOrPublic(user), user.openPrivateChannel().complete(), 0);
}
public DiscordChannel(String channel) {
this(DiscordBot.getGuild().getTextChannelById(channel));
public DiscordChannel(String channel, int maxNumberOfWebhooks) {
this(DiscordBot.getGuild().getTextChannelById(channel), maxNumberOfWebhooks);
}
public DiscordChannel(MessageChannel channel) {
this(SteamwarUser.get(-1), channel);
public DiscordChannel(MessageChannel channel, int maxNumberOfWebhooks) {
this(SteamwarUser.get(-1), channel, maxNumberOfWebhooks);
ChannelListener.getChannels().put(this.channel, this);
if (channel instanceof TextChannel) {
TextChannel textChannel = (TextChannel) channel;
webhooks.addAll(textChannel.retrieveWebhooks().complete());
while (webhooks.size() > maxNumberOfWebhooks) {
webhooks.remove().delete().queue();
}
while (webhooks.size() < maxNumberOfWebhooks) {
webhooks.add(textChannel.createWebhook(DiscordBot.getInstance().getJda().getSelfUser().getName()).complete());
}
}
}
public void send(String message) {
@@ -70,7 +89,7 @@ public class DiscordChannel extends Chatter.PlayerlessChatter {
.replace("@here", "`@here`")
.replaceAll("<[@#]!?\\d+>", "`$0`");
if (getChannel() instanceof TextChannel && message.contains("»")) {
if (maxNumberOfWebhooks > 0 && getChannel() instanceof TextChannel && message.contains("»")) {
String[] strings = message.split("»", 2);
String userName = strings[0];
String sendMessage = strings[1];
@@ -85,29 +104,24 @@ public class DiscordChannel extends Chatter.PlayerlessChatter {
return;
}
ImageProxy avatarUrl;
String avatarUrl;
if (user.getDiscordId() != null) {
avatarUrl = DiscordBot.getGuild().retrieveMemberById(user.getDiscordId()).complete().getEffectiveAvatar();
avatarUrl = DiscordBot.getGuild().retrieveMemberById(user.getDiscordId()).complete().getEffectiveAvatarUrl();
} else {
avatarUrl = DiscordBot.getInstance().getJda().getSelfUser().getAvatar();
avatarUrl = DiscordBot.getInstance().getJda().getSelfUser().getAvatarUrl();
}
TextChannel textChannel = (TextChannel) getChannel();
try {
textChannel.createWebhook(userName)
.setAvatar(Icon.from(avatarUrl.download(128).get()))
.onSuccess(webhook -> {
webhook.sendMessage(sendMessage)
.onSuccess(__ -> {
webhook.delete().queue();
})
.queue();
})
.queue();
return;
} catch (Exception e) {
// Ignore and send message as normal!
}
Webhook webhook = webhooks.poll();
webhooks.add(webhook);
// This works as per this documentation: https://discord.com/developers/docs/resources/webhook#execute-webhook
IncomingWebhookClientImpl webhookClient = (IncomingWebhookClientImpl) WebhookClient.createClient(DiscordBot.getInstance().getJda(), webhook.getUrl());
webhookClient.sendRequest()
.setUsername(userName)
.setAvatarUrl(avatarUrl)
.setContent(sendMessage)
.queue();
return;
}
send(new MessageCreateBuilder()

View File

@@ -33,8 +33,8 @@ public class DiscordChatRoom extends DiscordChannel {
private final String format;
private final Supplier<ChatterGroup> target;
public DiscordChatRoom(String channel, String format, Supplier<ChatterGroup> target) {
super(channel);
public DiscordChatRoom(String channel, String format, Supplier<ChatterGroup> target, int maxNumberOfWebhooks) {
super(channel, maxNumberOfWebhooks);
this.format = format;
this.target = target;
}

View File

@@ -46,14 +46,14 @@ public class StaticMessageChannel extends DiscordChannel {
}
public StaticMessageChannel(String channel, Supplier<MessageCreateBuilder> supplier, Consumer<GenericComponentInteractionCreateEvent> interaction) {
super(channel);
super(channel, 0);
this.supplier = supplier;
this.interaction = interaction;
init();
}
public StaticMessageChannel(MessageChannel channel, Supplier<MessageCreateBuilder> supplier, Consumer<GenericComponentInteractionCreateEvent> interaction) {
super(channel);
super(channel, 0);
this.supplier = supplier;
this.interaction = interaction;
init();

View File

@@ -70,7 +70,7 @@ public class DiscordTicketHandler extends ListenerAdapter {
Permission.MESSAGE_HISTORY).complete();
ticketChannel.getManager().setTopic(event.getUser().getId()).complete();
DiscordChannel channel = new DiscordChannel(DiscordChannel.userOrPublic(event.getUser()), ticketChannel);
DiscordChannel channel = new DiscordChannel(DiscordChannel.userOrPublic(event.getUser()), ticketChannel, 0);
channel.send(new MessageCreateBuilder()
.setEmbeds(new EmbedBuilder()
.setTitle(channel.parseToPlain("DC_TICKET_TITLE"))

View File

@@ -377,7 +377,8 @@ public class PluginMessage extends BasicListener {
"axiom:hello", "axiom:set_gamemode", "axiom:set_fly_speed", "axiom:set_world_time",
"axiom:set_world_property", "axiom:set_block", "axiom:set_hotbar_slot", "axiom:switch_active_hotbar",
"axiom:teleport", "axiom:request_chunk_data", "axiom:spawn_entity", "axiom:response_entity_data",
"axiom:manipulate_entity", "axiom:delete_entity", "axiom:marker_nbt_request", "axiom:set_buffer"
"axiom:manipulate_entity", "axiom:delete_entity", "axiom:marker_nbt_request", "axiom:set_buffer",
"axiom:allowed_gamemodes", "axiom:ignore_display_entities", "axiom:add_server_heightmap"
)) {
channelRegisterHandlers.put(channel, player -> {});
registerPassthroughToServer(channel);

View File

@@ -27,7 +27,6 @@ private val isInCi by lazy { Os.isFamily(Os.FAMILY_UNIX) && ProcessBuilder("host
dependencyResolutionManagement {
repositories {
maven {
url = URI("https://m2.dv8tion.net/releases")
content {