Compare commits

...

5 commits

Author SHA1 Message Date
7cdff81b36
this 2024-07-17 19:07:42 +02:00
a0a4619755
orient programming to object 2024-07-14 13:32:25 +02:00
7acdda21fa
fix also remove warnings 2024-07-12 09:29:51 +02:00
e1f6bfcf0b
stat 2024-07-11 18:38:52 +02:00
7d69e1db4a
fix psyhics
not a typo
2024-07-10 13:14:06 +02:00
16 changed files with 549 additions and 131 deletions

View file

@ -17,8 +17,13 @@
<dependency> <dependency>
<groupId>net.minestom</groupId> <groupId>net.minestom</groupId>
<artifactId>minestom-snapshots</artifactId> <artifactId>minestom-snapshots</artifactId>
<version>d606051f1e</version> <version>7dd1792096</version>
</dependency> </dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>2.0.13</version>
</dependency>
</dependencies> </dependencies>
</project> </project>

View file

@ -1,6 +1,7 @@
package net.pivipi; package net.pivipi;
import net.minestom.server.coordinate.Pos; import net.minestom.server.coordinate.Pos;
import net.minestom.server.coordinate.Vec;
import net.minestom.server.entity.GameMode; import net.minestom.server.entity.GameMode;
import net.minestom.server.entity.Player; import net.minestom.server.entity.Player;
import net.minestom.server.entity.attribute.Attribute; import net.minestom.server.entity.attribute.Attribute;
@ -9,24 +10,24 @@ import net.minestom.server.event.player.AsyncPlayerConfigurationEvent;
import net.minestom.server.event.player.PlayerDisconnectEvent; import net.minestom.server.event.player.PlayerDisconnectEvent;
import net.minestom.server.event.player.PlayerSpawnEvent; import net.minestom.server.event.player.PlayerSpawnEvent;
import net.minestom.server.instance.Instance; import net.minestom.server.instance.Instance;
import net.minestom.server.instance.Weather;
import net.pivipi.ball.Ball;
import net.pivipi.ball.BallKicker; import net.pivipi.ball.BallKicker;
import net.pivipi.world.Stadium; import net.pivipi.game.Game;
import net.pivipi.game.Stadium;
public class LoginHandler { public class LoginHandler {
private final Instance spawningInstance; private final Instance spawningInstance;
private final Stadium stadium = new Stadium(); private final Game game;
public LoginHandler(Instance spawningInstance) { public LoginHandler(Instance spawningInstance) {
this.spawningInstance = spawningInstance; this.spawningInstance = spawningInstance;
this.game = new Game(new Stadium(spawningInstance, new Vec(-50, 64, -50), new Vec(100)));
} }
public void setup(GlobalEventHandler globalEventHandler) { public void setup(GlobalEventHandler globalEventHandler) {
globalEventHandler.addListener(AsyncPlayerConfigurationEvent.class, event -> onLogin(event)); globalEventHandler.addListener(AsyncPlayerConfigurationEvent.class, event -> onLogin(event));
globalEventHandler.addListener(PlayerDisconnectEvent.class, event -> onQuit(event)); globalEventHandler.addListener(PlayerDisconnectEvent.class, event -> onQuit(event));
globalEventHandler.addListener(PlayerSpawnEvent.class, event -> onSpawn(event)); globalEventHandler.addListener(PlayerSpawnEvent.class, event -> onSpawn(event));
new BallKicker(stadium).setup(globalEventHandler); new BallKicker(game.stadium).setup(globalEventHandler);
} }
private void onLogin(AsyncPlayerConfigurationEvent event) { private void onLogin(AsyncPlayerConfigurationEvent event) {
@ -36,11 +37,11 @@ public class LoginHandler {
event.setHardcore(true); event.setHardcore(true);
player.setRespawnPoint(new Pos(0, 67, 0)); player.setRespawnPoint(new Pos(0, 67, 0));
stadium.players.add(player); game.addPlayer(player);
} }
private void onQuit(PlayerDisconnectEvent event) { private void onQuit(PlayerDisconnectEvent event) {
stadium.players.remove(event.getPlayer()); game.removePlayer(event.getPlayer());
} }
private void onSpawn(PlayerSpawnEvent event) { private void onSpawn(PlayerSpawnEvent event) {
@ -50,11 +51,6 @@ public class LoginHandler {
player.getAttribute(Attribute.GENERIC_MOVEMENT_SPEED).setBaseValue(0.2); player.getAttribute(Attribute.GENERIC_MOVEMENT_SPEED).setBaseValue(0.2);
player.getAttribute(Attribute.GENERIC_JUMP_STRENGTH).setBaseValue(0.36813); // just enough to jump a block player.getAttribute(Attribute.GENERIC_JUMP_STRENGTH).setBaseValue(0.36813); // just enough to jump a block
if (stadium.ball == null) { game.startWarmup();
Ball ball = new Ball(stadium);
ball.setInstance(event.getInstance());
ball.teleport(new Pos(0, 90, 5));
stadium.ball = ball;
}
} }
} }

View file

@ -6,10 +6,8 @@ import net.minestom.server.event.GlobalEventHandler;
import net.minestom.server.instance.InstanceContainer; import net.minestom.server.instance.InstanceContainer;
import net.minestom.server.instance.InstanceManager; import net.minestom.server.instance.InstanceManager;
import net.minestom.server.instance.LightingChunk; import net.minestom.server.instance.LightingChunk;
import net.minestom.server.instance.Weather;
import net.minestom.server.registry.DynamicRegistry.Key; import net.minestom.server.registry.DynamicRegistry.Key;
import net.minestom.server.timer.SchedulerManager; import net.minestom.server.timer.SchedulerManager;
import net.minestom.server.timer.TaskSchedule;
import net.minestom.server.world.DimensionType; import net.minestom.server.world.DimensionType;
import net.pivipi.world.FancyDimension; import net.pivipi.world.FancyDimension;
import net.pivipi.world.SoccerGenerator; import net.pivipi.world.SoccerGenerator;
@ -23,6 +21,7 @@ public class Main {
InstanceManager instanceManager = MinecraftServer.getInstanceManager(); InstanceManager instanceManager = MinecraftServer.getInstanceManager();
GlobalEventHandler globalEventHandler = MinecraftServer.getGlobalEventHandler(); GlobalEventHandler globalEventHandler = MinecraftServer.getGlobalEventHandler();
SchedulerManager schedulerManager = MinecraftServer.getSchedulerManager();
Key<DimensionType> dimension = FancyDimension.create(); Key<DimensionType> dimension = FancyDimension.create();
InstanceContainer instanceContainer = instanceManager.createInstanceContainer(dimension); InstanceContainer instanceContainer = instanceManager.createInstanceContainer(dimension);
@ -36,14 +35,16 @@ public class Main {
WorldConstraints worldConstraints = new WorldConstraints(); WorldConstraints worldConstraints = new WorldConstraints();
worldConstraints.setup(globalEventHandler); worldConstraints.setup(globalEventHandler);
new Stats(instanceManager).start(schedulerManager);
/* done */ /* done */
MinecraftServer.setCompressionThreshold(0); MinecraftServer.setCompressionThreshold(0);
MinecraftServer.setBrandName("PiViPi"); MinecraftServer.setBrandName("PiViPi");
minecraftServer.start("0.0.0.0", 25565); minecraftServer.start("127.0.0.1", 25565);
System.out.println("started"); System.out.println("Listening on port 25565");
} }
} }

