Skip to content

Commit

Permalink
Add ragdoll physics on death
Browse files Browse the repository at this point in the history
  • Loading branch information
emortaldev committed Aug 28, 2024
1 parent a1ccfce commit 5bf37e5
Show file tree
Hide file tree
Showing 21 changed files with 659 additions and 72 deletions.
1 change: 1 addition & 0 deletions Dockerfile
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ RUN apt-get update && apt-get install -y wget

COPY build/libs/*-all.jar /app/parkourtag.jar
COPY run/maps /app/maps
COPY run/natives /app/natives

ENTRYPOINT ["java"]
CMD ["-jar", "/app/parkourtag.jar"]
2 changes: 2 additions & 0 deletions build.gradle.kts
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ dependencies {

implementation("dev.hollowcube:polar:1.11.1")
implementation("net.kyori:adventure-text-minimessage:4.16.0")

implementation("com.github.stephengold:Libbulletjme:21.2.1")
}

java {
Expand Down
Binary file added run/natives/Linux64ReleaseSp_libbulletjme.so
Binary file not shown.
5 changes: 5 additions & 0 deletions src/main/java/dev/emortal/minestom/parkourtag/Main.java
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
package dev.emortal.minestom.parkourtag;

import com.jme3.system.NativeLibraryLoader;
import dev.emortal.minestom.gamesdk.MinestomGameServer;
import dev.emortal.minestom.gamesdk.config.GameSdkConfig;
import dev.emortal.minestom.parkourtag.commands.CreditsCommand;
import dev.emortal.minestom.parkourtag.map.MapManager;
import net.minestom.server.MinecraftServer;

import java.io.File;

public final class Main {

public static void main(String[] args) {
NativeLibraryLoader.loadLibbulletjme(true, new File("natives/"), "Release", "Sp");

MinestomGameServer server = MinestomGameServer.create(() -> {
MapManager mapManager = new MapManager();

Expand Down
13 changes: 10 additions & 3 deletions src/main/java/dev/emortal/minestom/parkourtag/ParkourTagGame.java
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@
import dev.emortal.minestom.gamesdk.config.GameCreationInfo;
import dev.emortal.minestom.gamesdk.game.Game;
import dev.emortal.minestom.gamesdk.util.GameWinLoseMessages;
import dev.emortal.minestom.parkourtag.listeners.parkourtag.ParkourTagAttackListener;
import dev.emortal.minestom.parkourtag.listeners.parkourtag.ParkourTagDoubleJumpListener;
import dev.emortal.minestom.parkourtag.listeners.parkourtag.ParkourTagTickListener;
import dev.emortal.minestom.parkourtag.listeners.ParkourTagAttackListener;
import dev.emortal.minestom.parkourtag.listeners.ParkourTagDoubleJumpListener;
import dev.emortal.minestom.parkourtag.listeners.ParkourTagTickListener;
import dev.emortal.minestom.parkourtag.map.LoadedMap;
import dev.emortal.minestom.parkourtag.map.MapData;
import dev.emortal.minestom.parkourtag.physics.MinecraftPhysicsHandler;
import io.micrometer.core.instrument.Gauge;
import io.micrometer.core.instrument.Metrics;
import net.kyori.adventure.bossbar.BossBar;
Expand Down Expand Up @@ -454,10 +455,16 @@ private void victory(Set<Player> winners) {
return map;
}

public MinecraftPhysicsHandler getPhysicsHandler() {
return getMap().physicsHandler();
}

@Override
public void cleanUp() {
this.map.instance().scheduleNextTick(MinecraftServer.getInstanceManager()::unregisterInstance);

this.getPhysicsHandler().cleanup();

MinecraftServer.getBossBarManager().destroyBossBar(this.bossBar);
if (this.gameTimerTask != null) this.gameTimerTask.cancel();
}
Expand Down
26 changes: 14 additions & 12 deletions .../parkourtag/ParkourTagAttackListener.java → ...g/listeners/ParkourTagAttackListener.java
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,22 +1,21 @@
package dev.emortal.minestom.parkourtag.listeners.parkourtag;
package dev.emortal.minestom.parkourtag.listeners;

import dev.emortal.minestom.parkourtag.GameStage;
import dev.emortal.minestom.parkourtag.ParkourTagGame;
import dev.emortal.minestom.parkourtag.utils.FireworkUtils;
import dev.emortal.minestom.parkourtag.physics.PlayerRagdoll;
import net.kyori.adventure.sound.Sound;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextDecoration;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import net.kyori.adventure.title.Title;
import net.minestom.server.color.Color;
import net.minestom.server.coordinate.Vec;
import net.minestom.server.entity.GameMode;
import net.minestom.server.entity.Player;
import net.minestom.server.event.Event;
import net.minestom.server.event.EventNode;
import net.minestom.server.event.entity.EntityAttackEvent;
import net.minestom.server.item.component.FireworkExplosion;
import net.minestom.server.network.packet.server.play.HitAnimationPacket;
import net.minestom.server.sound.SoundEvent;
import net.minestom.server.tag.Tag;
Expand Down Expand Up @@ -89,6 +88,9 @@ public static void registerListener(EventNode<Event> eventNode, ParkourTagGame g
target.setAutoViewable(false);
target.setGlowing(false);

Vec impulse = attacker.getPosition().direction().mul(60);
PlayerRagdoll.spawnRagdollWithImpulse(game.getPhysicsHandler(), target, target.getPosition(), impulse);

game.getTaggers().remove(target);
game.getGoons().remove(target);
target.setTeam(ParkourTagGame.DEAD_TEAM);
Expand All @@ -105,14 +107,14 @@ public static void registerListener(EventNode<Event> eventNode, ParkourTagGame g
target.setVelocity(attacker.getPosition().direction().mul(15.0));

// Firework death effect
FireworkExplosion randomColorEffect = new FireworkExplosion(
FireworkExplosion.Shape.LARGE_BALL,
List.of(new Color(java.awt.Color.HSBtoRGB(random.nextFloat(), 1f, 1f))),
List.of(),
false,
false
);
FireworkUtils.showFirework(game.getPlayers(), e.getInstance(), target.getPosition().add(0, 1.5, 0), List.of(randomColorEffect));
// FireworkExplosion randomColorEffect = new FireworkExplosion(
// FireworkExplosion.Shape.LARGE_BALL,
// List.of(new Color(java.awt.Color.HSBtoRGB(random.nextFloat(), 1f, 1f))),
// List.of(),
// false,
// false
// );
// FireworkUtils.showFirework(game.getPlayers(), e.getInstance(), target.getPosition().add(0, 1.5, 0), List.of(randomColorEffect));

// Check for win with new alive count
game.checkPlayerCounts();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package dev.emortal.minestom.parkourtag.listeners.parkourtag;
package dev.emortal.minestom.parkourtag.listeners;

import dev.emortal.minestom.parkourtag.GameStage;
import dev.emortal.minestom.parkourtag.ParkourTagGame;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package dev.emortal.minestom.parkourtag.listeners.parkourtag;
package dev.emortal.minestom.parkourtag.listeners;

import dev.emortal.minestom.parkourtag.GameStage;
import dev.emortal.minestom.parkourtag.ParkourTagGame;
import dev.emortal.minestom.parkourtag.map.MapManager;
import dev.emortal.minestom.parkourtag.utils.NoTickEntity;
import dev.emortal.minestom.parkourtag.utils.Quaternion;
import dev.emortal.minestom.parkourtag.utils.PTQuaternion;
import net.kyori.adventure.sound.Sound;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
Expand Down Expand Up @@ -84,6 +84,7 @@ private static void createRailParticle(double x, double y, double z, ParkourTagG

ParticlePacket packet = new ParticlePacket(Particle.SNOWFLAKE, true, x + Math.sin(tick * spinSpeed) * spinScale, y, z + Math.cos(tick * spinSpeed) * spinScale, 0f, 0.5f, 0f, 1, 0);

if (game.getGameStage() == GameStage.PRE_GAME) return;
game.sendGroupedPacket(packet);
}).repeat(TaskSchedule.tick(1)).schedule();
}
Expand All @@ -100,7 +101,7 @@ private static void createRailTextDisplay(double x, double y, double z, int dela
meta.setText(off);
meta.setBackgroundColor(0);
meta.setScale(new Vec(7.5));
Quaternion quaternion = new Quaternion(new Vec(1, 0, 0), Math.toRadians(90));
PTQuaternion quaternion = new PTQuaternion(new Vec(1, 0, 0), Math.toRadians(90));
meta.setLeftRotation(new float[]{(float) quaternion.getW(), (float) quaternion.getX(), (float) quaternion.getY(), (float) quaternion.getZ()});
entity.setTag(onTag, false);

Expand Down
3 changes: 2 additions & 1 deletion src/main/java/dev/emortal/minestom/parkourtag/map/LoadedMap.java
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package dev.emortal.minestom.parkourtag.map;

import dev.emortal.minestom.parkourtag.physics.MinecraftPhysicsHandler;
import net.minestom.server.instance.Instance;
import org.jetbrains.annotations.NotNull;

public record LoadedMap(@NotNull Instance instance, @NotNull MapData mapData) {
public record LoadedMap(@NotNull Instance instance, @NotNull MapData mapData, @NotNull MinecraftPhysicsHandler physicsHandler) {
}
24 changes: 18 additions & 6 deletions src/main/java/dev/emortal/minestom/parkourtag/map/MapManager.java
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@

import com.google.gson.Gson;
import com.google.gson.stream.JsonReader;
import dev.emortal.minestom.parkourtag.utils.NoopChunkLoader;
import com.jme3.bullet.collision.PhysicsCollisionObject;
import dev.emortal.minestom.parkourtag.physics.MinecraftPhysicsHandler;
import dev.emortal.minestom.parkourtag.physics.worldmesh.ChunkMesher;
import net.hollowcube.polar.PolarLoader;
import net.minestom.server.MinecraftServer;
import net.minestom.server.instance.Chunk;
import net.minestom.server.instance.IChunkLoader;
import net.minestom.server.instance.Instance;
import net.minestom.server.instance.InstanceContainer;
import net.minestom.server.registry.DynamicRegistry;
Expand Down Expand Up @@ -67,18 +70,25 @@ public MapManager() {
instance.setTimeSynchronizationTicks(0);

// Do some preloading!
List<PhysicsCollisionObject> chunkMeshes = new ArrayList<>();
List<CompletableFuture<Chunk>> futures = new ArrayList<>();
for (int x = -CHUNK_LOADING_RADIUS; x < CHUNK_LOADING_RADIUS; x++) {
for (int z = -CHUNK_LOADING_RADIUS; z < CHUNK_LOADING_RADIUS; z++) {
futures.add(instance.loadChunk(x, z));
CompletableFuture<Chunk> future = instance.loadChunk(x, z);
futures.add(future);
future.thenAccept((chunk) -> {
PhysicsCollisionObject obj = ChunkMesher.createChunk(chunk);
if (obj != null) chunkMeshes.add(obj);
});
}
}

CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).thenRun(() -> {
// Replace polar loader after all chunks are loaded to save memory
instance.setChunkLoader(new NoopChunkLoader());
instance.setChunkLoader(IChunkLoader.noop());
});

maps.put(mapName, new PreLoadedMap(instance, spawns));
maps.put(mapName, new PreLoadedMap(instance, spawns, chunkMeshes));
} catch (IOException exception) {
throw new UncheckedIOException(exception);
}
Expand Down Expand Up @@ -108,16 +118,18 @@ public MapManager() {
return map.load(randomMapId);
}

private record PreLoadedMap(@NotNull InstanceContainer rootInstance, @NotNull MapData mapData) {
private record PreLoadedMap(@NotNull InstanceContainer rootInstance, @NotNull MapData mapData, @NotNull List<PhysicsCollisionObject> chunkMeshes) {

@NotNull LoadedMap load(@NotNull String mapId) {
Instance shared = MinecraftServer.getInstanceManager().createSharedInstance(this.rootInstance());

MinecraftPhysicsHandler physicsHandler = new MinecraftPhysicsHandler(shared, chunkMeshes);

shared.setTag(MAP_ID_TAG, mapId);
shared.setTimeRate(0);
shared.setTimeSynchronizationTicks(0);

return new LoadedMap(shared, this.mapData());
return new LoadedMap(shared, this.mapData(), physicsHandler);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package dev.emortal.minestom.parkourtag.physics;

import com.jme3.bullet.PhysicsSpace;
import com.jme3.bullet.collision.PhysicsCollisionObject;
import com.jme3.math.Vector3f;
import dev.emortal.minestom.parkourtag.physics.objects.MinecraftPhysicsObject;
import net.minestom.server.instance.Instance;
import net.minestom.server.timer.TaskSchedule;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

public class MinecraftPhysicsHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(MinecraftPhysicsHandler.class);

private @Nullable PhysicsSpace physicsSpace;

private final @NotNull List<MinecraftPhysicsObject> objects = new CopyOnWriteArrayList<>();

public MinecraftPhysicsHandler(@NotNull Instance instance, @NotNull List<PhysicsCollisionObject> chunkMeshes) {

// Wrap in scheduler to avoid "WARNING: invoked from wrong thread"
instance.scheduleNextTick((i) -> {
System.out.println("Physics space no longer null");
this.physicsSpace = new PhysicsSpace(PhysicsSpace.BroadphaseType.DBVT);

// Default: -9.81f
// Minecraft: -31.36f
this.physicsSpace.setGravity(new Vector3f(0, -17f, 0));

for (PhysicsCollisionObject chunkMesh : chunkMeshes) {
this.physicsSpace.add(chunkMesh);
}
});

instance.scheduler().buildTask(new Runnable() {
long lastRan = System.nanoTime();
@Override
public void run() {
long diff = System.nanoTime() - lastRan;
float deltaTime = diff / 1_000_000_000f;

lastRan = System.nanoTime();
update(deltaTime);
}
}).delay(TaskSchedule.tick(1)).repeat(TaskSchedule.tick(1)).schedule();
}

public void update(float delta) {
if (physicsSpace == null) return;

physicsSpace.update(delta);
for (MinecraftPhysicsObject object : objects) {
object.updateEntity();
}
}

public void cleanup() {
if (physicsSpace == null) return;
physicsSpace.destroy();
physicsSpace = null;
}

public void addObject(MinecraftPhysicsObject object) {
objects.add(object);
}

public PhysicsSpace getPhysicsSpace() {
return physicsSpace;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package dev.emortal.minestom.parkourtag.physics;

import com.jme3.math.Vector3f;
import dev.emortal.minestom.parkourtag.physics.objects.RagdollPhysics;

public enum PlayerDisplayPart {
HEAD(0, 9, RagdollPhysics.HEAD_SIZE),
RIGHT_ARM(-1024, 12, RagdollPhysics.LIMB_SIZE),
LEFT_ARM(-2048, 11, RagdollPhysics.LIMB_SIZE),
TORSO(-3072, 4, RagdollPhysics.TORSO_SIZE),
RIGHT_LEG(-4096, 12, RagdollPhysics.LIMB_SIZE),
LEFT_LEG(-5120, 13, RagdollPhysics.LIMB_SIZE);

private final double yTranslation;
private final int customModelData;
private final Vector3f size;
PlayerDisplayPart(double yTranslation, int customModelData, Vector3f size) {
this.yTranslation = yTranslation;
this.customModelData = customModelData;
this.size = size;
}

public double getYTranslation() {
return yTranslation;
}

public int getCustomModelData() {
return customModelData;
}

public Vector3f getSize() {
return size;
}
}
Loading

0 comments on commit 5bf37e5

Please sign in to comment.