Updated server-side logic and added better inventory models.
This commit is contained in:
parent
758372108b
commit
d83ff8a816
|
@ -102,9 +102,9 @@ public class Player {
|
||||||
public Vector3f getRightVector() {
|
public Vector3f getRightVector() {
|
||||||
float x = orientation.x - (float) (Math.PI / 2);
|
float x = orientation.x - (float) (Math.PI / 2);
|
||||||
return new Vector3f(
|
return new Vector3f(
|
||||||
sin(orientation.x),
|
sin(x),
|
||||||
0,
|
0,
|
||||||
cos(orientation.x)
|
cos(x)
|
||||||
).normalize();
|
).normalize();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
package nl.andrewl.aos_core.model.item;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A block item that contains information about what type of block value is
|
||||||
|
* currently selected.
|
||||||
|
*/
|
||||||
|
public class BlockItem extends Item {
|
||||||
|
public BlockItem(int id) {
|
||||||
|
super(id, "Block", 100);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package nl.andrewl.aos_core.model.item;
|
||||||
|
|
||||||
|
public class BlockItemStack extends ItemStack {
|
||||||
|
private int selectedValue = 1;
|
||||||
|
|
||||||
|
public BlockItemStack(BlockItem item, int amount) {
|
||||||
|
super(item, amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSelectedValue() {
|
||||||
|
return selectedValue;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
package nl.andrewl.aos_core.model.item;
|
||||||
|
|
||||||
|
import nl.andrewl.aos_core.MathUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The base class for all types of guns.
|
||||||
|
*/
|
||||||
|
public class Gun extends Item {
|
||||||
|
private final int maxClipCount;
|
||||||
|
private final int maxBulletCount;
|
||||||
|
private final int bulletsPerRound;
|
||||||
|
private final float accuracy;
|
||||||
|
private final float shotCooldownTime;
|
||||||
|
private final float reloadTime;
|
||||||
|
private final float baseDamage;
|
||||||
|
private final float recoil;
|
||||||
|
|
||||||
|
public Gun(
|
||||||
|
int id,
|
||||||
|
String name,
|
||||||
|
int maxClipCount,
|
||||||
|
int maxBulletCount,
|
||||||
|
int bulletsPerRound,
|
||||||
|
float accuracy,
|
||||||
|
float shotCooldownTime,
|
||||||
|
float reloadTime,
|
||||||
|
float baseDamage,
|
||||||
|
float recoil
|
||||||
|
) {
|
||||||
|
super(id, name, 1);
|
||||||
|
this.maxClipCount = maxClipCount;
|
||||||
|
this.maxBulletCount = maxBulletCount;
|
||||||
|
this.bulletsPerRound = bulletsPerRound;
|
||||||
|
this.accuracy = MathUtils.clamp(accuracy, 0, 1);
|
||||||
|
this.shotCooldownTime = shotCooldownTime;
|
||||||
|
this.reloadTime = reloadTime;
|
||||||
|
this.baseDamage = baseDamage;
|
||||||
|
this.recoil = recoil;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMaxClipCount() {
|
||||||
|
return maxClipCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMaxBulletCount() {
|
||||||
|
return maxBulletCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getBulletsPerRound() {
|
||||||
|
return bulletsPerRound;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getAccuracy() {
|
||||||
|
return accuracy;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getShotCooldownTime() {
|
||||||
|
return shotCooldownTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getReloadTime() {
|
||||||
|
return reloadTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getBaseDamage() {
|
||||||
|
return baseDamage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getRecoil() {
|
||||||
|
return recoil;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
package nl.andrewl.aos_core.model.item;
|
||||||
|
|
||||||
|
public class GunItemStack extends ItemStack {
|
||||||
|
private int bulletCount;
|
||||||
|
private int clipCount;
|
||||||
|
|
||||||
|
public GunItemStack(Gun gun) {
|
||||||
|
super(gun, 1);
|
||||||
|
bulletCount = gun.getMaxBulletCount();
|
||||||
|
clipCount = gun.getMaxClipCount();
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,6 +21,10 @@ public class Inventory {
|
||||||
this.selectedIndex = selectedIndex;
|
this.selectedIndex = selectedIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<ItemStack> getItemStacks() {
|
||||||
|
return itemStacks;
|
||||||
|
}
|
||||||
|
|
||||||
public ItemStack getSelectedItemStack() {
|
public ItemStack getSelectedItemStack() {
|
||||||
return itemStacks.get(selectedIndex);
|
return itemStacks.get(selectedIndex);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,12 +3,23 @@ package nl.andrewl.aos_core.model.item;
|
||||||
/**
|
/**
|
||||||
* Represents a type of item that a player can have.
|
* Represents a type of item that a player can have.
|
||||||
*/
|
*/
|
||||||
public class ItemType {
|
public class Item {
|
||||||
private final int id;
|
/**
|
||||||
private final String name;
|
* The item's unique id.
|
||||||
private final int maxAmount;
|
*/
|
||||||
|
protected final int id;
|
||||||
|
|
||||||
public ItemType(int id, String name, int maxAmount) {
|
/**
|
||||||
|
* The item's unique name.
|
||||||
|
*/
|
||||||
|
protected final String name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum amount of this item that can be in a stack at once.
|
||||||
|
*/
|
||||||
|
protected final int maxAmount;
|
||||||
|
|
||||||
|
public Item(int id, String name, int maxAmount) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.maxAmount = maxAmount;
|
this.maxAmount = maxAmount;
|
|
@ -5,15 +5,15 @@ package nl.andrewl.aos_core.model.item;
|
||||||
* a type of item, and the amount of it.
|
* a type of item, and the amount of it.
|
||||||
*/
|
*/
|
||||||
public class ItemStack {
|
public class ItemStack {
|
||||||
private final ItemType type;
|
private final Item type;
|
||||||
private int amount;
|
private int amount;
|
||||||
|
|
||||||
public ItemStack(ItemType type, int amount) {
|
public ItemStack(Item type, int amount) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.amount = amount;
|
this.amount = amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Object getType() {
|
public Item getType() {
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package nl.andrewl.aos_core.model.item;
|
package nl.andrewl.aos_core.model.item;
|
||||||
|
|
||||||
|
import nl.andrewl.aos_core.model.item.gun.Rifle;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
@ -7,14 +9,26 @@ import java.util.Map;
|
||||||
* Global constant set of registered item types.
|
* Global constant set of registered item types.
|
||||||
*/
|
*/
|
||||||
public final class ItemTypes {
|
public final class ItemTypes {
|
||||||
public static final Map<Integer, ItemType> TYPES_MAP = new HashMap<>();
|
private static final Map<Integer, Item> TYPES_BY_ID = new HashMap<>();
|
||||||
|
private static final Map<String, Item> TYPES_BY_NAME = new HashMap<>();
|
||||||
|
|
||||||
static {
|
static {
|
||||||
registerType(new ItemType(1, "Rifle", 1));
|
registerType(new BlockItem(1));
|
||||||
registerType(new ItemType(2, "Block", 100));
|
registerType(new Rifle(2));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void registerType(ItemType type) {
|
public static void registerType(Item type) {
|
||||||
TYPES_MAP.put(type.getId(), type);
|
TYPES_BY_ID.put(type.getId(), type);
|
||||||
|
TYPES_BY_NAME.put(type.getName(), type);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public static <T extends Item> T get(int id) {
|
||||||
|
return (T) TYPES_BY_ID.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public static <T extends Item> T get(String name) {
|
||||||
|
return (T) TYPES_BY_NAME.get(name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
package nl.andrewl.aos_core.model.item.gun;
|
||||||
|
|
||||||
|
import nl.andrewl.aos_core.model.item.Gun;
|
||||||
|
|
||||||
|
public class Rifle extends Gun {
|
||||||
|
public Rifle(int id) {
|
||||||
|
super(
|
||||||
|
id,
|
||||||
|
"Rifle",
|
||||||
|
5,
|
||||||
|
8,
|
||||||
|
1,
|
||||||
|
0.97f,
|
||||||
|
0.8f,
|
||||||
|
2.5f,
|
||||||
|
80f,
|
||||||
|
50f
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,7 +12,8 @@ public record PlayerUpdateMessage(
|
||||||
float px, float py, float pz,
|
float px, float py, float pz,
|
||||||
float vx, float vy, float vz,
|
float vx, float vy, float vz,
|
||||||
float ox, float oy,
|
float ox, float oy,
|
||||||
boolean crouching
|
boolean crouching,
|
||||||
|
int selectedItemId
|
||||||
) implements Message {
|
) implements Message {
|
||||||
|
|
||||||
public void apply(Player p) {
|
public void apply(Player p) {
|
||||||
|
|
|
@ -4,7 +4,6 @@ import nl.andrewl.aos_core.Net;
|
||||||
import nl.andrewl.aos_core.net.PlayerJoinMessage;
|
import nl.andrewl.aos_core.net.PlayerJoinMessage;
|
||||||
import nl.andrewl.aos_core.net.PlayerLeaveMessage;
|
import nl.andrewl.aos_core.net.PlayerLeaveMessage;
|
||||||
import nl.andrewl.aos_core.net.udp.DatagramInit;
|
import nl.andrewl.aos_core.net.udp.DatagramInit;
|
||||||
import nl.andrewl.aos_core.net.udp.PlayerUpdateMessage;
|
|
||||||
import nl.andrewl.record_net.Message;
|
import nl.andrewl.record_net.Message;
|
||||||
import org.joml.Vector3f;
|
import org.joml.Vector3f;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@ -32,13 +31,7 @@ public class PlayerManager {
|
||||||
log.info("Registered player \"{}\" with id {}", player.getUsername(), player.getId());
|
log.info("Registered player \"{}\" with id {}", player.getUsername(), player.getId());
|
||||||
player.setPosition(new Vector3f(0, 64, 0));
|
player.setPosition(new Vector3f(0, 64, 0));
|
||||||
broadcastTcpMessageToAllBut(new PlayerJoinMessage(player), player);
|
broadcastTcpMessageToAllBut(new PlayerJoinMessage(player), player);
|
||||||
broadcastUdpMessage(new PlayerUpdateMessage(
|
broadcastUdpMessage(player.getUpdateMessage());
|
||||||
player.getId(),
|
|
||||||
player.getPosition().x, player.getPosition().y, player.getPosition().z,
|
|
||||||
player.getVelocity().x, player.getVelocity().y, player.getVelocity().z,
|
|
||||||
player.getOrientation().x, player.getOrientation().y,
|
|
||||||
player.getLastInputState().crouching()
|
|
||||||
));
|
|
||||||
return player;
|
return player;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package nl.andrewl.aos2_server;
|
package nl.andrewl.aos2_server;
|
||||||
|
|
||||||
import nl.andrewl.aos2_server.config.ServerConfig;
|
import nl.andrewl.aos2_server.config.ServerConfig;
|
||||||
|
import nl.andrewl.aos2_server.logic.WorldUpdater;
|
||||||
import nl.andrewl.aos_core.config.Config;
|
import nl.andrewl.aos_core.config.Config;
|
||||||
import nl.andrewl.aos_core.model.world.World;
|
import nl.andrewl.aos_core.model.world.World;
|
||||||
import nl.andrewl.aos_core.model.world.Worlds;
|
import nl.andrewl.aos_core.model.world.Worlds;
|
||||||
|
@ -8,7 +9,6 @@ import nl.andrewl.aos_core.net.UdpReceiver;
|
||||||
import nl.andrewl.aos_core.net.udp.ClientInputState;
|
import nl.andrewl.aos_core.net.udp.ClientInputState;
|
||||||
import nl.andrewl.aos_core.net.udp.ClientOrientationState;
|
import nl.andrewl.aos_core.net.udp.ClientOrientationState;
|
||||||
import nl.andrewl.aos_core.net.udp.DatagramInit;
|
import nl.andrewl.aos_core.net.udp.DatagramInit;
|
||||||
import nl.andrewl.aos_core.net.udp.PlayerUpdateMessage;
|
|
||||||
import nl.andrewl.record_net.Message;
|
import nl.andrewl.record_net.Message;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
@ -66,26 +66,14 @@ public class Server implements Runnable {
|
||||||
} else if (msg instanceof ClientInputState inputState) {
|
} else if (msg instanceof ClientInputState inputState) {
|
||||||
ServerPlayer player = playerManager.getPlayer(inputState.clientId());
|
ServerPlayer player = playerManager.getPlayer(inputState.clientId());
|
||||||
if (player != null) {
|
if (player != null) {
|
||||||
player.setLastInputState(inputState);
|
player.getActionManager().setLastInputState(inputState);
|
||||||
playerManager.broadcastUdpMessage(new PlayerUpdateMessage(
|
playerManager.broadcastUdpMessage(player.getUpdateMessage());
|
||||||
player.getId(),
|
|
||||||
player.getPosition().x, player.getPosition().y, player.getPosition().z,
|
|
||||||
player.getVelocity().x, player.getVelocity().y, player.getVelocity().z,
|
|
||||||
player.getOrientation().x, player.getOrientation().y,
|
|
||||||
player.getLastInputState().crouching()
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
} else if (msg instanceof ClientOrientationState orientationState) {
|
} else if (msg instanceof ClientOrientationState orientationState) {
|
||||||
ServerPlayer player = playerManager.getPlayer(orientationState.clientId());
|
ServerPlayer player = playerManager.getPlayer(orientationState.clientId());
|
||||||
if (player != null) {
|
if (player != null) {
|
||||||
player.setOrientation(orientationState.x(), orientationState.y());
|
player.setOrientation(orientationState.x(), orientationState.y());
|
||||||
playerManager.broadcastUdpMessageToAllBut(new PlayerUpdateMessage(
|
playerManager.broadcastUdpMessageToAllBut(player.getUpdateMessage(), player);
|
||||||
player.getId(),
|
|
||||||
player.getPosition().x, player.getPosition().y, player.getPosition().z,
|
|
||||||
player.getVelocity().x, player.getVelocity().y, player.getVelocity().z,
|
|
||||||
player.getOrientation().x, player.getOrientation().y,
|
|
||||||
player.getLastInputState().crouching()
|
|
||||||
), player);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,10 @@
|
||||||
package nl.andrewl.aos2_server;
|
package nl.andrewl.aos2_server;
|
||||||
|
|
||||||
import nl.andrewl.aos2_server.config.ServerConfig;
|
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.world.World;
|
import nl.andrewl.aos_core.model.item.*;
|
||||||
import nl.andrewl.aos_core.net.udp.ChunkUpdateMessage;
|
import nl.andrewl.aos_core.model.item.gun.Rifle;
|
||||||
import nl.andrewl.aos_core.net.udp.ClientInputState;
|
import nl.andrewl.aos_core.net.udp.PlayerUpdateMessage;
|
||||||
import org.joml.Math;
|
|
||||||
import org.joml.Vector2i;
|
|
||||||
import org.joml.Vector3f;
|
|
||||||
import org.joml.Vector3i;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
@ -25,303 +21,34 @@ public class ServerPlayer extends Player {
|
||||||
public static final float WIDTH = 0.75f;
|
public static final float WIDTH = 0.75f;
|
||||||
public static final float RADIUS = WIDTH / 2f;
|
public static final float RADIUS = WIDTH / 2f;
|
||||||
|
|
||||||
private ClientInputState lastInputState;
|
private final PlayerActionManager actionManager;
|
||||||
private long lastBlockRemovedAt = 0;
|
private final Inventory inventory;
|
||||||
private long lastBlockPlacedAt = 0;
|
|
||||||
|
|
||||||
private boolean updated = false;
|
|
||||||
|
|
||||||
public ServerPlayer(int id, String username) {
|
public ServerPlayer(int id, String username) {
|
||||||
super(id, username);
|
super(id, username);
|
||||||
// Initialize with a default state of no input.
|
this.actionManager = new PlayerActionManager(this);
|
||||||
lastInputState = new ClientInputState(id, false, false, false, false, false, false, false, false, false);
|
this.inventory = new Inventory(new ArrayList<>(), 0);
|
||||||
|
inventory.getItemStacks().add(new GunItemStack(ItemTypes.get("Rifle")));
|
||||||
|
inventory.getItemStacks().add(new BlockItemStack(ItemTypes.get("Block"), 50));
|
||||||
}
|
}
|
||||||
|
|
||||||
public ClientInputState getLastInputState() {
|
public PlayerActionManager getActionManager() {
|
||||||
return lastInputState;
|
return actionManager;
|
||||||
}
|
|
||||||
|
|
||||||
public void setLastInputState(ClientInputState inputState) {
|
|
||||||
this.lastInputState = inputState;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isUpdated() {
|
|
||||||
return updated;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void tick(float dt, World world, Server server) {
|
|
||||||
long now = System.currentTimeMillis();
|
|
||||||
// Check for breaking blocks.
|
|
||||||
if (lastInputState.hitting() && now - lastBlockRemovedAt > server.getConfig().actions.blockRemoveCooldown * 1000) {
|
|
||||||
Vector3f eyePos = new Vector3f(position);
|
|
||||||
eyePos.y += getEyeHeight();
|
|
||||||
var hit = world.getLookingAtPos(eyePos, viewVector, 10);
|
|
||||||
if (hit != null) {
|
|
||||||
world.setBlockAt(hit.pos().x, hit.pos().y, hit.pos().z, (byte) 0);
|
|
||||||
lastBlockRemovedAt = now;
|
|
||||||
server.getPlayerManager().broadcastUdpMessage(ChunkUpdateMessage.fromWorld(hit.pos(), world));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Check for placing blocks.
|
|
||||||
if (lastInputState.interacting() && now - lastBlockPlacedAt > server.getConfig().actions.blockPlaceCooldown * 1000) {
|
|
||||||
Vector3f eyePos = new Vector3f(position);
|
|
||||||
eyePos.y += getEyeHeight();
|
|
||||||
var hit = world.getLookingAtPos(eyePos, viewVector, 10);
|
|
||||||
if (hit != null) {
|
|
||||||
Vector3i placePos = new Vector3i(hit.pos());
|
|
||||||
placePos.add(hit.norm());
|
|
||||||
world.setBlockAt(placePos.x, placePos.y, placePos.z, (byte) 1);
|
|
||||||
lastBlockPlacedAt = now;
|
|
||||||
server.getPlayerManager().broadcastUdpMessage(ChunkUpdateMessage.fromWorld(placePos, world));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tickMovement(dt, world, server.getConfig().physics);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void tickMovement(float dt, World world, ServerConfig.PhysicsConfig config) {
|
|
||||||
updated = false; // Reset the updated flag. This will be set to true if the player was updated in this tick.
|
|
||||||
boolean grounded = isGrounded(world);
|
|
||||||
tickHorizontalVelocity(config, grounded);
|
|
||||||
|
|
||||||
if (isGrounded(world)) {
|
|
||||||
if (lastInputState.jumping()) {
|
|
||||||
velocity.y = config.jumpVerticalSpeed * (lastInputState.sprinting() ? 1.25f : 1f);
|
|
||||||
updated = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
velocity.y -= config.gravity * dt;
|
|
||||||
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, world);
|
|
||||||
position.add(movement);
|
|
||||||
updated = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void tickHorizontalVelocity(ServerConfig.PhysicsConfig config, boolean doDeceleration) {
|
|
||||||
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 (lastInputState.forward()) acceleration.z -= 1;
|
|
||||||
if (lastInputState.backward()) acceleration.z += 1;
|
|
||||||
if (lastInputState.left()) acceleration.x -= 1;
|
|
||||||
if (lastInputState.right()) acceleration.x += 1;
|
|
||||||
if (acceleration.lengthSquared() > 0) {
|
|
||||||
acceleration.normalize();
|
|
||||||
acceleration.rotateAxis(orientation.x, 0, 1, 0);
|
|
||||||
acceleration.mul(config.movementAcceleration);
|
|
||||||
horizontalVelocity.add(acceleration);
|
|
||||||
final float maxSpeed;
|
|
||||||
if (lastInputState.crouching()) {
|
|
||||||
maxSpeed = config.crouchingSpeed;
|
|
||||||
} else if (lastInputState.sprinting()) {
|
|
||||||
maxSpeed = config.sprintingSpeed;
|
|
||||||
} else {
|
|
||||||
maxSpeed = config.walkingSpeed;
|
|
||||||
}
|
|
||||||
if (horizontalVelocity.length() > maxSpeed) {
|
|
||||||
horizontalVelocity.normalize(maxSpeed);
|
|
||||||
}
|
|
||||||
updated = true;
|
|
||||||
} else if (doDeceleration && horizontalVelocity.lengthSquared() > 0) {
|
|
||||||
Vector3f deceleration = new Vector3f(horizontalVelocity)
|
|
||||||
.negate().normalize()
|
|
||||||
.mul(Math.min(horizontalVelocity.length(), config.movementDeceleration));
|
|
||||||
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) {
|
|
||||||
// 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
|
* Helper method to build an update message for this player, to be sent to
|
||||||
* horizontal XZ plane. This can be between 1 and 4 spaces, depending on
|
* various clients.
|
||||||
* if the player's position is overlapping with a few blocks.
|
* @return The update message.
|
||||||
* @param pos The position.
|
|
||||||
* @return The list of 2d positions occupied.
|
|
||||||
*/
|
*/
|
||||||
private List<Vector2i> getHorizontalSpaceOccupied(Vector3f pos) {
|
public PlayerUpdateMessage getUpdateMessage() {
|
||||||
// Get the list of 2d x,z coordinates that we overlap with.
|
return new PlayerUpdateMessage(
|
||||||
List<Vector2i> points = new ArrayList<>(4); // Due to the size of radius, there can only be a max of 4 blocks.
|
id,
|
||||||
int minX = (int) Math.floor(pos.x - RADIUS);
|
position.x, position.y, position.z,
|
||||||
int minZ = (int) Math.floor(pos.z - RADIUS);
|
velocity.x, velocity.y, velocity.z,
|
||||||
int maxX = (int) Math.floor(pos.x + RADIUS);
|
orientation.x, orientation.y,
|
||||||
int maxZ = (int) Math.floor(pos.z + RADIUS);
|
actionManager.getLastInputState().crouching(),
|
||||||
for (int x = minX; x <= maxX; x++) {
|
inventory.getSelectedItemStack().getType().getId()
|
||||||
for (int z = minZ; z <= maxZ; z++) {
|
);
|
||||||
points.add(new Vector2i(x, z));
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return points;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkBlockCollisions(Vector3f movement, World world) {
|
|
||||||
final Vector3f nextTickPosition = new Vector3f(position).add(movement);
|
|
||||||
// System.out.printf("Pos:\t\t%.3f, %.3f, %.3f%nmov:\t\t%.3f, %.3f, %.3f%nNexttick:\t%.3f, %.3f, %.3f%n",
|
|
||||||
// position.x, position.y, position.z,
|
|
||||||
// movement.x, movement.y, movement.z,
|
|
||||||
// nextTickPosition.x, nextTickPosition.y, nextTickPosition.z
|
|
||||||
// );
|
|
||||||
float height = 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) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public float getCurrentHeight() {
|
|
||||||
return lastInputState.crouching() ? HEIGHT_CROUCH : HEIGHT;
|
|
||||||
}
|
|
||||||
|
|
||||||
public float getEyeHeight() {
|
|
||||||
return lastInputState.crouching() ? EYE_HEIGHT_CROUCH : EYE_HEIGHT;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,329 @@
|
||||||
|
package nl.andrewl.aos2_server.logic;
|
||||||
|
|
||||||
|
import nl.andrewl.aos2_server.Server;
|
||||||
|
import nl.andrewl.aos2_server.ServerPlayer;
|
||||||
|
import nl.andrewl.aos2_server.config.ServerConfig;
|
||||||
|
import nl.andrewl.aos_core.model.world.World;
|
||||||
|
import nl.andrewl.aos_core.net.udp.ChunkUpdateMessage;
|
||||||
|
import nl.andrewl.aos_core.net.udp.ClientInputState;
|
||||||
|
import org.joml.Math;
|
||||||
|
import org.joml.Vector2i;
|
||||||
|
import org.joml.Vector3f;
|
||||||
|
import org.joml.Vector3i;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static nl.andrewl.aos2_server.ServerPlayer.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component that manages a server player's current actions and movement.
|
||||||
|
*/
|
||||||
|
public class PlayerActionManager {
|
||||||
|
private final ServerPlayer player;
|
||||||
|
|
||||||
|
private ClientInputState lastInputState;
|
||||||
|
private long lastBlockRemovedAt = 0;
|
||||||
|
private long lastBlockPlacedAt = 0;
|
||||||
|
|
||||||
|
private boolean updated = false;
|
||||||
|
|
||||||
|
public PlayerActionManager(ServerPlayer player) {
|
||||||
|
this.player = player;
|
||||||
|
lastInputState = new ClientInputState(player.getId(), false, false, false, false, false, false, false, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientInputState getLastInputState() {
|
||||||
|
return lastInputState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastInputState(ClientInputState lastInputState) {
|
||||||
|
this.lastInputState = lastInputState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isUpdated() {
|
||||||
|
return updated;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void tick(float dt, World world, Server server) {
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
// Check for breaking blocks.
|
||||||
|
if (lastInputState.hitting() && now - lastBlockRemovedAt > server.getConfig().actions.blockRemoveCooldown * 1000) {
|
||||||
|
Vector3f eyePos = new Vector3f(player.getPosition());
|
||||||
|
eyePos.y += getEyeHeight();
|
||||||
|
var hit = world.getLookingAtPos(eyePos, player.getViewVector(), 10);
|
||||||
|
if (hit != null) {
|
||||||
|
world.setBlockAt(hit.pos().x, hit.pos().y, hit.pos().z, (byte) 0);
|
||||||
|
lastBlockRemovedAt = now;
|
||||||
|
server.getPlayerManager().broadcastUdpMessage(ChunkUpdateMessage.fromWorld(hit.pos(), world));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Check for placing blocks.
|
||||||
|
if (lastInputState.interacting() && now - lastBlockPlacedAt > server.getConfig().actions.blockPlaceCooldown * 1000) {
|
||||||
|
Vector3f eyePos = new Vector3f(player.getPosition());
|
||||||
|
eyePos.y += getEyeHeight();
|
||||||
|
var hit = world.getLookingAtPos(eyePos, player.getViewVector(), 10);
|
||||||
|
if (hit != null) {
|
||||||
|
Vector3i placePos = new Vector3i(hit.pos());
|
||||||
|
placePos.add(hit.norm());
|
||||||
|
world.setBlockAt(placePos.x, placePos.y, placePos.z, (byte) 1);
|
||||||
|
lastBlockPlacedAt = now;
|
||||||
|
server.getPlayerManager().broadcastUdpMessage(ChunkUpdateMessage.fromWorld(placePos, world));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tickMovement(dt, world, server.getConfig().physics);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tickMovement(float dt, World world, ServerConfig.PhysicsConfig config) {
|
||||||
|
updated = false; // Reset the updated flag. This will be set to true if the player was updated in this tick.
|
||||||
|
var velocity = player.getVelocity();
|
||||||
|
var position = player.getPosition();
|
||||||
|
boolean grounded = isGrounded(world);
|
||||||
|
tickHorizontalVelocity(config, grounded);
|
||||||
|
|
||||||
|
if (isGrounded(world)) {
|
||||||
|
if (lastInputState.jumping()) {
|
||||||
|
velocity.y = config.jumpVerticalSpeed * (lastInputState.sprinting() ? 1.25f : 1f);
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
velocity.y -= config.gravity * dt;
|
||||||
|
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, world);
|
||||||
|
position.add(movement);
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tickHorizontalVelocity(ServerConfig.PhysicsConfig config, boolean doDeceleration) {
|
||||||
|
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 (lastInputState.forward()) acceleration.z -= 1;
|
||||||
|
if (lastInputState.backward()) acceleration.z += 1;
|
||||||
|
if (lastInputState.left()) acceleration.x -= 1;
|
||||||
|
if (lastInputState.right()) acceleration.x += 1;
|
||||||
|
if (acceleration.lengthSquared() > 0) {
|
||||||
|
acceleration.normalize();
|
||||||
|
acceleration.rotateAxis(orientation.x, 0, 1, 0);
|
||||||
|
acceleration.mul(config.movementAcceleration);
|
||||||
|
horizontalVelocity.add(acceleration);
|
||||||
|
final float maxSpeed;
|
||||||
|
if (lastInputState.crouching()) {
|
||||||
|
maxSpeed = config.crouchingSpeed;
|
||||||
|
} else if (lastInputState.sprinting()) {
|
||||||
|
maxSpeed = config.sprintingSpeed;
|
||||||
|
} else {
|
||||||
|
maxSpeed = config.walkingSpeed;
|
||||||
|
}
|
||||||
|
if (horizontalVelocity.length() > maxSpeed) {
|
||||||
|
horizontalVelocity.normalize(maxSpeed);
|
||||||
|
}
|
||||||
|
updated = true;
|
||||||
|
} else if (doDeceleration && horizontalVelocity.lengthSquared() > 0) {
|
||||||
|
Vector3f deceleration = new Vector3f(horizontalVelocity)
|
||||||
|
.negate().normalize()
|
||||||
|
.mul(Math.min(horizontalVelocity.length(), config.movementDeceleration));
|
||||||
|
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, World world) {
|
||||||
|
var position = player.getPosition();
|
||||||
|
var velocity = player.getVelocity();
|
||||||
|
final Vector3f nextTickPosition = new Vector3f(position).add(movement);
|
||||||
|
// System.out.printf("Pos:\t\t%.3f, %.3f, %.3f%nmov:\t\t%.3f, %.3f, %.3f%nNexttick:\t%.3f, %.3f, %.3f%n",
|
||||||
|
// position.x, position.y, position.z,
|
||||||
|
// movement.x, movement.y, movement.z,
|
||||||
|
// nextTickPosition.x, nextTickPosition.y, nextTickPosition.z
|
||||||
|
// );
|
||||||
|
float height = 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) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getCurrentHeight() {
|
||||||
|
return lastInputState.crouching() ? HEIGHT_CROUCH : HEIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getEyeHeight() {
|
||||||
|
return lastInputState.crouching() ? EYE_HEIGHT_CROUCH : EYE_HEIGHT;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,7 @@
|
||||||
package nl.andrewl.aos2_server;
|
package nl.andrewl.aos2_server.logic;
|
||||||
|
|
||||||
import nl.andrewl.aos_core.net.udp.PlayerUpdateMessage;
|
import nl.andrewl.aos2_server.Server;
|
||||||
import org.joml.Math;
|
import org.joml.Math;
|
||||||
import org.joml.Matrix4f;
|
|
||||||
import org.joml.Vector3f;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
@ -56,14 +54,8 @@ public class WorldUpdater implements Runnable {
|
||||||
|
|
||||||
private void tick() {
|
private void tick() {
|
||||||
for (var player : server.getPlayerManager().getPlayers()) {
|
for (var player : server.getPlayerManager().getPlayers()) {
|
||||||
player.tick(secondsPerTick, server.getWorld(), server);
|
player.getActionManager().tick(secondsPerTick, server.getWorld(), server);
|
||||||
if (player.isUpdated()) server.getPlayerManager().broadcastUdpMessage(new PlayerUpdateMessage(
|
if (player.getActionManager().isUpdated()) server.getPlayerManager().broadcastUdpMessage(player.getUpdateMessage());
|
||||||
player.getId(),
|
|
||||||
player.getPosition().x, player.getPosition().y, player.getPosition().z,
|
|
||||||
player.getVelocity().x, player.getVelocity().y, player.getVelocity().z,
|
|
||||||
player.getOrientation().x, player.getOrientation().y,
|
|
||||||
player.getLastInputState().crouching()
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue