diff --git a/BauSystem_15/src/de/steamwar/bausystem/features/simulator/Simulator15.java b/BauSystem_15/src/de/steamwar/bausystem/features/simulator/Simulator15.java
new file mode 100644
index 00000000..5576ff45
--- /dev/null
+++ b/BauSystem_15/src/de/steamwar/bausystem/features/simulator/Simulator15.java
@@ -0,0 +1,23 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2023 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 .
+ */
+
+package de.steamwar.bausystem.features.simulator;
+
+public class Simulator15 implements Simulator {
+}
diff --git a/BauSystem_19/src/de/steamwar/bausystem/features/simulator/Simulator19.java b/BauSystem_19/src/de/steamwar/bausystem/features/simulator/Simulator19.java
new file mode 100644
index 00000000..0afd4a54
--- /dev/null
+++ b/BauSystem_19/src/de/steamwar/bausystem/features/simulator/Simulator19.java
@@ -0,0 +1,37 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2023 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 .
+ */
+
+package de.steamwar.bausystem.features.simulator;
+
+import org.bukkit.Bukkit;
+import org.bukkit.World;
+
+public class Simulator19 implements Simulator {
+
+ public static final World WORLD = Bukkit.getWorlds().get(0);
+
+ @Override
+ public void run() {
+ TNT tnt = new TNT(0, 120, 0);
+ do {
+ System.out.println(tnt);
+ } while (!tnt.tick());
+ System.out.println(tnt);
+ }
+}
diff --git a/BauSystem_19/src/de/steamwar/bausystem/features/simulator/TNT.java b/BauSystem_19/src/de/steamwar/bausystem/features/simulator/TNT.java
new file mode 100644
index 00000000..4c1c267f
--- /dev/null
+++ b/BauSystem_19/src/de/steamwar/bausystem/features/simulator/TNT.java
@@ -0,0 +1,327 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2023 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 .
+ */
+
+package de.steamwar.bausystem.features.simulator;
+
+import lombok.Getter;
+import lombok.Setter;
+import net.minecraft.core.EnumDirection;
+import net.minecraft.world.level.RayTrace;
+import net.minecraft.world.phys.AxisAlignedBB;
+import net.minecraft.world.phys.MovingObjectPosition;
+import net.minecraft.world.phys.Vec3D;
+import net.minecraft.world.phys.shapes.VoxelShape;
+import net.minecraft.world.phys.shapes.VoxelShapes;
+import org.bukkit.Material;
+import org.bukkit.World;
+import org.bukkit.block.Block;
+import org.bukkit.block.data.BlockData;
+import org.bukkit.block.data.type.Bed;
+import org.bukkit.block.data.type.Fence;
+import org.bukkit.block.data.type.Gate;
+import org.bukkit.block.data.type.Wall;
+import org.bukkit.craftbukkit.v1_19_R2.CraftWorld;
+import org.bukkit.craftbukkit.v1_19_R2.entity.CraftEntity;
+import org.bukkit.entity.Entity;
+import org.bukkit.util.Vector;
+
+import java.util.List;
+import java.util.Random;
+
+@Getter
+@Setter
+public class TNT {
+
+ private static final Random R = new Random();
+
+ // Bottom left corner
+ private double x;
+ private double y;
+ private double z;
+
+ private double vx;
+ private double vy;
+ private double vz;
+
+ private int fuse;
+
+ private boolean onGround = false;
+ private float fallDistance = 0;
+
+ private boolean horizontalCollision = false;
+ private boolean verticalCollision = false;
+ private Vector movementMultiplier = new Vector(0, 0, 0);
+
+ public TNT(double x, double y, double z) {
+ this.x = x;
+ this.y = y;
+ this.z = z;
+
+ double seed = R.nextDouble() * 6.2831854820251465;
+ this.vx = -Math.sin(seed) * 0.02;
+ this.vy = 0.20000000298023224;
+ this.vz = -Math.cos(seed) * 0.02;
+
+ this.fuse = 80;
+ }
+
+ public boolean tick() {
+ this.vy -= 0.04;
+
+ move(new Vector(this.vx, this.vy, this.vz));
+
+ this.vx *= 0.98;
+ this.vy *= 0.98;
+ this.vz *= 0.98;
+
+ this.fuse--;
+ if (this.fuse <= 0) {
+ // TODO: Add explosion
+ return true;
+ }
+ return false;
+ }
+
+ private void move(Vector movement) {
+ if (movement.lengthSquared() > 1.0E-7) {
+ movement.multiply(movementMultiplier);
+ movementMultiplier = new Vector(0, 0, 0);
+ vx = 0;
+ vy = 0;
+ vz = 0;
+ }
+
+ Vector vec3d = adjustMovementForCollisions(movement);
+ double lengthSquared = vec3d.lengthSquared();
+ if (lengthSquared > 1.0E-7) {
+ if (fallDistance != 0.0F) {
+ // TODO: This could be wrong
+ MovingObjectPosition movingObjectPosition = ((CraftWorld) Simulator19.WORLD).getHandle().a(new RayTrace(new Vec3D(x, y, z), new Vec3D(x + vx, y + vy, z + vz), RayTrace.BlockCollisionOption.d, RayTrace.FluidCollisionOption.d, null));
+ if (movingObjectPosition.c() != MovingObjectPosition.EnumMovingObjectType.a) {
+ onLanding();
+ }
+ }
+
+ this.x += movement.getX();
+ this.y += movement.getY();
+ this.z += movement.getZ();
+ }
+
+ boolean bl = !approximatelyEquals(movement.getX(), vec3d.getX());
+ boolean bl2 = !approximatelyEquals(movement.getZ(), vec3d.getZ());
+ this.horizontalCollision = bl || bl2;
+ this.verticalCollision = movement.getY() != vec3d.getY();
+
+ this.onGround = this.verticalCollision && movement.getY() < 0.0D;
+ Vector blockPos = getLandingPos();
+ Block block = Simulator19.WORLD.getBlockAt(blockPos.getBlockX(), blockPos.getBlockY(), blockPos.getBlockZ());
+ fall(vec3d.getX(), onGround, block, blockPos);
+
+ if (horizontalCollision) {
+ this.vx = bl ? 0.0 : this.vx;
+ this.vz = bl2 ? 0.0 : this.vz;
+ }
+
+ if (movement.getY() != vec3d.getY()) {
+ if (block.getType() == Material.SLIME_BLOCK) {
+ if (this.vy < 0.0) {
+ this.vy = -this.vy * 0.8;
+ }
+ }
+ if (block.getBlockData() instanceof Bed) {
+ if (this.vy < 0.0) {
+ this.vy = -this.vy * 0.6600000262260437 * 0.8;
+ }
+ }
+ }
+
+ if (onGround) {
+ if (block.getType() == Material.SLIME_BLOCK) {
+ double cy = Math.abs(this.vy);
+ if (cy < 0.1) {
+ double cy2 = 0.4 + cy * 0.2;
+ this.vx = vy * cy2;
+ this.vz = vz * cy2;
+ }
+ }
+ }
+
+ checkBlockCollision();
+ float j = this.getVelocityMultiplier();
+ this.vy *= j;
+ this.vx *= j;
+ }
+
+ private Vector adjustMovementForCollisions(Vector movement) {
+ AxisAlignedBB boundingBox = new AxisAlignedBB(x, y, z, x + 0.98, y + 0.98, z + 0.98);
+ return movement.lengthSquared() == 0.0 ? movement : adjustMovementForCollisions(null, movement, boundingBox, Simulator19.WORLD);
+ }
+
+ public static Vector adjustMovementForCollisions(Entity entity, Vector movement, AxisAlignedBB entityBoundingBox, World world) {
+ Iterable voxelShapes = ((CraftWorld) world).getHandle().d(((CraftEntity) entity).getHandle(), entityBoundingBox);
+ List collisions = new java.util.ArrayList<>();
+ voxelShapes.forEach(collisions::add);
+
+ return adjustMovementForCollisions(movement, entityBoundingBox, collisions);
+ }
+
+ private static Vector adjustMovementForCollisions(Vector movement, AxisAlignedBB entityBoundingBox, List collisions) {
+ if (collisions.isEmpty()) {
+ return movement;
+ } else {
+ double d = movement.getX();
+ double e = movement.getY();
+ double f = movement.getZ();
+ if (e != 0.0) {
+ e = VoxelShapes.a(EnumDirection.EnumAxis.b, entityBoundingBox, collisions, e);
+ if (e != 0.0) {
+ entityBoundingBox = entityBoundingBox.c(0.0, e, 0.0);
+ }
+ }
+
+ boolean bl = Math.abs(d) < Math.abs(f);
+ if (bl && f != 0.0) {
+ f = VoxelShapes.a(EnumDirection.EnumAxis.c, entityBoundingBox, collisions, f);
+ if (f != 0.0) {
+ entityBoundingBox = entityBoundingBox.c(0.0, 0.0, f);
+ }
+ }
+
+ if (d != 0.0) {
+ d = VoxelShapes.a(EnumDirection.EnumAxis.a, entityBoundingBox, collisions, d);
+ if (!bl && d != 0.0) {
+ entityBoundingBox = entityBoundingBox.c(d, 0.0, 0.0);
+ }
+ }
+
+ if (!bl && f != 0.0) {
+ f = VoxelShapes.a(EnumDirection.EnumAxis.c, entityBoundingBox, collisions, f);
+ }
+
+ return new Vector(d, e, f);
+ }
+ }
+
+ private boolean approximatelyEquals(double a, double b) {
+ return Math.abs(b - a) < 9.999999747378752E-6;
+ }
+
+ private int floor(double value) {
+ int i = (int)value;
+ return value < (double)i ? i - 1 : i;
+ }
+
+ private Vector getLandingPos() {
+ int x = floor(this.x);
+ int y = floor(this.y - 0.2F);
+ int z = floor(this.z);
+
+ if (Simulator19.WORLD.getBlockAt(x, y, z).isEmpty()) {
+ Block block = Simulator19.WORLD.getBlockAt(x, y - 1, z);
+ BlockData blockData = block.getBlockData();
+ if (blockData instanceof Fence || blockData instanceof Wall || blockData instanceof Gate) {
+ return new Vector(x, y - 1, z);
+ }
+ }
+ return new Vector(x, y, z);
+ }
+
+ private void fall(double heightDifference, boolean onGround, Block state, Vector landedPosition) {
+ if (onGround) {
+ if (this.fallDistance > 0.0F) {
+ state.getBlock().onLandedUpon(this.world, state, landedPosition, this, this.fallDistance);
+ }
+
+ this.onLanding();
+ } else if (heightDifference < 0.0) {
+ this.fallDistance -= (float)heightDifference;
+ }
+ }
+
+ private void checkBlockCollision() {
+ int x1 = floor(x + 1.0E-7);
+ int y1 = floor(y + 1.0E-7);
+ int z1 = floor(z + 1.0E-7);
+ int x2 = floor(x + 0.98 - 1.0E-7);
+ int y2 = floor(y + 0.98 - 1.0E-7);
+ int z2 = floor(z + 0.98 - 1.0E-7);
+
+ for (int x = x1; x <= x2; x++) {
+ for (int y = y1; y <= y2; y++) {
+ for (int z = z1; z <= z2; z++) {
+ Block block = Simulator19.WORLD.getBlockAt(x, y, z);
+
+ if (block.getType() == Material.POWDER_SNOW) {
+ slowMovement(new Vector(0.8999999761581421, 1.5, 0.8999999761581421));
+ } else if (block.getType() == Material.HONEY_BLOCK) {
+ if (isSliding(new Vector(x, y, z))) {
+ updateSlidingVelocity();
+ }
+ } else if (block.getType() == Material.COBWEB) {
+ slowMovement(new Vector(0.25, 0.05000000074505806, 0.25));
+ } else if (block.getType() == Material.BUBBLE_COLUMN) {
+ // TODO: Bubble column
+ }
+ }
+ }
+ }
+ }
+
+ private void slowMovement(Vector movementMultiplier) {
+ onLanding();
+ this.movementMultiplier = movementMultiplier;
+ }
+
+ private void updateSlidingVelocity() {
+ if (vy < -0.13) {
+ double d = -0.05 / vy;
+ vx *= d;
+ vz *= d;
+ }
+ vy = -0.05;
+ onLanding();
+ }
+
+ private boolean isSliding(Vector pos) {
+ if (onGround) return false;
+ if (y > pos.getY() + 0.9375 - 1.0E-7) return false;
+ if (vy >= -0.08) return false;
+ double d = Math.abs(pos.getX() + 0.5 - x);
+ double e = Math.abs(pos.getZ() + 0.5 - z);
+ double f = 0.4375 + 0.98 / 2.0;
+ return d + 1.0E-7 > f || e + 1.0E-7 > f;
+ }
+
+ private void onLanding() {
+ this.fallDistance = 0.0F;
+ }
+
+ @Override
+ public String toString() {
+ return "TNT{" +
+ "x=" + x +
+ ", y=" + y +
+ ", z=" + z +
+ ", vx=" + vx +
+ ", vy=" + vy +
+ ", vz=" + vz +
+ ", fuse=" + fuse +
+ '}';
+ }
+}
diff --git a/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/Simulator.java b/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/Simulator.java
new file mode 100644
index 00000000..6e1a8ab6
--- /dev/null
+++ b/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/Simulator.java
@@ -0,0 +1,30 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2023 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 .
+ */
+
+package de.steamwar.bausystem.features.simulator;
+
+import de.steamwar.bausystem.BauSystem;
+import de.steamwar.core.VersionDependent;
+
+public interface Simulator {
+ Simulator impl = VersionDependent.getVersionImpl(BauSystem.getInstance());
+
+ default void run() {
+ }
+}
diff --git a/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/TestCommand.java b/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/TestCommand.java
new file mode 100644
index 00000000..c0869fd6
--- /dev/null
+++ b/BauSystem_Main/src/de/steamwar/bausystem/features/simulator/TestCommand.java
@@ -0,0 +1,37 @@
+/*
+ * This file is a part of the SteamWar software.
+ *
+ * Copyright (C) 2023 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 .
+ */
+
+package de.steamwar.bausystem.features.simulator;
+
+import de.steamwar.command.SWCommand;
+import de.steamwar.linkage.Linked;
+import org.bukkit.entity.Player;
+
+@Linked
+public class TestCommand extends SWCommand {
+
+ public TestCommand() {
+ super("test");
+ }
+
+ @Register
+ public void genericCommand(Player player) {
+ Simulator.impl.run();
+ }
+}