Added primitive player health and killing system.
This commit is contained in:
parent
4ef355f707
commit
e1456707dd
|
@ -1,10 +1,13 @@
|
||||||
package nl.andrewl.aos_core;
|
package nl.andrewl.aos_core;
|
||||||
|
|
||||||
|
import org.joml.Vector3f;
|
||||||
|
import org.joml.Vector3fc;
|
||||||
import org.joml.Vector3i;
|
import org.joml.Vector3i;
|
||||||
import org.joml.Vector3ic;
|
import org.joml.Vector3ic;
|
||||||
|
|
||||||
public class Directions {
|
public class Directions {
|
||||||
public static final Vector3ic UP = new Vector3i(0, 1, 0);
|
public static final Vector3ic UP = new Vector3i(0, 1, 0);
|
||||||
|
public static final Vector3fc UPf = new Vector3f(0, 1, 0);
|
||||||
public static final Vector3ic DOWN = new Vector3i(0, -1, 0);
|
public static final Vector3ic DOWN = new Vector3i(0, -1, 0);
|
||||||
public static final Vector3ic NEGATIVE_X = new Vector3i(-1, 0, 0);
|
public static final Vector3ic NEGATIVE_X = new Vector3i(-1, 0, 0);
|
||||||
public static final Vector3ic POSITIVE_X = new Vector3i(1, 0, 0);
|
public static final Vector3ic POSITIVE_X = new Vector3i(1, 0, 0);
|
||||||
|
|
|
@ -44,6 +44,7 @@ public final class Net {
|
||||||
serializer.registerType(15, InventorySelectedStackMessage.class);
|
serializer.registerType(15, InventorySelectedStackMessage.class);
|
||||||
serializer.registerType(16, SoundMessage.class);
|
serializer.registerType(16, SoundMessage.class);
|
||||||
serializer.registerType(17, ProjectileMessage.class);
|
serializer.registerType(17, ProjectileMessage.class);
|
||||||
|
serializer.registerType(18, ClientHealthMessage.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ExtendedDataInputStream getInputStream(InputStream in) {
|
public static ExtendedDataInputStream getInputStream(InputStream in) {
|
||||||
|
|
|
@ -1,8 +1,13 @@
|
||||||
package nl.andrewl.aos_core.model;
|
package nl.andrewl.aos_core.model;
|
||||||
|
|
||||||
import nl.andrewl.aos_core.MathUtils;
|
import nl.andrewl.aos_core.MathUtils;
|
||||||
|
import org.joml.Math;
|
||||||
import org.joml.Vector2f;
|
import org.joml.Vector2f;
|
||||||
import org.joml.Vector3f;
|
import org.joml.Vector3f;
|
||||||
|
import org.joml.Vector3i;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import static org.joml.Math.*;
|
import static org.joml.Math.*;
|
||||||
|
|
||||||
|
@ -162,4 +167,35 @@ public class Player {
|
||||||
public float getCurrentHeight() {
|
public float getCurrentHeight() {
|
||||||
return crouching ? HEIGHT_CROUCH : HEIGHT;
|
return crouching ? HEIGHT_CROUCH : HEIGHT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<Vector3i> getBlockSpaceOccupied() {
|
||||||
|
float playerBodyMinZ = position.z - RADIUS;
|
||||||
|
float playerBodyMaxZ = position.z + RADIUS;
|
||||||
|
float playerBodyMinX = position.x - RADIUS;
|
||||||
|
float playerBodyMaxX = position.x + RADIUS;
|
||||||
|
float playerBodyMinY = position.y;
|
||||||
|
float playerBodyMaxY = position.y + getCurrentHeight();
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
|
||||||
|
List<Vector3i> vectors = new ArrayList<>(8);
|
||||||
|
for (int x = minX; x <= maxX; x++) {
|
||||||
|
for (int y = minY; y <= maxY; y++) {
|
||||||
|
for (int z = minZ; z <= maxZ; z++) {
|
||||||
|
vectors.add(new Vector3i(x, y, z));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return vectors;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSpaceOccupied(Vector3i pos) {
|
||||||
|
return getBlockSpaceOccupied().contains(pos);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package nl.andrewl.aos_core.model.world;
|
package nl.andrewl.aos_core.model.world;
|
||||||
|
|
||||||
|
import org.joml.Vector3f;
|
||||||
import org.joml.Vector3i;
|
import org.joml.Vector3i;
|
||||||
import org.joml.Vector3ic;
|
import org.joml.Vector3ic;
|
||||||
|
|
||||||
|
@ -9,5 +10,6 @@ import org.joml.Vector3ic;
|
||||||
*/
|
*/
|
||||||
public record Hit (
|
public record Hit (
|
||||||
Vector3i pos,
|
Vector3i pos,
|
||||||
Vector3ic norm
|
Vector3ic norm,
|
||||||
|
Vector3f rawPos
|
||||||
) {}
|
) {}
|
||||||
|
|
|
@ -17,7 +17,7 @@ import java.util.Map;
|
||||||
* that players can interact in.
|
* that players can interact in.
|
||||||
*/
|
*/
|
||||||
public class World {
|
public class World {
|
||||||
private static final float DELTA = 0.00001f;
|
private static final float DELTA = 0.0001f;
|
||||||
|
|
||||||
protected final Map<Vector3ic, Chunk> chunkMap = new HashMap<>();
|
protected final Map<Vector3ic, Chunk> chunkMap = new HashMap<>();
|
||||||
protected ColorPalette palette;
|
protected ColorPalette palette;
|
||||||
|
@ -160,7 +160,7 @@ public class World {
|
||||||
System.err.println("Invalid hit!");
|
System.err.println("Invalid hit!");
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Hit(hitPos, hitNorm);
|
return new Hit(hitPos, hitNorm, pos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
@ -189,7 +189,7 @@ public class World {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static float factorToNextValue(float n, float dir) {
|
private static float factorToNextValue(float n, float dir) {
|
||||||
if (dir == 0) return 0;
|
if (dir == 0) return Float.MAX_VALUE;
|
||||||
float nextValue;
|
float nextValue;
|
||||||
if (dir > 0) {
|
if (dir > 0) {
|
||||||
nextValue = (Math.ceil(n) == n) ? n + 1 : Math.ceil(n);
|
nextValue = (Math.ceil(n) == n) ? n + 1 : Math.ceil(n);
|
||||||
|
@ -197,6 +197,11 @@ public class World {
|
||||||
nextValue = Math.floor(n) - DELTA;
|
nextValue = Math.floor(n) - DELTA;
|
||||||
}
|
}
|
||||||
float diff = nextValue - n;
|
float diff = nextValue - n;
|
||||||
|
// Testing code!
|
||||||
|
if (diff == 0) {
|
||||||
|
System.out.printf("n = %.8f, nextValue = %.8f, floor(n) - DELTA = %.8f%n", n, nextValue, Math.floor(n) - DELTA);
|
||||||
|
throw new RuntimeException("EEK");
|
||||||
|
}
|
||||||
return Math.abs(diff / dir);
|
return Math.abs(diff / dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
package nl.andrewl.aos_core.net.client;
|
||||||
|
|
||||||
|
import nl.andrewl.record_net.Message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A message that's sent to update a client with their player's latest health
|
||||||
|
* information.
|
||||||
|
* @param health The player's health.
|
||||||
|
*/
|
||||||
|
public record ClientHealthMessage(
|
||||||
|
float health
|
||||||
|
) implements Message {}
|
|
@ -3,6 +3,7 @@ package nl.andrewl.aos2_server;
|
||||||
import nl.andrewl.aos2_server.model.ServerPlayer;
|
import nl.andrewl.aos2_server.model.ServerPlayer;
|
||||||
import nl.andrewl.aos_core.Net;
|
import nl.andrewl.aos_core.Net;
|
||||||
import nl.andrewl.aos_core.model.Team;
|
import nl.andrewl.aos_core.model.Team;
|
||||||
|
import nl.andrewl.aos_core.net.client.ClientHealthMessage;
|
||||||
import nl.andrewl.aos_core.net.client.PlayerJoinMessage;
|
import nl.andrewl.aos_core.net.client.PlayerJoinMessage;
|
||||||
import nl.andrewl.aos_core.net.client.PlayerLeaveMessage;
|
import nl.andrewl.aos_core.net.client.PlayerLeaveMessage;
|
||||||
import nl.andrewl.aos_core.net.connect.DatagramInit;
|
import nl.andrewl.aos_core.net.connect.DatagramInit;
|
||||||
|
@ -121,6 +122,21 @@ public class PlayerManager {
|
||||||
return server.getWorld().getSpawnPoints().values().stream().findAny().orElse(new Vector3f(0, 0, 0));
|
return server.getWorld().getSpawnPoints().values().stream().findAny().orElse(new Vector3f(0, 0, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is invoked by the server's logic if a player has been
|
||||||
|
* determined to be killed somehow. We will reset their inventory, health,
|
||||||
|
* and respawn them.
|
||||||
|
* @param player The player that died.
|
||||||
|
*/
|
||||||
|
public void playerKilled(ServerPlayer player) {
|
||||||
|
player.setPosition(getBestSpawnPoint(player));
|
||||||
|
player.setVelocity(new Vector3f(0));
|
||||||
|
player.setHealth(1);
|
||||||
|
getHandler(player.getId()).sendDatagramPacket(new ClientHealthMessage(player.getHealth()));
|
||||||
|
broadcastUdpMessage(player.getUpdateMessage(System.currentTimeMillis()));
|
||||||
|
// TODO: Team points or something.
|
||||||
|
}
|
||||||
|
|
||||||
public void handleUdpInit(DatagramInit init, DatagramPacket packet) {
|
public void handleUdpInit(DatagramInit init, DatagramPacket packet) {
|
||||||
var handler = getHandler(init.clientId());
|
var handler = getHandler(init.clientId());
|
||||||
if (handler != null) {
|
if (handler != null) {
|
||||||
|
|
|
@ -2,9 +2,12 @@ package nl.andrewl.aos2_server;
|
||||||
|
|
||||||
import nl.andrewl.aos2_server.model.ServerPlayer;
|
import nl.andrewl.aos2_server.model.ServerPlayer;
|
||||||
import nl.andrewl.aos2_server.model.ServerProjectile;
|
import nl.andrewl.aos2_server.model.ServerProjectile;
|
||||||
|
import nl.andrewl.aos_core.Directions;
|
||||||
|
import nl.andrewl.aos_core.model.Player;
|
||||||
import nl.andrewl.aos_core.model.Projectile;
|
import nl.andrewl.aos_core.model.Projectile;
|
||||||
import nl.andrewl.aos_core.model.world.Hit;
|
import nl.andrewl.aos_core.model.world.Hit;
|
||||||
import nl.andrewl.aos_core.net.world.ChunkUpdateMessage;
|
import nl.andrewl.aos_core.net.world.ChunkUpdateMessage;
|
||||||
|
import org.joml.Matrix4f;
|
||||||
import org.joml.Vector3f;
|
import org.joml.Vector3f;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
@ -12,7 +15,13 @@ import java.util.LinkedList;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component that manages the set of all active projectiles in the world, and
|
||||||
|
* performs tick updates for them.
|
||||||
|
*/
|
||||||
public class ProjectileManager {
|
public class ProjectileManager {
|
||||||
|
public static final float MOVEMENT_FACTOR = 1f;
|
||||||
|
|
||||||
private final Server server;
|
private final Server server;
|
||||||
private int nextProjectileId = 1;
|
private int nextProjectileId = 1;
|
||||||
private final Map<Integer, ServerProjectile> projectiles;
|
private final Map<Integer, ServerProjectile> projectiles;
|
||||||
|
@ -27,8 +36,15 @@ public class ProjectileManager {
|
||||||
public void spawnBullet(ServerPlayer player) {
|
public void spawnBullet(ServerPlayer player) {
|
||||||
int id = nextProjectileId++;
|
int id = nextProjectileId++;
|
||||||
if (nextProjectileId == Integer.MAX_VALUE) nextProjectileId = 1;
|
if (nextProjectileId == Integer.MAX_VALUE) nextProjectileId = 1;
|
||||||
Vector3f pos = new Vector3f(player.getEyePosition());
|
Vector3f pos = new Vector3f();
|
||||||
Vector3f vel = new Vector3f(player.getViewVector()).normalize().mul(300);
|
Matrix4f bulletTransform = new Matrix4f()
|
||||||
|
.translate(player.getEyePosition())
|
||||||
|
.rotate(player.getOrientation().x + (float) Math.PI, Directions.UPf)
|
||||||
|
.translate(-0.35f, -0.4f, 0.35f);
|
||||||
|
bulletTransform.transformPosition(pos);
|
||||||
|
Vector3f vel = new Vector3f(player.getViewVector()).normalize()
|
||||||
|
.mul(200 * MOVEMENT_FACTOR)
|
||||||
|
.add(player.getVelocity());
|
||||||
ServerProjectile bullet = new ServerProjectile(id, pos, vel, Projectile.Type.BULLET, player);
|
ServerProjectile bullet = new ServerProjectile(id, pos, vel, Projectile.Type.BULLET, player);
|
||||||
projectiles.put(bullet.getId(), bullet);
|
projectiles.put(bullet.getId(), bullet);
|
||||||
server.getPlayerManager().broadcastUdpMessage(bullet.toMessage(false));
|
server.getPlayerManager().broadcastUdpMessage(bullet.toMessage(false));
|
||||||
|
@ -45,21 +61,82 @@ public class ProjectileManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void tickProjectile(ServerProjectile projectile, float dt) {
|
private void tickProjectile(ServerProjectile projectile, float dt) {
|
||||||
projectile.getVelocity().y -= server.getConfig().physics.gravity * dt;
|
projectile.getVelocity().y -= server.getConfig().physics.gravity * dt * MOVEMENT_FACTOR;
|
||||||
// TODO: Check if bullet will hit anything, like blocks or players, if it follows current velocity.
|
|
||||||
|
// Check for if the bullet will move close enough to a player to hit them.
|
||||||
Vector3f movement = new Vector3f(projectile.getVelocity()).mul(dt);
|
Vector3f movement = new Vector3f(projectile.getVelocity()).mul(dt);
|
||||||
|
|
||||||
|
// Check first to see if we'll hit a player this tick.
|
||||||
|
Vector3f testPos = new Vector3f();
|
||||||
|
Vector3f testMovement = new Vector3f(movement).normalize(0.1f);
|
||||||
|
Vector3f playerHit = null;
|
||||||
|
ServerPlayer hitPlayer = null;
|
||||||
|
int playerHitType = -1;
|
||||||
|
for (ServerPlayer player : server.getPlayerManager().getPlayers()) {
|
||||||
|
// Don't allow players to shoot themselves.
|
||||||
|
if (projectile.getPlayer() != null && projectile.getPlayer().equals(player)) continue;
|
||||||
|
|
||||||
|
Vector3f headPos = player.getEyePosition();
|
||||||
|
Vector3f bodyPos = new Vector3f(player.getPosition());
|
||||||
|
bodyPos.y += 1.0f;
|
||||||
|
|
||||||
|
// Do a really shitty collision detection... check in 10cm increments if we're close to the player.
|
||||||
|
// TODO: Come up with a better collision system.
|
||||||
|
testPos.set(projectile.getPosition());
|
||||||
|
while (testPos.distanceSquared(projectile.getPosition()) < movement.lengthSquared() && playerHit == null) {
|
||||||
|
if (testPos.distanceSquared(headPos) < Player.RADIUS * Player.RADIUS) {
|
||||||
|
playerHitType = 1;
|
||||||
|
playerHit = new Vector3f(testPos);
|
||||||
|
hitPlayer = player;
|
||||||
|
} else if (testPos.distanceSquared(bodyPos) < Player.RADIUS * Player.RADIUS) {
|
||||||
|
playerHitType = 2;
|
||||||
|
playerHit = new Vector3f(testPos);
|
||||||
|
hitPlayer = player;
|
||||||
|
}
|
||||||
|
testPos.add(testMovement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then check to see if we'll hit the world during this tick.
|
||||||
Vector3f movementDir = new Vector3f(movement).normalize();
|
Vector3f movementDir = new Vector3f(movement).normalize();
|
||||||
// Hit hit = server.getWorld().getLookingAtPos(projectile.getPosition(), movementDir, movement.length());
|
Hit hit = server.getWorld().getLookingAtPos(projectile.getPosition(), movementDir, movement.length());
|
||||||
|
|
||||||
|
float playerHitDist = Float.MAX_VALUE;
|
||||||
|
if (playerHit != null) playerHitDist = projectile.getPosition().distanceSquared(playerHit);
|
||||||
|
float worldHitDist = Float.MAX_VALUE;
|
||||||
|
if (hit != null) worldHitDist = projectile.getPosition().distanceSquared(hit.rawPos());
|
||||||
|
|
||||||
|
// If we hit the world before the player,
|
||||||
|
if (hit != null && (playerHit == null || worldHitDist < playerHitDist)) {
|
||||||
|
// Bullet struck the world first.
|
||||||
|
server.getWorld().setBlockAt(hit.pos().x, hit.pos().y, hit.pos().z, (byte) 0);
|
||||||
|
server.getPlayerManager().broadcastUdpMessage(ChunkUpdateMessage.fromWorld(hit.pos(), server.getWorld()));
|
||||||
|
deleteProjectile(projectile);
|
||||||
|
} else if (playerHit != null && (hit == null || playerHitDist < worldHitDist)) {
|
||||||
|
// Bullet struck the player first.
|
||||||
|
System.out.println("Player hit: " + playerHitType);
|
||||||
|
float damage = 0.4f;
|
||||||
|
if (playerHitType == 1) damage *= 2;
|
||||||
|
hitPlayer.setHealth(hitPlayer.getHealth() - damage);
|
||||||
|
System.out.println(hitPlayer.getHealth());
|
||||||
|
if (hitPlayer.getHealth() == 0) {
|
||||||
|
System.out.println("Player killed!!!");
|
||||||
|
server.getPlayerManager().playerKilled(hitPlayer);
|
||||||
|
}
|
||||||
|
deleteProjectile(projectile);
|
||||||
|
} else {
|
||||||
|
// Bullet struck nothing.
|
||||||
projectile.getPosition().add(movement);
|
projectile.getPosition().add(movement);
|
||||||
if (projectile.getDistanceTravelled() > 500) {
|
if (projectile.getDistanceTravelled() > 500) {
|
||||||
// if (hit != null) {
|
deleteProjectile(projectile);
|
||||||
// server.getWorld().setBlockAt(hit.pos().x, hit.pos().y, hit.pos().z, (byte) 0);
|
|
||||||
// server.getPlayerManager().broadcastUdpMessage(ChunkUpdateMessage.fromWorld(hit.pos(), server.getWorld()));
|
|
||||||
// }
|
|
||||||
removalQueue.add(projectile);
|
|
||||||
server.getPlayerManager().broadcastUdpMessage(projectile.toMessage(true));
|
|
||||||
} else {
|
} else {
|
||||||
server.getPlayerManager().broadcastUdpMessage(projectile.toMessage(false));
|
server.getPlayerManager().broadcastUdpMessage(projectile.toMessage(false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteProjectile(ServerProjectile p) {
|
||||||
|
removalQueue.add(p);
|
||||||
|
server.getPlayerManager().broadcastUdpMessage(p.toMessage(true));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,9 +22,16 @@ public class ServerPlayer extends Player {
|
||||||
private final PlayerActionManager actionManager;
|
private final PlayerActionManager actionManager;
|
||||||
private final Inventory inventory;
|
private final Inventory inventory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The player's health, from 0 to 1, where <= 0 means death, and 1 means
|
||||||
|
* full health.
|
||||||
|
*/
|
||||||
|
private float health;
|
||||||
|
|
||||||
public ServerPlayer(int id, String username) {
|
public ServerPlayer(int id, String username) {
|
||||||
super(id, username);
|
super(id, username);
|
||||||
this.inventory = new Inventory(new ArrayList<>(), 0);
|
this.inventory = new Inventory(new ArrayList<>(), 0);
|
||||||
|
this.health = 1f;
|
||||||
this.actionManager = new PlayerActionManager(this);
|
this.actionManager = new PlayerActionManager(this);
|
||||||
inventory.getItemStacks().add(new GunItemStack(ItemTypes.RIFLE));
|
inventory.getItemStacks().add(new GunItemStack(ItemTypes.RIFLE));
|
||||||
inventory.getItemStacks().add(new BlockItemStack(ItemTypes.BLOCK, 50, (byte) 1));
|
inventory.getItemStacks().add(new BlockItemStack(ItemTypes.BLOCK, 50, (byte) 1));
|
||||||
|
@ -39,6 +46,16 @@ public class ServerPlayer extends Player {
|
||||||
return inventory;
|
return inventory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public float getHealth() {
|
||||||
|
return health;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setHealth(float health) {
|
||||||
|
if (health > 1) health = 1;
|
||||||
|
if (health < 0) health = 0;
|
||||||
|
this.health = health;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper method to build an update message for this player, to be sent to
|
* Helper method to build an update message for this player, to be sent to
|
||||||
* various clients.
|
* various clients.
|
||||||
|
|
Loading…
Reference in New Issue