View file

@ -0,0 +1,43 @@
package net.pivipi;
import net.kyori.adventure.text.Component;
import net.minestom.server.instance.InstanceManager;
import net.minestom.server.timer.ExecutionType;
import net.minestom.server.timer.SchedulerManager;
import net.minestom.server.timer.TaskSchedule;
public class Stats implements Runnable {
private int tickDelay = 10;
private Runtime runtime = Runtime.getRuntime();
private InstanceManager instanceManager;
//private BenchmarkManager benchmarkManager;
private long lastRun;
public Stats(InstanceManager instanceManager) {
this.instanceManager = instanceManager;
}
public void start(SchedulerManager schedulerManager) {
//this.benchmarkManager = MinecraftServer.getBenchmarkManager();
schedulerManager.scheduleTask(this, TaskSchedule.immediate(), TaskSchedule.tick(tickDelay), ExecutionType.TICK_END);
}
@Override
public void run() {
long now = System.currentTimeMillis();
long delay = now - lastRun;
lastRun = now;
double mspt = (double)delay / tickDelay;
long memUsed = (runtime.totalMemory() - runtime.freeMemory()) / 1024 / 1024;
Component footer = Component.text("%.3f MSPT | %dMB".formatted(mspt, memUsed));
instanceManager.getInstances().forEach(instance -> { // TODO make this async
instance.getPlayers().forEach(player -> {
player.sendPlayerListFooter(footer);
});
});
}
}

View file

@ -1,18 +1,15 @@
package net.pivipi.ball; package net.pivipi.ball;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.coordinate.Vec; import net.minestom.server.coordinate.Vec;
import net.minestom.server.entity.Entity;
import net.minestom.server.entity.EntityType; import net.minestom.server.entity.EntityType;
import net.minestom.server.entity.Player; import net.minestom.server.entity.Player;
import net.minestom.server.entity.metadata.other.FallingBlockMeta; import net.minestom.server.entity.metadata.other.FallingBlockMeta;
import net.minestom.server.event.player.PlayerEntityInteractEvent;
import net.minestom.server.instance.Instance;
import net.minestom.server.instance.block.Block; import net.minestom.server.instance.block.Block;
import net.pivipi.entity.PhysicsEntity;
import net.pivipi.game.Stadium;
import net.pivipi.physics.Physics; import net.pivipi.physics.Physics;
import net.pivipi.world.Stadium;
public class Ball extends Entity { public class Ball extends PhysicsEntity {
private long lastTick; private long lastTick;
private Player holder; private Player holder;

View file

@ -1,27 +1,18 @@
package net.pivipi.ball; package net.pivipi.ball;
import javax.print.attribute.standard.MediaSize.Engineering;
import javax.swing.plaf.basic.BasicInternalFrameTitlePane.IconifyAction;
import net.minestom.server.coordinate.Point; import net.minestom.server.coordinate.Point;
import net.minestom.server.coordinate.Pos; import net.minestom.server.coordinate.Pos;
import net.minestom.server.coordinate.Vec;
import net.minestom.server.entity.Entity; import net.minestom.server.entity.Entity;
import net.minestom.server.entity.Player; import net.minestom.server.entity.Player;
import net.minestom.server.entity.Entity.Pose;
import net.minestom.server.event.GlobalEventHandler; import net.minestom.server.event.GlobalEventHandler;
import net.minestom.server.event.player.PlayerEntityInteractEvent; import net.minestom.server.event.player.PlayerEntityInteractEvent;
import net.minestom.server.event.player.PlayerHandAnimationEvent; import net.minestom.server.event.player.PlayerHandAnimationEvent;
import net.minestom.server.event.player.PlayerMoveEvent;
import net.minestom.server.event.player.PlayerStartSneakingEvent; import net.minestom.server.event.player.PlayerStartSneakingEvent;
import net.minestom.server.event.player.PlayerStopSneakingEvent; import net.minestom.server.event.player.PlayerStopSneakingEvent;
import net.minestom.server.instance.block.Block; import net.minestom.server.instance.block.Block;
import net.minestom.server.item.ItemStack; import net.pivipi.game.Stadium;
import net.minestom.server.network.packet.server.play.BlockActionPacket;
import net.minestom.server.particle.Particle.Item;
import net.pivipi.physics.Collision; import net.pivipi.physics.Collision;
import net.pivipi.physics.CollisionData; import net.pivipi.physics.CollisionData;
import net.pivipi.world.Stadium;
public class BallKicker { // TODO apply physics settings here public class BallKicker { // TODO apply physics settings here
public final Stadium stadium; public final Stadium stadium;
@ -86,8 +77,7 @@ public class BallKicker { // TODO apply physics settings here
private void onSwing(PlayerHandAnimationEvent event) { private void onSwing(PlayerHandAnimationEvent event) {
Player player = event.getPlayer(); Player player = event.getPlayer();
CollisionData collisionData = new CollisionData(); CollisionData collisionData = Collision.pushOutside(player, stadium.ball, false);
Collision.pushOutside(player, stadium.ball, collisionData);
double distance = collisionData.distance.withY(0).distance(0, 0, 0); double distance = collisionData.distance.withY(0).distance(0, 0, 0);
if (collisionData.distance.y() < verticalReach && distance < reach) { if (collisionData.distance.y() < verticalReach && distance < reach) {

View file

@ -0,0 +1,54 @@
package net.pivipi.entity;
import net.minestom.server.coordinate.Vec;
public class AABB {
Vec min, max;
Vec velocity;
public AABB(Vec min, Vec max, Vec velocity) {
this.min = min;
this.max = max;
this.velocity = velocity;
}
/**
* gets the position of the lower corner
* @return
*/
public Vec getMin() {
return min;
}
/**
* gets the position of the lower corner + velocity
* @return
*/
public Vec getMinSwept() {
return min.add(velocity);
}
/**
* gets the position of the upper corner
* @return
*/
public Vec getMax() {
return min;
}
/**
* gets the position of the upper corner + velocity
* @return
*/
public Vec getMaxSwept() {
return max.add(velocity);
}
/**
* gets the velocity that is blocks per second
* @return
*/
public Vec getVelocity() {
return velocity;
}
}

View file

@ -0,0 +1,21 @@
package net.pivipi.entity;
import org.jetbrains.annotations.NotNull;
import net.minestom.server.coordinate.Vec;
import net.minestom.server.entity.Entity;
import net.minestom.server.entity.EntityType;
public class PhysicsEntity extends Entity {
public PhysicsEntity(@NotNull EntityType entityType) {
super(entityType);
// TODO Auto-generated constructor stub
}
public AABB getAabb() {
Vec min = this.getPosition().sub(this.getBoundingBox().width() / 2, 0, this.getBoundingBox().depth() / 2).asVec();
Vec max = this.getPosition().add(this.getBoundingBox().width() / 2, this.getBoundingBox().height(), this.getBoundingBox().depth() / 2).asVec();
return new AABB(min, max, this.velocity);
}
}

View file

@ -0,0 +1,104 @@
package net.pivipi.game;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.Player;
import net.pivipi.ball.Ball;
public class Game {
public final Stadium stadium;
public boolean started; // true even if warmup
public boolean warmup;
public boolean paused; // is game paused
public long startedTime; // fine until year 292272993 but trust me it will pass in an Instant
public long lostTime; // how much ms the game was paused for
public long addedTime; // also called stoppage time, cosmetic since a person decides when to end the match
private long pauseStart;
public Game(Stadium stadium) {
this.stadium = stadium;
}
public boolean addPlayer(Player player) {
return stadium.players.add(player);
}
public boolean removePlayer(Player player) {
return stadium.players.remove(player);
}
/**
* starts warmup
* @return true if the game was not running
*/
public boolean startWarmup() {
if (!started && !warmup) return false;
this.started = true;
this.warmup = true;
this.startedTime = System.currentTimeMillis();
Ball ball = new Ball(stadium);
ball.setInstance(stadium.instance);
ball.teleport(new Pos(stadium.max.sub(stadium.size.div(2))));
stadium.ball = ball;
return true;
}
/**
* starts the game
* @return true if the game was not running or if there was warmup
*/
public boolean start() {
if (started && !warmup) return false;
this.started = true;
this.warmup = false;
this.startedTime = System.currentTimeMillis();
// TODO
return true;
}
/**
* ends the game
* @return true if the game was running
*/
public boolean end() {
if (!started) return false;
this.started = false;
this.warmup = false;
// TODO
return true;
}
public boolean pause() {
if (paused) return false;
pauseStart = System.currentTimeMillis();
paused = true;
return true;
}
public boolean unpause() {
if (!paused) return false;
lostTime += System.currentTimeMillis() - pauseStart;
paused = false;
return true;
}
/**
* suggests how much added time
* basically rounds down lost time to minutes
* @return
*/
public long suggestAddedTime() {
return (lostTime / 60000) * 60000;
}
}

View file

@ -0,0 +1,47 @@
package net.pivipi.game;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
public class PhysicsSettings {
public double gravity = 9.8;
public double blockBounciness = 0.5;
public double playerHeadBounciness = 0.5;
public double playerSneakingHeadBounciness = 0.2;
public double playerBodyBounciness = 0.2;
public double playerSneakingBodyBounciness = 0;
public double playerCarryStrength = 2;
public double headJumpStrength = 5;
public double headNodStep = 20;
public double jumpStrength = 20;
public double groundFriction = 0.5;
public double airFriction = 0.1;
public String setByString(String key, String valueStr) {
try {
Field field = this.getClass().getDeclaredField(key);
Type type = field.getAnnotatedType().getType();
Object value;
if (type.equals(double.class)) {
value = Double.valueOf(valueStr);
} else {
return "This property is not changeable because I can't yet handle type " + type.getTypeName();
}
field.setAccessible(true);
field.set(this, value);
return field.getName() + " is now " + value.toString();
} catch (NumberFormatException e) {
return "Not a decimal number: " + valueStr;
} catch (NoSuchFieldException e) {
return "Invalid key: " + key;
} catch (IllegalArgumentException e) {
return "Invalid value: " + valueStr;
} catch (IllegalAccessException e) {
return "This property is not changeable";
}
}
}

View file

@ -0,0 +1,42 @@
package net.pivipi.game;
import java.util.HashSet;
import java.util.Set;
import net.minestom.server.coordinate.Vec;
import net.minestom.server.entity.Player;
import net.minestom.server.instance.Instance;
import net.pivipi.ball.Ball;
public class Stadium {
public final Set<Player> players = new HashSet<Player>();
public Ball ball; // TODO public?
/**
* the instance where this stadium is
*/
public final Instance instance;
/**
* the lower corner pos
*/
public final Vec pos;
/**
* the stadium size
*/
public final Vec size;
/**
* the max corner pos that is pos + size
*/
public final Vec max;
public Stadium(Instance instance, Vec pos, Vec size) {
this.instance = instance;
this.pos = pos;
this.size = size;
this.max = pos.add(size);
}
}

View file

@ -6,32 +6,53 @@ import net.minestom.server.collision.VisibleSweepResult;
import net.minestom.server.coordinate.Pos; import net.minestom.server.coordinate.Pos;
import net.minestom.server.coordinate.Vec; import net.minestom.server.coordinate.Vec;
import net.minestom.server.entity.Entity; import net.minestom.server.entity.Entity;
import net.minestom.server.entity.Player;
public class Collision { public class Collision {
/** /**
* *
* @param entity * @param pos static object's pos
* @param boundingBox static object's bounding box
* @param moving moving entity
* @param movement moving entity movement
* @return
*/
private static CollisionData willCollide(Pos pos, BoundingBox boundingBox, Entity moving, Vec movement) {
SweepResult sweepResult = new SweepResult(1, 0, 0, 0, null, 0, 0, 0);
boolean collides = boundingBox.intersectBoxSwept(moving.getPosition(), movement, pos, moving.getBoundingBox(), sweepResult);
VisibleSweepResult visibleSweepResult = new VisibleSweepResult(sweepResult);
CollisionData collisionData = new CollisionData();
collisionData.swept = true;
collisionData.percentage = visibleSweepResult.res;
collisionData.collidedPosition = new Vec(visibleSweepResult.collidedPositionX, visibleSweepResult.collidedPositionY, visibleSweepResult.collidedPositionZ);
collisionData.normal = new Vec(visibleSweepResult.normalX, visibleSweepResult.normalY, visibleSweepResult.normalZ);
return collides ? collisionData : null;
}
/**
*
* @param entity the static entity
* @param moving the moving entity * @param moving the moving entity
* @param velocity the applied velocity * @param movement the movement
* @return null if no collision * @return null if no collision
*/ */
public static VisibleSweepResult willCollideWithEntity(Entity entity, Entity moving, Vec movement) { public static CollisionData willCollideWithEntity(Entity entity, Entity moving, Vec movement) {
SweepResult sweepResult = new SweepResult(1, 0, 0, 0, null, 0, 0, 0); return willCollide(entity.getPosition(), entity.getBoundingBox(), moving, movement);
boolean collided = entity.getBoundingBox().intersectBoxSwept(moving.getPosition(), movement, entity.getPosition(), moving.getBoundingBox(), sweepResult);
return collided ? new VisibleSweepResult(sweepResult) : null;
} }
/** /**
* *
* @param groundY ground Y * @param groundY ground Y
* @param moving the moving entity * @param moving the moving entity
* @param movement the movement
* @return null if no collision * @return null if no collision
*/ */
public static VisibleSweepResult willCollideWithGround(double groundY, Entity moving, Vec movement) { public static CollisionData willCollideWithGround(double groundY, Entity moving, Vec movement) {
SweepResult sweepResult = new SweepResult(1, 0, 0, 0, null, 0, 0, 0); return willCollide(new Pos(Double.MIN_VALUE, 0, Double.MIN_VALUE), new BoundingBox(Double.MAX_VALUE, 64, Double.MAX_VALUE), moving, movement);
boolean collided = new BoundingBox(10, 1, 10).intersectBoxSwept(moving.getPosition(), movement, moving.getPosition().withY(groundY - 1).sub(5, 0, 5), moving.getBoundingBox(), sweepResult); }
return collided ? new VisibleSweepResult(sweepResult) : null;
public static CollisionData willCollideWithBlock(Pos blockPos, Entity moving, Vec movement) {
return willCollide(blockPos, new BoundingBox(1, 1, 1), moving, movement);
} }
public static boolean collidesWithEntity(Entity entity1, Entity entity2) { public static boolean collidesWithEntity(Entity entity1, Entity entity2) {
@ -42,32 +63,40 @@ public class Collision {
return new BoundingBox(10, 1, 10).intersectEntity(entity.getPosition().withY(groundY - 1).sub(5, 0, 5), entity); return new BoundingBox(10, 1, 10).intersectEntity(entity.getPosition().withY(groundY - 1).sub(5, 0, 5), entity);
} }
/** public static boolean collidesWithBlock(Pos blockPos, Entity entity) {
* return new BoundingBox(1, 1, 1).intersectEntity(blockPos, entity);
* @param pusher the entity that's pushing like a player
* @param pushed the entity that's being pushed like a ball
* @return offset you must apply to push the entity outside, null if doesn't collide
*/
public static Vec pushOutside(Entity pusher, Entity pushed) {
return pushOutside(pusher, pushed, null);
} }
/** /**
* *
* @param pusher the entity that's pushing like a player * @param pusher the entity that's pushing like a player
* @param pushed the entity that's being pushed like a ball * @param pushed the entity that's being pushed like a ball
* @param collisionData puts data in this object
* @return offset you must apply to push the entity outside, null if doesn't collide * @return offset you must apply to push the entity outside, null if doesn't collide
*/ */
public static Vec pushOutside(Entity pusher, Entity pushed, CollisionData collisionData) { public static CollisionData pushOutside(Entity pusher, Entity pushed, boolean y) {
return pushOutside(pusher.getBoundingBox(), pusher.getPosition(), pushed, y);
}
/**
*
* @param pusher the entity that's pushing like a player
* @param pushed the entity that's being pushed like a ball
* @return offset you must apply to push the entity outside, null if doesn't collide
*/
public static CollisionData pushOutside(Pos blockPos, Entity pushed, boolean y) {
return pushOutside(new BoundingBox(1, 1, 1), blockPos, pushed, y);
}
/*public static Vec pushOutside(BoundingBox pusherBox, Pos pusherPos, Entity pushed, CollisionData collisionData) {
double ballXSize = pushed.getBoundingBox().width(); double ballXSize = pushed.getBoundingBox().width();
double playerXSize = pusher.getBoundingBox().width(); double playerXSize = pusherBox.width();
double ballZSize = pushed.getBoundingBox().depth(); double ballZSize = pushed.getBoundingBox().depth();
double playerZSize = pusher.getBoundingBox().depth(); double playerZSize = pusherBox.depth();
double ballHeight = pushed.getBoundingBox().height(); double ballHeight = pushed.getBoundingBox().height();
double playerHeight = pusher.getBoundingBox().height(); double playerHeight = pusherBox.height();
Pos ballCenterPos = pushed.getPosition(); Pos ballCenterPos = pushed.getPosition();
Pos playerCenterPos = pusher.getPosition(); Pos playerCenterPos = pusherPos;
Pos ballPos = ballCenterPos.sub(ballXSize / 2, 0, ballZSize / 2); // corner Pos ballPos = ballCenterPos.sub(ballXSize / 2, 0, ballZSize / 2); // corner
Pos playerPos = playerCenterPos.sub(playerXSize / 2, 0, playerZSize / 2); Pos playerPos = playerCenterPos.sub(playerXSize / 2, 0, playerZSize / 2);
@ -107,5 +136,62 @@ public class Collision {
return diffX < diffZ ? newBallOffset.withX(0) : newBallOffset.withZ(0); return diffX < diffZ ? newBallOffset.withX(0) : newBallOffset.withZ(0);
return null; return null;
}*/
/**
*
* @param pusherBox the entity that's pushing like a player or a block, its bounding box
* @param pusherPos the position, bottom center
* @param pushed the entity that's being pushed like a ball
* @return collision data
*/
private static CollisionData pushOutside(BoundingBox pusherBox, Pos pusherPos, Entity pushed, boolean y) {
double ballXSize = pushed.getBoundingBox().width();
double playerXSize = pusherBox.width();
double ballZSize = pushed.getBoundingBox().depth();
double playerZSize = pusherBox.depth();
double ballHeight = pushed.getBoundingBox().height();
double playerHeight = pusherBox.height();
Pos ballCenterPos = pushed.getPosition();
Pos playerCenterPos = pusherPos;
Pos ballPos = ballCenterPos.sub(ballXSize / 2, 0, ballZSize / 2);
Pos playerPos = playerCenterPos.sub(playerXSize / 2, 0, playerZSize / 2);
Vec ballDistance = ballCenterPos.sub(playerCenterPos).asVec();
CollisionData collisionData = new CollisionData();
collisionData.distance = ballDistance;
double diffX = ballDistance.x() > 0
? ballPos.x() - (playerPos.x() + playerXSize)
: playerPos.x() - (ballPos.x() + ballXSize);
double diffY = ballDistance.y() > 0
? ballPos.y() - (playerPos.y() + playerHeight)
: playerPos.y() - (ballPos.y() + ballHeight);
double diffZ = ballDistance.z() > 0
? ballPos.z() - (playerPos.z() + playerZSize)
: playerPos.z() - (ballPos.z() + ballZSize);
Vec newBallOffset = new Vec(0);
Vec offset = null;
if (diffX < 0 && diffZ < 0 && diffY < 0) {
newBallOffset = newBallOffset.withX(ballDistance.x() > 0 ? -diffX : diffX);
newBallOffset = newBallOffset.withY(ballDistance.x() > 0 ? -diffY : diffY);
newBallOffset = newBallOffset.withZ(ballDistance.z() > 0 ? -diffZ : diffZ);
if (!y) diffY = Double.POSITIVE_INFINITY;
if (diffX < diffZ && diffX < diffY)
offset = newBallOffset.mul(0, 0, 1);
else if (diffY < diffX && diffY < diffZ)
offset = newBallOffset.mul(0, 1, 0);
else if (diffZ < diffX && diffZ < diffY)
offset = newBallOffset.mul(1, 0, 0);
}
collisionData.offset = offset;
return collisionData;
} }
} }

View file

@ -1,10 +1,40 @@
package net.pivipi.physics; package net.pivipi.physics;
import net.minestom.server.coordinate.Vec; import net.minestom.server.coordinate.Vec;
import net.minestom.server.entity.Entity;
public class CollisionData { public class CollisionData {
public boolean swept;
/** /**
* distance between centers of entities (but bottom y) * distance between centers of entities (but bottom y)
* only when not swept
*/ */
public Vec distance; public Vec distance;
/**
* the offset to the entity to to resolve the collision
* only when not swept
*/
public Vec offset;
/**
* percentage of movement until collision (only when swept)
*/
public double percentage;
/**
* the position where the collision takes place (only when swept)
*/
public Vec collidedPosition;
/**
* the direction and speed where an object should bounce (only when swept)
*/
public Vec normal;
/**
* used to track what entity collided
*/
public Entity entity;
} }

View file

@ -1,41 +1,33 @@
package net.pivipi.physics; package net.pivipi.physics;
import java.util.ArrayList;
import java.util.Set; import java.util.Set;
import net.minestom.server.collision.VisibleSweepResult;
import net.minestom.server.coordinate.Pos; import net.minestom.server.coordinate.Pos;
import net.minestom.server.coordinate.Vec; import net.minestom.server.coordinate.Vec;
import net.minestom.server.entity.Entity;
import net.minestom.server.entity.Player; import net.minestom.server.entity.Player;
import net.minestom.server.network.packet.server.play.ParticlePacket; import net.minestom.server.network.packet.server.play.ParticlePacket;
import net.minestom.server.particle.Particle; import net.minestom.server.particle.Particle;
import net.pivipi.entity.AABB;
import net.pivipi.entity.PhysicsEntity;
import net.pivipi.game.PhysicsSettings;
public class Physics { public class Physics {
private final double gravity = 9.8; private PhysicsSettings settings;
private final double blockBounciness = 0.5;
private final double playerHeadBounciness = 0.5;
private final double playerSneakingHeadBounciness = 0.2;
private final double playerBodyBounciness = 0.2;
private final double playerSneakingBodyBounciness = 0;
private final double playerCarryStrength = 2;
private final double headJumpStrength = 5;
private final double headNodStep = 20;
private final double jumpStrength = 20;
private final double groundFriction = 0.5;
private final double airFriction = 0.1;
private final int groundY = 64;
private final double flame = 20; // flame starts at speed private final double flame = 20; // flame starts at speed
private final double flameFreq = 100; // ms per flame private final double flameFreq = 100; // ms per flame
private final double flameIncrement = 0.5; // ms per flame decrement per speed unit private final double flameIncrement = 0.5; // ms per flame decrement per speed unit
private final float flameSpread = 0.2f; private final float flameSpread = 0.2f;
private final int groundY = 64; // this should be removed when block collisions
private Vec velocity = new Vec(0, 0, 0); private Vec velocity = new Vec(0, 0, 0);
private final Entity entity; private final PhysicsEntity entity;
private int p; private int p;
public Physics(Entity entity) { public Physics(PhysicsEntity entity) {
this.entity = entity; this.entity = entity;
} }
@ -59,62 +51,84 @@ public class Physics {
} }
private Vec applyPhysics(double delta, Set<Player> players) { private Vec applyPhysics(double delta, Set<Player> players) {
velocity = velocity.sub(0, gravity * delta, 0).mul(1 - (airFriction * delta), 1, 1 - (airFriction * delta)); velocity = velocity.sub(0, settings.gravity * delta, 0).mul(1 - (settings.airFriction * delta), 1, 1 - (settings.airFriction * delta));
ArrayList<CollisionData> collisions = new ArrayList<>();
if (Collision.collidesWithGround(groundY, entity)) { if (Collision.collidesWithGround(groundY, entity)) {
entity.teleport(entity.getPosition().withY(groundY)); entity.teleport(entity.getPosition().withY(groundY));
velocity = velocity.mul(1 - (groundFriction * delta), -blockBounciness, 1 - (groundFriction * delta)); velocity = velocity.mul(1 - (settings.groundFriction * delta), -settings.blockBounciness, 1 - (settings.groundFriction * delta));
} else { } else {
for (Player player : players) { for (Player player : players) {
boolean collides = Collision.collidesWithEntity(player, entity); CollisionData collisionData = Collision.pushOutside(player, entity, false); // TODO predict the collision
if (collides) {
handlePlayerTouch(player); if (collisionData.offset != null) {
collisionData.entity = player;
break; collisions.add(collisionData);
} }
} }
AABB aabb = entity.getAabb();
int minX = aabb.getMin().blockX();
int minY = aabb.getMin().blockY();
int minZ = aabb.getMin().blockZ();
int maxX = aabb.getMax().blockX();
int maxY = aabb.getMax().blockY();
int maxZ = aabb.getMax().blockZ();
for (int x=minX; x<=maxX; x++) {
for (int y=minY; y<=maxY; y++) {
for (int z=minZ; z<=maxZ; z++) {
if (entity.getInstance().getBlock(x, y, z).isSolid()) {
CollisionData cd = Collision.pushOutside(new Pos(x, y, z), entity, true);
collisions.add(cd);
}
}
}
}
}
for (CollisionData collisionData : collisions) {
if (collisionData.entity instanceof Player) {
handlePlayerTouch((Player) collisionData.entity, collisionData);
} else {
handleBlockTouch(collisionData);
}
} }
return velocity; return velocity;
} }
private void handlePlayerTouch(Player player) { private void handleBlockTouch(CollisionData collisionData) {
CollisionData collisionData = new CollisionData();
Vec offset = Collision.pushOutside(player, entity, collisionData); // TODO predict the collision
if (offset != null) { }
Pos movementPos = player.getPosition().sub(player.getPreviousPosition());
private void handlePlayerTouch(Player player, CollisionData collisionData) {
Vec offset = collisionData.offset;
Pos movementPos = player.getPosition().sub(player.getPreviousPosition());
boolean oppositeX = (collisionData.distance.x() > 0 ? velocity.x() < 0 : velocity.x() > 0); boolean oppositeX = (collisionData.distance.x() > 0 ? velocity.x() < 0 : velocity.x() > 0);
boolean oppositeZ = (collisionData.distance.z() > 0 ? velocity.z() < 0 : velocity.z() > 0); boolean oppositeZ = (collisionData.distance.z() > 0 ? velocity.z() < 0 : velocity.z() > 0);
double headOffsetY = player.getBoundingBox().height() - collisionData.distance.y(); double headOffsetY = player.getBoundingBox().height() - collisionData.distance.y();
System.out.println(offset); System.out.println(offset);
System.out.println(oppositeX); System.out.println(oppositeX);
System.out.println(velocity.x()); System.out.println(velocity.x());
if (headOffsetY < 0.6) { // landed on head, magic value head height if (headOffsetY < 0.6) { // landed on head, magic value head height
if (movementPos.y() > 0) { if (movementPos.y() > 0) {
double headPitchDiff = Math.abs(movementPos.pitch()); double headPitchDiff = Math.abs(movementPos.pitch());
System.out.println(headPitchDiff); System.out.println(headPitchDiff);
addVelocity(player.getPosition().direction().mul(headPitchDiff / headNodStep).withY(headJumpStrength)); addVelocity(player.getPosition().direction().mul(headPitchDiff / settings.headNodStep).withY(settings.headJumpStrength));
} else velocity = velocity.mul(1, player.isSneaking() ? -playerSneakingHeadBounciness : -playerHeadBounciness, 1); } else velocity = velocity.mul(1, player.isSneaking() ? -settings.playerSneakingHeadBounciness : -settings.playerHeadBounciness, 1);
entity.teleport(entity.getPosition().withY(y -> y + headOffsetY)); entity.teleport(entity.getPosition().withY(y -> y + headOffsetY));
} else { } else {
System.out.println("tping"); entity.teleport(entity.getPosition().add(offset.mul(1.1))); // magic value to make some space in case of desync
entity.teleport(entity.getPosition().add(offset.mul(1.1))); // magic value to make some space in case of desync double bodyBounciness = player.isSneaking() ? -settings.playerSneakingBodyBounciness : -settings.playerBodyBounciness;
double bodyBounciness = player.isSneaking() ? -playerSneakingBodyBounciness : -playerBodyBounciness;
System.out.println(velocity);
velocity = velocity
.withX(x -> oppositeX ? x * bodyBounciness : x + movementPos.x() * playerCarryStrength)
.withY(y -> collisionData.distance.y() < 1.0 ? movementPos.y() * jumpStrength : y) // magic value legs
.withZ(z -> oppositeZ ? z * bodyBounciness : z + movementPos.z() * playerCarryStrength);
System.out.println(velocity);
}
velocity = velocity
.withX(x -> oppositeX ? x * bodyBounciness : x + movementPos.x() * settings.playerCarryStrength)
.withY(y -> collisionData.distance.y() < 1.0 ? movementPos.y() * settings.jumpStrength : y) // magic value legs
.withZ(z -> oppositeZ ? z * bodyBounciness : z + movementPos.z() * settings.playerCarryStrength);
} }
} }

View file

@ -14,7 +14,7 @@ public class SoccerGenerator implements Generator {
public void generate(@NotNull GenerationUnit unit) { public void generate(@NotNull GenerationUnit unit) {
UnitModifier modifier = unit.modifier(); UnitModifier modifier = unit.modifier();
modifier.fillBiome(Biome.CRIMSON_FOREST); modifier.fillBiome(Biome.PLAINS);
modifier.fillHeight(63, 64, Block.WHITE_WOOL); modifier.fillHeight(63, 64, Block.WHITE_WOOL);
modifier.fill(unit.absoluteStart().withY(63).add(1, 0, 1), unit.absoluteEnd().withY(64), Block.GREEN_CONCRETE_POWDER); modifier.fill(unit.absoluteStart().withY(63).add(1, 0, 1), unit.absoluteEnd().withY(64), Block.GREEN_CONCRETE_POWDER);

View file

@ -1,12 +0,0 @@
package net.pivipi.world;
import java.util.HashSet;
import java.util.Set;
import net.minestom.server.entity.Player;
import net.pivipi.ball.Ball;
public class Stadium {
public final Set<Player> players = new HashSet<Player>();
public Ball ball;
}