Added creative movement.
This commit is contained in:
parent
1465da7506
commit
53b8718a06
|
@ -2,6 +2,7 @@ package nl.andrewl.aos_core.model;
|
||||||
|
|
||||||
import nl.andrewl.aos_core.Directions;
|
import nl.andrewl.aos_core.Directions;
|
||||||
import nl.andrewl.aos_core.MathUtils;
|
import nl.andrewl.aos_core.MathUtils;
|
||||||
|
import nl.andrewl.aos_core.model.world.World;
|
||||||
import org.joml.*;
|
import org.joml.*;
|
||||||
import org.joml.Math;
|
import org.joml.Math;
|
||||||
|
|
||||||
|
@ -194,6 +195,36 @@ public class Player {
|
||||||
.translate(-0.35f, getEyeHeight() - 0.4f, 0.35f);
|
.translate(-0.35f, getEyeHeight() - 0.4f, 0.35f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the list of all spaces occupied by a player's position, in the
|
||||||
|
* horizontal XZ plane. This can be between 1 and 4 spaces, depending on
|
||||||
|
* if the player's position is overlapping with a few blocks.
|
||||||
|
* @param pos The position.
|
||||||
|
* @return The list of 2d positions occupied.
|
||||||
|
*/
|
||||||
|
private List<Vector2i> getHorizontalSpaceOccupied(Vector3f pos) {
|
||||||
|
// Get the list of 2d x,z coordinates that we overlap with.
|
||||||
|
List<Vector2i> points = new ArrayList<>(4); // Due to the size of radius, there can only be a max of 4 blocks.
|
||||||
|
int minX = (int) Math.floor(pos.x - RADIUS);
|
||||||
|
int minZ = (int) Math.floor(pos.z - RADIUS);
|
||||||
|
int maxX = (int) Math.floor(pos.x + RADIUS);
|
||||||
|
int maxZ = (int) Math.floor(pos.z + RADIUS);
|
||||||
|
for (int x = minX; x <= maxX; x++) {
|
||||||
|
for (int z = minZ; z <= maxZ; z++) {
|
||||||
|
points.add(new Vector2i(x, z));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return points;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isGrounded(World world) {
|
||||||
|
// Player must be flat on the top of a block.
|
||||||
|
if (Math.floor(position.y) != position.y) return false;
|
||||||
|
// Check to see if there's a block under any of the spaces the player is over.
|
||||||
|
return getHorizontalSpaceOccupied(position).stream()
|
||||||
|
.anyMatch(point -> world.getBlockAt(point.x, position.y - 0.1f, point.y) != 0);
|
||||||
|
}
|
||||||
|
|
||||||
public List<Vector3i> getBlockSpaceOccupied() {
|
public List<Vector3i> getBlockSpaceOccupied() {
|
||||||
float playerBodyMinZ = position.z - RADIUS;
|
float playerBodyMinZ = position.z - RADIUS;
|
||||||
float playerBodyMaxZ = position.z + RADIUS;
|
float playerBodyMaxZ = position.z + RADIUS;
|
||||||
|
|
|
@ -47,7 +47,7 @@ public class PlayerManager {
|
||||||
joinMessage = username + " joined the game.";
|
joinMessage = username + " joined the game.";
|
||||||
}
|
}
|
||||||
player.setPosition(getBestSpawnPoint(player));
|
player.setPosition(getBestSpawnPoint(player));
|
||||||
player.setMode(PlayerMode.NORMAL);
|
player.setMode(PlayerMode.CREATIVE);
|
||||||
// Tell all other players that this one has joined.
|
// Tell all other players that this one has joined.
|
||||||
broadcastTcpMessageToAllBut(new PlayerJoinMessage(
|
broadcastTcpMessageToAllBut(new PlayerJoinMessage(
|
||||||
player.getId(), player.getUsername(), player.getTeam() == null ? -1 : player.getTeam().getId(),
|
player.getId(), player.getUsername(), player.getTeam() == null ? -1 : player.getTeam().getId(),
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
package nl.andrewl.aos2_server.logic;
|
||||||
|
|
||||||
|
import nl.andrewl.aos2_server.Server;
|
||||||
|
import nl.andrewl.aos2_server.config.ServerConfig;
|
||||||
|
import nl.andrewl.aos2_server.model.ServerPlayer;
|
||||||
|
import nl.andrewl.aos_core.model.world.World;
|
||||||
|
import org.joml.Vector3f;
|
||||||
|
|
||||||
|
public class CreativeMovementController implements PlayerMovementController {
|
||||||
|
@Override
|
||||||
|
public boolean tickMovement(float dt, ServerPlayer player, PlayerInputTracker input, Server server, World world, ServerConfig.PhysicsConfig config) {
|
||||||
|
boolean updated = tickVelocity(player, input, config);
|
||||||
|
if (player.getVelocity().lengthSquared() > 0) {
|
||||||
|
Vector3f movement = new Vector3f(player.getVelocity()).mul(dt);
|
||||||
|
player.getPosition().add(movement);
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
return updated;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean tickVelocity(ServerPlayer player, PlayerInputTracker input, ServerConfig.PhysicsConfig config) {
|
||||||
|
boolean updated = false;
|
||||||
|
var velocity = player.getVelocity();
|
||||||
|
var orientation = player.getOrientation();
|
||||||
|
Vector3f acceleration = new Vector3f(0);
|
||||||
|
if (input.forward()) acceleration.z -= 1;
|
||||||
|
if (input.backward()) acceleration.z += 1;
|
||||||
|
if (input.left()) acceleration.x -= 1;
|
||||||
|
if (input.right()) acceleration.x += 1;
|
||||||
|
if (input.jumping()) acceleration.y += 1;
|
||||||
|
if (input.crouching()) acceleration.y -= 1;
|
||||||
|
if (acceleration.lengthSquared() > 0) {
|
||||||
|
acceleration.normalize()
|
||||||
|
.rotateY(orientation.x)
|
||||||
|
.mul(config.movementAcceleration);
|
||||||
|
velocity.add(acceleration);
|
||||||
|
|
||||||
|
float maxSpeed = config.walkingSpeed;
|
||||||
|
if (input.sprinting()) {
|
||||||
|
maxSpeed = config.sprintingSpeed;
|
||||||
|
}
|
||||||
|
maxSpeed *= 4;
|
||||||
|
if (velocity.length() > maxSpeed) {
|
||||||
|
velocity.normalize(maxSpeed);
|
||||||
|
}
|
||||||
|
updated = true;
|
||||||
|
} else if (velocity.lengthSquared() > 0) {
|
||||||
|
float decel = Math.min(velocity.length(), config.movementDeceleration);
|
||||||
|
Vector3f deceleration = new Vector3f(velocity).negate().normalize().mul(decel);
|
||||||
|
velocity.add(deceleration);
|
||||||
|
if (Math.abs(velocity.x) < 0.1f) velocity.x = 0;
|
||||||
|
if (Math.abs(velocity.y) < 0.1f) velocity.y = 0;
|
||||||
|
if (Math.abs(velocity.z) < 0.1f) velocity.z = 0;
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
return updated;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,257 @@
|
||||||
|
package nl.andrewl.aos2_server.logic;
|
||||||
|
|
||||||
|
import nl.andrewl.aos2_server.Server;
|
||||||
|
import nl.andrewl.aos2_server.config.ServerConfig;
|
||||||
|
import nl.andrewl.aos2_server.model.ServerPlayer;
|
||||||
|
import nl.andrewl.aos_core.model.item.GunItemStack;
|
||||||
|
import nl.andrewl.aos_core.model.world.World;
|
||||||
|
import nl.andrewl.aos_core.net.client.ClientHealthMessage;
|
||||||
|
import nl.andrewl.aos_core.net.client.SoundMessage;
|
||||||
|
import org.joml.Math;
|
||||||
|
import org.joml.Vector3f;
|
||||||
|
|
||||||
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
|
||||||
|
import static nl.andrewl.aos_core.model.Player.RADIUS;
|
||||||
|
|
||||||
|
public class NormalMovementController implements PlayerMovementController {
|
||||||
|
@Override
|
||||||
|
public boolean tickMovement(float dt, ServerPlayer player, PlayerInputTracker input, Server server, World world, ServerConfig.PhysicsConfig config) {
|
||||||
|
boolean updated;
|
||||||
|
var velocity = player.getVelocity();
|
||||||
|
var position = player.getPosition();
|
||||||
|
boolean grounded = player.isGrounded(world);
|
||||||
|
updated = tickHorizontalVelocity(player, input, config, grounded);
|
||||||
|
|
||||||
|
if (grounded) {
|
||||||
|
if (input.jumping()) {
|
||||||
|
velocity.y = config.jumpVerticalSpeed * (input.sprinting() ? 1.25f : 1f);
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
velocity.y -= config.gravity * dt * 2; // Apply double-gravity to players to make the game feel faster.
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply updated velocity to the player.
|
||||||
|
if (velocity.lengthSquared() > 0) {
|
||||||
|
Vector3f movement = new Vector3f(velocity).mul(dt);
|
||||||
|
// Check for collisions if we try to move according to what the player wants.
|
||||||
|
checkBlockCollisions(player, movement, server, world);
|
||||||
|
position.add(movement);
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, check to see if the player is outside the world, and kill them if so.
|
||||||
|
if (
|
||||||
|
player.getPosition().x < world.getMinX() - 5 || player.getPosition().x > world.getMaxX() + 6 ||
|
||||||
|
player.getPosition().z < world.getMinZ() - 5 || player.getPosition().z > world.getMaxZ() + 6 ||
|
||||||
|
player.getPosition().y < world.getMinY() - 50 || player.getPosition().y > world.getMaxY() + 500
|
||||||
|
) {
|
||||||
|
server.getPlayerManager().playerKilled(player, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return updated;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean tickHorizontalVelocity(ServerPlayer player, PlayerInputTracker input, ServerConfig.PhysicsConfig config, boolean grounded) {
|
||||||
|
boolean updated = false;
|
||||||
|
var velocity = player.getVelocity();
|
||||||
|
var orientation = player.getOrientation();
|
||||||
|
Vector3f horizontalVelocity = new Vector3f(
|
||||||
|
velocity.x == velocity.x ? velocity.x : 0f,
|
||||||
|
0,
|
||||||
|
velocity.z == velocity.z ? velocity.z : 0f
|
||||||
|
);
|
||||||
|
Vector3f acceleration = new Vector3f(0);
|
||||||
|
if (input.forward()) acceleration.z -= 1;
|
||||||
|
if (input.backward()) acceleration.z += 1;
|
||||||
|
if (input.left()) acceleration.x -= 1;
|
||||||
|
if (input.right()) acceleration.x += 1;
|
||||||
|
if (acceleration.lengthSquared() > 0) {
|
||||||
|
acceleration.normalize();
|
||||||
|
acceleration.rotateAxis(orientation.x, 0, 1, 0);
|
||||||
|
float accelerationMagnitude = config.movementAcceleration;
|
||||||
|
if (!grounded) accelerationMagnitude *= 0.25f;
|
||||||
|
acceleration.mul(accelerationMagnitude);
|
||||||
|
horizontalVelocity.add(acceleration);
|
||||||
|
float maxSpeed;
|
||||||
|
if (input.crouching()) {
|
||||||
|
maxSpeed = config.crouchingSpeed;
|
||||||
|
} else if (input.sprinting()) {
|
||||||
|
maxSpeed = config.sprintingSpeed;
|
||||||
|
} else {
|
||||||
|
maxSpeed = config.walkingSpeed;
|
||||||
|
}
|
||||||
|
// If scoping, then force limit to crouching speed.
|
||||||
|
if (input.interacting() && player.getInventory().getSelectedItemStack() instanceof GunItemStack) {
|
||||||
|
maxSpeed = config.crouchingSpeed;
|
||||||
|
}
|
||||||
|
if (horizontalVelocity.length() > maxSpeed) {
|
||||||
|
horizontalVelocity.normalize(maxSpeed);
|
||||||
|
}
|
||||||
|
updated = true;
|
||||||
|
} else if (horizontalVelocity.lengthSquared() > 0) {
|
||||||
|
float baseDecel = config.movementDeceleration;
|
||||||
|
if (!grounded) baseDecel *= 0.25f;
|
||||||
|
float decelerationMagnitude = Math.min(horizontalVelocity.length(), baseDecel);
|
||||||
|
Vector3f deceleration = new Vector3f(horizontalVelocity).negate().normalize().mul(decelerationMagnitude);
|
||||||
|
horizontalVelocity.add(deceleration);
|
||||||
|
if (horizontalVelocity.length() < 0.1f) {
|
||||||
|
horizontalVelocity.set(0);
|
||||||
|
}
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the player's velocity with what we've computed.
|
||||||
|
velocity.x = horizontalVelocity.x;
|
||||||
|
velocity.z = horizontalVelocity.z;
|
||||||
|
return updated;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkBlockCollisions(ServerPlayer player, Vector3f movement, Server server, World world) {
|
||||||
|
var position = player.getPosition();
|
||||||
|
var velocity = player.getVelocity();
|
||||||
|
final Vector3f nextTickPosition = new Vector3f(position).add(movement);
|
||||||
|
float height = player.getCurrentHeight();
|
||||||
|
float delta = 0.00001f;
|
||||||
|
final Vector3f stepSize = new Vector3f(movement).normalize(1.0f);
|
||||||
|
// The number of steps we'll make towards the next tick position.
|
||||||
|
int stepCount = (int) Math.ceil(movement.length());
|
||||||
|
if (stepCount == 0) return; // No movement, so exit.
|
||||||
|
final Vector3f nextPos = new Vector3f(position);
|
||||||
|
final Vector3f lastPos = new Vector3f(position);
|
||||||
|
for (int i = 0; i < stepCount; i++) {
|
||||||
|
lastPos.set(nextPos);
|
||||||
|
nextPos.add(stepSize);
|
||||||
|
// If we shot past the next tick position, clamp it to that.
|
||||||
|
if (new Vector3f(nextPos).sub(position).length() > movement.length()) {
|
||||||
|
nextPos.set(nextTickPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we collide with anything at this new position.
|
||||||
|
|
||||||
|
float playerBodyPrevMinZ = lastPos.z - RADIUS;
|
||||||
|
float playerBodyPrevMaxZ = lastPos.z + RADIUS;
|
||||||
|
float playerBodyPrevMinX = lastPos.x - RADIUS;
|
||||||
|
float playerBodyPrevMaxX = lastPos.x + RADIUS;
|
||||||
|
float playerBodyPrevMinY = lastPos.y;
|
||||||
|
float playerBodyPrevMaxY = lastPos.y + height;
|
||||||
|
|
||||||
|
float playerBodyMinZ = nextPos.z - RADIUS;
|
||||||
|
float playerBodyMaxZ = nextPos.z + RADIUS;
|
||||||
|
float playerBodyMinX = nextPos.x - RADIUS;
|
||||||
|
float playerBodyMaxX = nextPos.x + RADIUS;
|
||||||
|
float playerBodyMinY = nextPos.y;
|
||||||
|
float playerBodyMaxY = nextPos.y + height;
|
||||||
|
|
||||||
|
// Compute the bounds of all blocks the player is intersecting with.
|
||||||
|
int minX = (int) Math.floor(playerBodyMinX);
|
||||||
|
int minZ = (int) Math.floor(playerBodyMinZ);
|
||||||
|
int minY = (int) Math.floor(playerBodyMinY);
|
||||||
|
int maxX = (int) Math.floor(playerBodyMaxX);
|
||||||
|
int maxZ = (int) Math.floor(playerBodyMaxZ);
|
||||||
|
int maxY = (int) Math.floor(playerBodyMaxY);
|
||||||
|
|
||||||
|
for (int x = minX; x <= maxX; x++) {
|
||||||
|
for (int z = minZ; z <= maxZ; z++) {
|
||||||
|
for (int y = minY; y <= maxY; y++) {
|
||||||
|
byte block = world.getBlockAt(x, y, z);
|
||||||
|
if (block <= 0) continue; // We're not colliding with this block.
|
||||||
|
float blockMinY = (float) y;
|
||||||
|
float blockMaxY = (float) y + 1;
|
||||||
|
float blockMinX = (float) x;
|
||||||
|
float blockMaxX = (float) x + 1;
|
||||||
|
float blockMinZ = (float) z;
|
||||||
|
float blockMaxZ = (float) z + 1;
|
||||||
|
|
||||||
|
/*
|
||||||
|
To determine if the player is moving into the -Z side of a block:
|
||||||
|
- The player's max z position went from < blockMinZ to >= blockMinZ.
|
||||||
|
- The block to the -Z direction is air.
|
||||||
|
*/
|
||||||
|
boolean collidingWithNegativeZ = playerBodyPrevMaxZ < blockMinZ && playerBodyMaxZ >= blockMinZ && world.getBlockAt(x, y, z - 1) <= 0;
|
||||||
|
if (collidingWithNegativeZ) {
|
||||||
|
position.z = blockMinZ - RADIUS - delta;
|
||||||
|
velocity.z = 0;
|
||||||
|
movement.z = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
To determine if the player is moving into the +Z side of a block:
|
||||||
|
- The player's min z position went from >= blockMaxZ to < blockMaxZ.
|
||||||
|
- The block to the +Z direction is air.
|
||||||
|
*/
|
||||||
|
boolean collidingWithPositiveZ = playerBodyPrevMinZ >= blockMaxZ && playerBodyMinZ < blockMaxZ && world.getBlockAt(x, y, z + 1) <= 0;
|
||||||
|
if (collidingWithPositiveZ) {
|
||||||
|
position.z = blockMaxZ + RADIUS + delta;
|
||||||
|
velocity.z = 0;
|
||||||
|
movement.z = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
To determine if the player is moving into the -X side of a block:
|
||||||
|
- The player's max x position went from < blockMinX to >= blockMinX
|
||||||
|
- The block to the -X direction is air.
|
||||||
|
*/
|
||||||
|
boolean collidingWithNegativeX = playerBodyPrevMaxX < blockMinX && playerBodyMaxX >= blockMinX && world.getBlockAt(x - 1, y, z) <= 0;
|
||||||
|
if (collidingWithNegativeX) {
|
||||||
|
position.x = blockMinX - RADIUS - delta;
|
||||||
|
velocity.x = 0;
|
||||||
|
movement.x = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
To determine if the player is moving into the +X side of a block:
|
||||||
|
- The player's min x position went from >= blockMaxX to < blockMaxX.
|
||||||
|
- The block to the +X direction is air.
|
||||||
|
*/
|
||||||
|
boolean collidingWithPositiveX = playerBodyPrevMinX >= blockMaxX && playerBodyMinX < blockMaxX && world.getBlockAt(x + 1, y, z) <= 0;
|
||||||
|
if (collidingWithPositiveX) {
|
||||||
|
position.x = blockMaxX + RADIUS + delta;
|
||||||
|
velocity.x = 0;
|
||||||
|
movement.x = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
To determine if the player is moving down onto a block:
|
||||||
|
- The player's min y position went from >= blockMaxY to < blockMaxY
|
||||||
|
- The block above the current one is air.
|
||||||
|
*/
|
||||||
|
boolean collidingWithFloor = playerBodyPrevMinY >= blockMaxY && playerBodyMinY < blockMaxY && world.getBlockAt(x, y + 1, z) <= 0;
|
||||||
|
if (collidingWithFloor) {
|
||||||
|
// This is a special case! We need to check for fall damage.
|
||||||
|
if (velocity.y < -20) {
|
||||||
|
float damage = velocity.y / 50f;
|
||||||
|
player.setHealth(player.getHealth() + damage);
|
||||||
|
if (player.getHealth() <= 0) {
|
||||||
|
server.getPlayerManager().playerKilled(player, player);
|
||||||
|
} else {
|
||||||
|
var handler = server.getPlayerManager().getHandler(player.getId());
|
||||||
|
handler.sendDatagramPacket(new ClientHealthMessage(player.getHealth()));
|
||||||
|
int soundVariant = ThreadLocalRandom.current().nextInt(1, 4);
|
||||||
|
handler.sendDatagramPacket(new SoundMessage("hurt_" + soundVariant, 1, player.getPosition()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
position.y = blockMaxY;
|
||||||
|
velocity.y = 0;
|
||||||
|
movement.y = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
To determine if the player is moving up into a block:
|
||||||
|
- The player's y position went from below blockMinY to >= blockMinY
|
||||||
|
- The block below the current one is air.
|
||||||
|
*/
|
||||||
|
boolean collidingWithCeiling = playerBodyPrevMaxY < blockMinY && playerBodyMaxY >= blockMinY && world.getBlockAt(x, y - 1, z) <= 0;
|
||||||
|
if (collidingWithCeiling) {
|
||||||
|
position.y = blockMinY - height - delta;
|
||||||
|
velocity.y = 0;
|
||||||
|
movement.y = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,8 +1,8 @@
|
||||||
package nl.andrewl.aos2_server.logic;
|
package nl.andrewl.aos2_server.logic;
|
||||||
|
|
||||||
import nl.andrewl.aos2_server.Server;
|
import nl.andrewl.aos2_server.Server;
|
||||||
import nl.andrewl.aos2_server.config.ServerConfig;
|
|
||||||
import nl.andrewl.aos2_server.model.ServerPlayer;
|
import nl.andrewl.aos2_server.model.ServerPlayer;
|
||||||
|
import nl.andrewl.aos_core.model.PlayerMode;
|
||||||
import nl.andrewl.aos_core.model.item.BlockItemStack;
|
import nl.andrewl.aos_core.model.item.BlockItemStack;
|
||||||
import nl.andrewl.aos_core.model.item.Gun;
|
import nl.andrewl.aos_core.model.item.Gun;
|
||||||
import nl.andrewl.aos_core.model.item.GunItemStack;
|
import nl.andrewl.aos_core.model.item.GunItemStack;
|
||||||
|
@ -14,22 +14,19 @@ import nl.andrewl.aos_core.model.world.World;
|
||||||
import nl.andrewl.aos_core.net.client.*;
|
import nl.andrewl.aos_core.net.client.*;
|
||||||
import nl.andrewl.aos_core.net.world.ChunkUpdateMessage;
|
import nl.andrewl.aos_core.net.world.ChunkUpdateMessage;
|
||||||
import org.joml.Math;
|
import org.joml.Math;
|
||||||
import org.joml.Vector2i;
|
|
||||||
import org.joml.Vector3f;
|
import org.joml.Vector3f;
|
||||||
import org.joml.Vector3i;
|
import org.joml.Vector3i;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.ThreadLocalRandom;
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
|
||||||
import static nl.andrewl.aos2_server.model.ServerPlayer.RADIUS;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component that manages a server player's current actions and movement.
|
* Component that manages a server player's current actions and movement.
|
||||||
*/
|
*/
|
||||||
public class PlayerActionManager {
|
public class PlayerActionManager {
|
||||||
private final ServerPlayer player;
|
private final ServerPlayer player;
|
||||||
private final PlayerInputTracker input;
|
private final PlayerInputTracker input;
|
||||||
|
private final PlayerMovementController normalMovementController = new NormalMovementController();
|
||||||
|
private final CreativeMovementController creativeMovementController = new CreativeMovementController();
|
||||||
|
|
||||||
private long lastBlockRemovedAt = 0;
|
private long lastBlockRemovedAt = 0;
|
||||||
private long lastBlockPlacedAt = 0;
|
private long lastBlockPlacedAt = 0;
|
||||||
|
@ -91,7 +88,7 @@ public class PlayerActionManager {
|
||||||
lastResupplyAt = now;
|
lastResupplyAt = now;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (server.getConfig().actions.healthRegenPerSecond != 0 && player.getHealth() < 1) {
|
if (player.getMode() == PlayerMode.NORMAL && server.getConfig().actions.healthRegenPerSecond != 0 && player.getHealth() < 1) {
|
||||||
player.setHealth(player.getHealth() + server.getConfig().actions.healthRegenPerSecond * dt);
|
player.setHealth(player.getHealth() + server.getConfig().actions.healthRegenPerSecond * dt);
|
||||||
server.getPlayerManager().getHandler(player).sendDatagramPacket(new ClientHealthMessage(player.getHealth()));
|
server.getPlayerManager().getHandler(player).sendDatagramPacket(new ClientHealthMessage(player.getHealth()));
|
||||||
}
|
}
|
||||||
|
@ -101,7 +98,11 @@ public class PlayerActionManager {
|
||||||
updated = true;
|
updated = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
tickMovement(dt, server, world, server.getConfig().physics);
|
updated = switch (player.getMode()) {
|
||||||
|
case NORMAL -> normalMovementController.tickMovement(dt, player, input, server, world, server.getConfig().physics);
|
||||||
|
case CREATIVE -> creativeMovementController.tickMovement(dt, player, input, server, world, server.getConfig().physics);
|
||||||
|
case SPECTATOR -> false;
|
||||||
|
} || updated;
|
||||||
input.reset(); // Reset our input state after processing this tick's player input.
|
input.reset(); // Reset our input state after processing this tick's player input.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,12 +142,16 @@ public class PlayerActionManager {
|
||||||
stack.getAmount() < stack.getType().getMaxAmount() &&
|
stack.getAmount() < stack.getType().getMaxAmount() &&
|
||||||
now - lastBlockRemovedAt > server.getConfig().actions.blockBreakCooldown * 1000
|
now - lastBlockRemovedAt > server.getConfig().actions.blockBreakCooldown * 1000
|
||||||
) {
|
) {
|
||||||
var hit = world.getLookingAtPos(player.getEyePosition(), player.getViewVector(), server.getConfig().actions.blockBreakReach);
|
float reach = server.getConfig().actions.blockBreakReach;
|
||||||
|
if (player.getMode() == PlayerMode.CREATIVE) reach *= 10;
|
||||||
|
var hit = world.getLookingAtPos(player.getEyePosition(), player.getViewVector(), reach);
|
||||||
if (hit != null && !server.getTeamManager().isProtected(hit.pos())) {
|
if (hit != null && !server.getTeamManager().isProtected(hit.pos())) {
|
||||||
world.setBlockAt(hit.pos().x, hit.pos().y, hit.pos().z, (byte) 0);
|
world.setBlockAt(hit.pos().x, hit.pos().y, hit.pos().z, (byte) 0);
|
||||||
lastBlockRemovedAt = now;
|
lastBlockRemovedAt = now;
|
||||||
stack.incrementAmount();
|
if (player.getMode() == PlayerMode.NORMAL) {
|
||||||
server.getPlayerManager().getHandler(player.getId()).sendDatagramPacket(new ItemStackMessage(player.getInventory()));
|
stack.incrementAmount();
|
||||||
|
server.getPlayerManager().getHandler(player.getId()).sendDatagramPacket(new ItemStackMessage(player.getInventory()));
|
||||||
|
}
|
||||||
server.getPlayerManager().broadcastUdpMessage(ChunkUpdateMessage.fromWorld(hit.pos(), world));
|
server.getPlayerManager().broadcastUdpMessage(ChunkUpdateMessage.fromWorld(hit.pos(), world));
|
||||||
server.getPlayerManager().broadcastUdpMessage(new SoundMessage("block_break_1", 1, player.getPosition()));
|
server.getPlayerManager().broadcastUdpMessage(new SoundMessage("block_break_1", 1, player.getPosition()));
|
||||||
}
|
}
|
||||||
|
@ -157,7 +162,9 @@ public class PlayerActionManager {
|
||||||
stack.getAmount() > 0 &&
|
stack.getAmount() > 0 &&
|
||||||
now - lastBlockPlacedAt > server.getConfig().actions.blockPlaceCooldown * 1000
|
now - lastBlockPlacedAt > server.getConfig().actions.blockPlaceCooldown * 1000
|
||||||
) {
|
) {
|
||||||
var hit = world.getLookingAtPos(player.getEyePosition(), player.getViewVector(), server.getConfig().actions.blockPlaceReach);
|
float reach = server.getConfig().actions.blockPlaceReach;
|
||||||
|
if (player.getMode() == PlayerMode.CREATIVE) reach *= 10;
|
||||||
|
var hit = world.getLookingAtPos(player.getEyePosition(), player.getViewVector(), reach);
|
||||||
if (hit != null && !server.getTeamManager().isProtected(hit.pos())) {
|
if (hit != null && !server.getTeamManager().isProtected(hit.pos())) {
|
||||||
Vector3i placePos = new Vector3i(hit.pos());
|
Vector3i placePos = new Vector3i(hit.pos());
|
||||||
placePos.add(hit.norm());
|
placePos.add(hit.norm());
|
||||||
|
@ -167,8 +174,10 @@ public class PlayerActionManager {
|
||||||
if (canPlace) { // Ensure that we can't place blocks in space we're occupying.
|
if (canPlace) { // Ensure that we can't place blocks in space we're occupying.
|
||||||
world.setBlockAt(placePos.x, placePos.y, placePos.z, stack.getSelectedValue());
|
world.setBlockAt(placePos.x, placePos.y, placePos.z, stack.getSelectedValue());
|
||||||
lastBlockPlacedAt = now;
|
lastBlockPlacedAt = now;
|
||||||
stack.decrementAmount();
|
if (player.getMode() == PlayerMode.NORMAL) {
|
||||||
server.getPlayerManager().getHandler(player.getId()).sendDatagramPacket(new ItemStackMessage(player.getInventory()));
|
stack.decrementAmount();
|
||||||
|
server.getPlayerManager().getHandler(player.getId()).sendDatagramPacket(new ItemStackMessage(player.getInventory()));
|
||||||
|
}
|
||||||
server.getPlayerManager().broadcastUdpMessage(ChunkUpdateMessage.fromWorld(placePos, world));
|
server.getPlayerManager().broadcastUdpMessage(ChunkUpdateMessage.fromWorld(placePos, world));
|
||||||
server.getPlayerManager().broadcastUdpMessage(new SoundMessage("block_place_1", 1, player.getPosition()));
|
server.getPlayerManager().broadcastUdpMessage(new SoundMessage("block_place_1", 1, player.getPosition()));
|
||||||
}
|
}
|
||||||
|
@ -176,271 +185,6 @@ public class PlayerActionManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void tickMovement(float dt, Server server, World world, ServerConfig.PhysicsConfig config) {
|
|
||||||
var velocity = player.getVelocity();
|
|
||||||
var position = player.getPosition();
|
|
||||||
boolean grounded = isGrounded(world);
|
|
||||||
tickHorizontalVelocity(config, grounded);
|
|
||||||
|
|
||||||
if (grounded) {
|
|
||||||
if (input.jumping()) {
|
|
||||||
velocity.y = config.jumpVerticalSpeed * (input.sprinting() ? 1.25f : 1f);
|
|
||||||
updated = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
velocity.y -= config.gravity * dt * 2; // Apply double-gravity to players to make the game feel faster.
|
|
||||||
updated = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply updated velocity to the player.
|
|
||||||
if (velocity.lengthSquared() > 0) {
|
|
||||||
Vector3f movement = new Vector3f(velocity).mul(dt);
|
|
||||||
// Check for collisions if we try to move according to what the player wants.
|
|
||||||
checkBlockCollisions(movement, server, world);
|
|
||||||
position.add(movement);
|
|
||||||
updated = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finally, check to see if the player is outside the world, and kill them if so.
|
|
||||||
if (
|
|
||||||
player.getPosition().x < world.getMinX() - 5 || player.getPosition().x > world.getMaxX() + 6 ||
|
|
||||||
player.getPosition().z < world.getMinZ() - 5 || player.getPosition().z > world.getMaxZ() + 6 ||
|
|
||||||
player.getPosition().y < world.getMinY() - 50 || player.getPosition().y > world.getMaxY() + 500
|
|
||||||
) {
|
|
||||||
server.getPlayerManager().playerKilled(player, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void tickHorizontalVelocity(ServerConfig.PhysicsConfig config, boolean grounded) {
|
|
||||||
var velocity = player.getVelocity();
|
|
||||||
var orientation = player.getOrientation();
|
|
||||||
Vector3f horizontalVelocity = new Vector3f(
|
|
||||||
velocity.x == velocity.x ? velocity.x : 0f,
|
|
||||||
0,
|
|
||||||
velocity.z == velocity.z ? velocity.z : 0f
|
|
||||||
);
|
|
||||||
Vector3f acceleration = new Vector3f(0);
|
|
||||||
if (input.forward()) acceleration.z -= 1;
|
|
||||||
if (input.backward()) acceleration.z += 1;
|
|
||||||
if (input.left()) acceleration.x -= 1;
|
|
||||||
if (input.right()) acceleration.x += 1;
|
|
||||||
if (acceleration.lengthSquared() > 0) {
|
|
||||||
acceleration.normalize();
|
|
||||||
acceleration.rotateAxis(orientation.x, 0, 1, 0);
|
|
||||||
float accelerationMagnitude = config.movementAcceleration;
|
|
||||||
if (!grounded) accelerationMagnitude *= 0.25f;
|
|
||||||
acceleration.mul(accelerationMagnitude);
|
|
||||||
horizontalVelocity.add(acceleration);
|
|
||||||
float maxSpeed;
|
|
||||||
if (input.crouching()) {
|
|
||||||
maxSpeed = config.crouchingSpeed;
|
|
||||||
} else if (input.sprinting()) {
|
|
||||||
maxSpeed = config.sprintingSpeed;
|
|
||||||
} else {
|
|
||||||
maxSpeed = config.walkingSpeed;
|
|
||||||
}
|
|
||||||
// If scoping, then force limit to crouching speed.
|
|
||||||
if (isScopeEnabled()) maxSpeed = config.crouchingSpeed;
|
|
||||||
if (horizontalVelocity.length() > maxSpeed) {
|
|
||||||
horizontalVelocity.normalize(maxSpeed);
|
|
||||||
}
|
|
||||||
updated = true;
|
|
||||||
} else if (horizontalVelocity.lengthSquared() > 0) {
|
|
||||||
float baseDecel = config.movementDeceleration;
|
|
||||||
if (!grounded) baseDecel *= 0.25f;
|
|
||||||
float decelerationMagnitude = Math.min(horizontalVelocity.length(), baseDecel);
|
|
||||||
Vector3f deceleration = new Vector3f(horizontalVelocity).negate().normalize().mul(decelerationMagnitude);
|
|
||||||
horizontalVelocity.add(deceleration);
|
|
||||||
if (horizontalVelocity.length() < 0.1f) {
|
|
||||||
horizontalVelocity.set(0);
|
|
||||||
}
|
|
||||||
updated = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the player's velocity with what we've computed.
|
|
||||||
velocity.x = horizontalVelocity.x;
|
|
||||||
velocity.z = horizontalVelocity.z;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isGrounded(World world) {
|
|
||||||
var position = player.getPosition();
|
|
||||||
// Player must be flat on the top of a block.
|
|
||||||
if (Math.floor(position.y) != position.y) return false;
|
|
||||||
// Check to see if there's a block under any of the spaces the player is over.
|
|
||||||
return getHorizontalSpaceOccupied(position).stream()
|
|
||||||
.anyMatch(point -> world.getBlockAt(point.x, position.y - 0.1f, point.y) != 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the list of all spaces occupied by a player's position, in the
|
|
||||||
* horizontal XZ plane. This can be between 1 and 4 spaces, depending on
|
|
||||||
* if the player's position is overlapping with a few blocks.
|
|
||||||
* @param pos The position.
|
|
||||||
* @return The list of 2d positions occupied.
|
|
||||||
*/
|
|
||||||
private List<Vector2i> getHorizontalSpaceOccupied(Vector3f pos) {
|
|
||||||
// Get the list of 2d x,z coordinates that we overlap with.
|
|
||||||
List<Vector2i> points = new ArrayList<>(4); // Due to the size of radius, there can only be a max of 4 blocks.
|
|
||||||
int minX = (int) Math.floor(pos.x - RADIUS);
|
|
||||||
int minZ = (int) Math.floor(pos.z - RADIUS);
|
|
||||||
int maxX = (int) Math.floor(pos.x + RADIUS);
|
|
||||||
int maxZ = (int) Math.floor(pos.z + RADIUS);
|
|
||||||
for (int x = minX; x <= maxX; x++) {
|
|
||||||
for (int z = minZ; z <= maxZ; z++) {
|
|
||||||
points.add(new Vector2i(x, z));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return points;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkBlockCollisions(Vector3f movement, Server server, World world) {
|
|
||||||
var position = player.getPosition();
|
|
||||||
var velocity = player.getVelocity();
|
|
||||||
final Vector3f nextTickPosition = new Vector3f(position).add(movement);
|
|
||||||
float height = player.getCurrentHeight();
|
|
||||||
float delta = 0.00001f;
|
|
||||||
final Vector3f stepSize = new Vector3f(movement).normalize(1.0f);
|
|
||||||
// The number of steps we'll make towards the next tick position.
|
|
||||||
int stepCount = (int) Math.ceil(movement.length());
|
|
||||||
if (stepCount == 0) return; // No movement, so exit.
|
|
||||||
final Vector3f nextPos = new Vector3f(position);
|
|
||||||
final Vector3f lastPos = new Vector3f(position);
|
|
||||||
for (int i = 0; i < stepCount; i++) {
|
|
||||||
lastPos.set(nextPos);
|
|
||||||
nextPos.add(stepSize);
|
|
||||||
// If we shot past the next tick position, clamp it to that.
|
|
||||||
if (new Vector3f(nextPos).sub(position).length() > movement.length()) {
|
|
||||||
nextPos.set(nextTickPosition);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if we collide with anything at this new position.
|
|
||||||
|
|
||||||
float playerBodyPrevMinZ = lastPos.z - RADIUS;
|
|
||||||
float playerBodyPrevMaxZ = lastPos.z + RADIUS;
|
|
||||||
float playerBodyPrevMinX = lastPos.x - RADIUS;
|
|
||||||
float playerBodyPrevMaxX = lastPos.x + RADIUS;
|
|
||||||
float playerBodyPrevMinY = lastPos.y;
|
|
||||||
float playerBodyPrevMaxY = lastPos.y + height;
|
|
||||||
|
|
||||||
float playerBodyMinZ = nextPos.z - RADIUS;
|
|
||||||
float playerBodyMaxZ = nextPos.z + RADIUS;
|
|
||||||
float playerBodyMinX = nextPos.x - RADIUS;
|
|
||||||
float playerBodyMaxX = nextPos.x + RADIUS;
|
|
||||||
float playerBodyMinY = nextPos.y;
|
|
||||||
float playerBodyMaxY = nextPos.y + height;
|
|
||||||
|
|
||||||
// Compute the bounds of all blocks the player is intersecting with.
|
|
||||||
int minX = (int) Math.floor(playerBodyMinX);
|
|
||||||
int minZ = (int) Math.floor(playerBodyMinZ);
|
|
||||||
int minY = (int) Math.floor(playerBodyMinY);
|
|
||||||
int maxX = (int) Math.floor(playerBodyMaxX);
|
|
||||||
int maxZ = (int) Math.floor(playerBodyMaxZ);
|
|
||||||
int maxY = (int) Math.floor(playerBodyMaxY);
|
|
||||||
|
|
||||||
for (int x = minX; x <= maxX; x++) {
|
|
||||||
for (int z = minZ; z <= maxZ; z++) {
|
|
||||||
for (int y = minY; y <= maxY; y++) {
|
|
||||||
byte block = world.getBlockAt(x, y, z);
|
|
||||||
if (block <= 0) continue; // We're not colliding with this block.
|
|
||||||
float blockMinY = (float) y;
|
|
||||||
float blockMaxY = (float) y + 1;
|
|
||||||
float blockMinX = (float) x;
|
|
||||||
float blockMaxX = (float) x + 1;
|
|
||||||
float blockMinZ = (float) z;
|
|
||||||
float blockMaxZ = (float) z + 1;
|
|
||||||
|
|
||||||
/*
|
|
||||||
To determine if the player is moving into the -Z side of a block:
|
|
||||||
- The player's max z position went from < blockMinZ to >= blockMinZ.
|
|
||||||
- The block to the -Z direction is air.
|
|
||||||
*/
|
|
||||||
boolean collidingWithNegativeZ = playerBodyPrevMaxZ < blockMinZ && playerBodyMaxZ >= blockMinZ && world.getBlockAt(x, y, z - 1) <= 0;
|
|
||||||
if (collidingWithNegativeZ) {
|
|
||||||
position.z = blockMinZ - RADIUS - delta;
|
|
||||||
velocity.z = 0;
|
|
||||||
movement.z = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
To determine if the player is moving into the +Z side of a block:
|
|
||||||
- The player's min z position went from >= blockMaxZ to < blockMaxZ.
|
|
||||||
- The block to the +Z direction is air.
|
|
||||||
*/
|
|
||||||
boolean collidingWithPositiveZ = playerBodyPrevMinZ >= blockMaxZ && playerBodyMinZ < blockMaxZ && world.getBlockAt(x, y, z + 1) <= 0;
|
|
||||||
if (collidingWithPositiveZ) {
|
|
||||||
position.z = blockMaxZ + RADIUS + delta;
|
|
||||||
velocity.z = 0;
|
|
||||||
movement.z = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
To determine if the player is moving into the -X side of a block:
|
|
||||||
- The player's max x position went from < blockMinX to >= blockMinX
|
|
||||||
- The block to the -X direction is air.
|
|
||||||
*/
|
|
||||||
boolean collidingWithNegativeX = playerBodyPrevMaxX < blockMinX && playerBodyMaxX >= blockMinX && world.getBlockAt(x - 1, y, z) <= 0;
|
|
||||||
if (collidingWithNegativeX) {
|
|
||||||
position.x = blockMinX - RADIUS - delta;
|
|
||||||
velocity.x = 0;
|
|
||||||
movement.x = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
To determine if the player is moving into the +X side of a block:
|
|
||||||
- The player's min x position went from >= blockMaxX to < blockMaxX.
|
|
||||||
- The block to the +X direction is air.
|
|
||||||
*/
|
|
||||||
boolean collidingWithPositiveX = playerBodyPrevMinX >= blockMaxX && playerBodyMinX < blockMaxX && world.getBlockAt(x + 1, y, z) <= 0;
|
|
||||||
if (collidingWithPositiveX) {
|
|
||||||
position.x = blockMaxX + RADIUS + delta;
|
|
||||||
velocity.x = 0;
|
|
||||||
movement.x = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
To determine if the player is moving down onto a block:
|
|
||||||
- The player's min y position went from >= blockMaxY to < blockMaxY
|
|
||||||
- The block above the current one is air.
|
|
||||||
*/
|
|
||||||
boolean collidingWithFloor = playerBodyPrevMinY >= blockMaxY && playerBodyMinY < blockMaxY && world.getBlockAt(x, y + 1, z) <= 0;
|
|
||||||
if (collidingWithFloor) {
|
|
||||||
// This is a special case! We need to check for fall damage.
|
|
||||||
if (velocity.y < -20) {
|
|
||||||
float damage = velocity.y / 50f;
|
|
||||||
player.setHealth(player.getHealth() + damage);
|
|
||||||
if (player.getHealth() <= 0) {
|
|
||||||
server.getPlayerManager().playerKilled(player, player);
|
|
||||||
} else {
|
|
||||||
var handler = server.getPlayerManager().getHandler(player.getId());
|
|
||||||
handler.sendDatagramPacket(new ClientHealthMessage(player.getHealth()));
|
|
||||||
int soundVariant = ThreadLocalRandom.current().nextInt(1, 4);
|
|
||||||
handler.sendDatagramPacket(new SoundMessage("hurt_" + soundVariant, 1, player.getPosition()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
position.y = blockMaxY;
|
|
||||||
velocity.y = 0;
|
|
||||||
movement.y = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
To determine if the player is moving up into a block:
|
|
||||||
- The player's y position went from below blockMinY to >= blockMinY
|
|
||||||
- The block below the current one is air.
|
|
||||||
*/
|
|
||||||
boolean collidingWithCeiling = playerBodyPrevMaxY < blockMinY && playerBodyMaxY >= blockMinY && world.getBlockAt(x, y - 1, z) <= 0;
|
|
||||||
if (collidingWithCeiling) {
|
|
||||||
position.y = blockMinY - height - delta;
|
|
||||||
velocity.y = 0;
|
|
||||||
movement.y = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
updated = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void shootGun(long now, Server server, Gun gun, GunItemStack g) {
|
private void shootGun(long now, Server server, Gun gun, GunItemStack g) {
|
||||||
server.getProjectileManager().spawnBullets(player, gun);
|
server.getProjectileManager().spawnBullets(player, gun);
|
||||||
g.setBulletCount(g.getBulletCount() - 1);
|
g.setBulletCount(g.getBulletCount() - 1);
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
package nl.andrewl.aos2_server.logic;
|
||||||
|
|
||||||
|
import nl.andrewl.aos2_server.Server;
|
||||||
|
import nl.andrewl.aos2_server.config.ServerConfig;
|
||||||
|
import nl.andrewl.aos2_server.model.ServerPlayer;
|
||||||
|
import nl.andrewl.aos_core.model.world.World;
|
||||||
|
|
||||||
|
public interface PlayerMovementController {
|
||||||
|
boolean tickMovement(float dt, ServerPlayer player, PlayerInputTracker input, Server server, World world, ServerConfig.PhysicsConfig config);
|
||||||
|
}
|
|
@ -2,7 +2,6 @@ package nl.andrewl.aos2_server.model;
|
||||||
|
|
||||||
import nl.andrewl.aos2_server.logic.PlayerActionManager;
|
import nl.andrewl.aos2_server.logic.PlayerActionManager;
|
||||||
import nl.andrewl.aos_core.model.Player;
|
import nl.andrewl.aos_core.model.Player;
|
||||||
import nl.andrewl.aos_core.model.PlayerMode;
|
|
||||||
import nl.andrewl.aos_core.model.item.BlockItemStack;
|
import nl.andrewl.aos_core.model.item.BlockItemStack;
|
||||||
import nl.andrewl.aos_core.model.item.GunItemStack;
|
import nl.andrewl.aos_core.model.item.GunItemStack;
|
||||||
import nl.andrewl.aos_core.model.item.Inventory;
|
import nl.andrewl.aos_core.model.item.Inventory;
|
||||||
|
|
Binary file not shown.
Loading…
Reference in New Issue