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;
 | 
			
		||||
 | 
			
		||||
import org.joml.Vector3f;
 | 
			
		||||
import org.joml.Vector3fc;
 | 
			
		||||
import org.joml.Vector3i;
 | 
			
		||||
import org.joml.Vector3ic;
 | 
			
		||||
 | 
			
		||||
public class Directions {
 | 
			
		||||
	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 NEGATIVE_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(16, SoundMessage.class);
 | 
			
		||||
		serializer.registerType(17, ProjectileMessage.class);
 | 
			
		||||
		serializer.registerType(18, ClientHealthMessage.class);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public static ExtendedDataInputStream getInputStream(InputStream in) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,8 +1,13 @@
 | 
			
		|||
package nl.andrewl.aos_core.model;
 | 
			
		||||
 | 
			
		||||
import nl.andrewl.aos_core.MathUtils;
 | 
			
		||||
import org.joml.Math;
 | 
			
		||||
import org.joml.Vector2f;
 | 
			
		||||
import org.joml.Vector3f;
 | 
			
		||||
import org.joml.Vector3i;
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
import static org.joml.Math.*;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -162,4 +167,35 @@ public class Player {
 | 
			
		|||
	public float getCurrentHeight() {
 | 
			
		||||
		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;
 | 
			
		||||
 | 
			
		||||
import org.joml.Vector3f;
 | 
			
		||||
import org.joml.Vector3i;
 | 
			
		||||
import org.joml.Vector3ic;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -9,5 +10,6 @@ import org.joml.Vector3ic;
 | 
			
		|||
 */
 | 
			
		||||
public record Hit (
 | 
			
		||||
		Vector3i pos,
 | 
			
		||||
		Vector3ic norm
 | 
			
		||||
		Vector3ic norm,
 | 
			
		||||
		Vector3f rawPos
 | 
			
		||||
) {}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,7 +17,7 @@ import java.util.Map;
 | 
			
		|||
 * that players can interact in.
 | 
			
		||||
 */
 | 
			
		||||
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 ColorPalette palette;
 | 
			
		||||
| 
						 | 
				
			
			@ -160,7 +160,7 @@ public class World {
 | 
			
		|||
					System.err.println("Invalid hit!");
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				return new Hit(hitPos, hitNorm);
 | 
			
		||||
				return new Hit(hitPos, hitNorm, pos);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		return null;
 | 
			
		||||
| 
						 | 
				
			
			@ -189,7 +189,7 @@ public class World {
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	private static float factorToNextValue(float n, float dir) {
 | 
			
		||||
		if (dir == 0) return 0;
 | 
			
		||||
		if (dir == 0) return Float.MAX_VALUE;
 | 
			
		||||
		float nextValue;
 | 
			
		||||
		if (dir > 0) {
 | 
			
		||||
			nextValue = (Math.ceil(n) == n) ? n + 1 : Math.ceil(n);
 | 
			
		||||
| 
						 | 
				
			
			@ -197,6 +197,11 @@ public class World {
 | 
			
		|||
			nextValue = Math.floor(n) - DELTA;
 | 
			
		||||
		}
 | 
			
		||||
		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);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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.aos_core.Net;
 | 
			
		||||
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.PlayerLeaveMessage;
 | 
			
		||||
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));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * 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) {
 | 
			
		||||
		var handler = getHandler(init.clientId());
 | 
			
		||||
		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.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.world.Hit;
 | 
			
		||||
import nl.andrewl.aos_core.net.world.ChunkUpdateMessage;
 | 
			
		||||
import org.joml.Matrix4f;
 | 
			
		||||
import org.joml.Vector3f;
 | 
			
		||||
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
| 
						 | 
				
			
			@ -12,7 +15,13 @@ import java.util.LinkedList;
 | 
			
		|||
import java.util.Map;
 | 
			
		||||
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 static final float MOVEMENT_FACTOR = 1f;
 | 
			
		||||
 | 
			
		||||
	private final Server server;
 | 
			
		||||
	private int nextProjectileId = 1;
 | 
			
		||||
	private final Map<Integer, ServerProjectile> projectiles;
 | 
			
		||||
| 
						 | 
				
			
			@ -27,8 +36,15 @@ public class ProjectileManager {
 | 
			
		|||
	public void spawnBullet(ServerPlayer player) {
 | 
			
		||||
		int id = nextProjectileId++;
 | 
			
		||||
		if (nextProjectileId == Integer.MAX_VALUE) nextProjectileId = 1;
 | 
			
		||||
		Vector3f pos = new Vector3f(player.getEyePosition());
 | 
			
		||||
		Vector3f vel = new Vector3f(player.getViewVector()).normalize().mul(300);
 | 
			
		||||
		Vector3f pos = new Vector3f();
 | 
			
		||||
		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);
 | 
			
		||||
		projectiles.put(bullet.getId(), bullet);
 | 
			
		||||
		server.getPlayerManager().broadcastUdpMessage(bullet.toMessage(false));
 | 
			
		||||
| 
						 | 
				
			
			@ -45,21 +61,82 @@ public class ProjectileManager {
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	private void tickProjectile(ServerProjectile projectile, float dt) {
 | 
			
		||||
		projectile.getVelocity().y -= server.getConfig().physics.gravity * dt;
 | 
			
		||||
		// TODO: Check if bullet will hit anything, like blocks or players, if it follows current velocity.
 | 
			
		||||
		projectile.getVelocity().y -= server.getConfig().physics.gravity * dt * MOVEMENT_FACTOR;
 | 
			
		||||
 | 
			
		||||
		// Check for if the bullet will move close enough to a player to hit them.
 | 
			
		||||
		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();
 | 
			
		||||
//		Hit hit = server.getWorld().getLookingAtPos(projectile.getPosition(), movementDir, movement.length());
 | 
			
		||||
		projectile.getPosition().add(movement);
 | 
			
		||||
		if (projectile.getDistanceTravelled() > 500) {
 | 
			
		||||
//			if (hit != null) {
 | 
			
		||||
//				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));
 | 
			
		||||
		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 {
 | 
			
		||||
			server.getPlayerManager().broadcastUdpMessage(projectile.toMessage(false));
 | 
			
		||||
			// Bullet struck nothing.
 | 
			
		||||
			projectile.getPosition().add(movement);
 | 
			
		||||
			if (projectile.getDistanceTravelled() > 500) {
 | 
			
		||||
				deleteProjectile(projectile);
 | 
			
		||||
			} else {
 | 
			
		||||
				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 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) {
 | 
			
		||||
		super(id, username);
 | 
			
		||||
		this.inventory = new Inventory(new ArrayList<>(), 0);
 | 
			
		||||
		this.health = 1f;
 | 
			
		||||
		this.actionManager = new PlayerActionManager(this);
 | 
			
		||||
		inventory.getItemStacks().add(new GunItemStack(ItemTypes.RIFLE));
 | 
			
		||||
		inventory.getItemStacks().add(new BlockItemStack(ItemTypes.BLOCK, 50, (byte) 1));
 | 
			
		||||
| 
						 | 
				
			
			@ -39,6 +46,16 @@ public class ServerPlayer extends Player {
 | 
			
		|||
		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
 | 
			
		||||
	 * various clients.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue