ace-of-shades-2/server/src/main/java/nl/andrewl/aos2_server/ProjectileManager.java

166 lines
6.5 KiB
Java

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.item.Gun;
import nl.andrewl.aos_core.model.world.Hit;
import nl.andrewl.aos_core.net.client.ClientHealthMessage;
import nl.andrewl.aos_core.net.client.SoundMessage;
import nl.andrewl.aos_core.net.world.ChunkUpdateMessage;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
/**
* 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;
private final Queue<ServerProjectile> removalQueue;
public ProjectileManager(Server server) {
this.server = server;
this.projectiles = new HashMap<>();
this.removalQueue = new LinkedList<>();
}
public void spawnBullets(ServerPlayer player, Gun gun) {
Random rand = ThreadLocalRandom.current();
Vector3f pos = new Vector3f();
Vector3f direction = new Vector3f();
Matrix4f bulletTransform = new Matrix4f();
for (int i = 0; i < gun.getBulletsPerRound(); i++) {
int id = nextProjectileId++;
if (nextProjectileId == Integer.MAX_VALUE) nextProjectileId = 1;
pos.set(0);
bulletTransform.identity()
.translate(player.getEyePosition())
.rotate(player.getOrientation().x + (float) Math.PI, Directions.UPf)
.translate(-0.35f, -0.4f, 0.35f);
bulletTransform.transformPosition(pos);
direction.set(player.getViewVector()).normalize();
float accuracy = gun.getAccuracy();
accuracy -= server.getConfig().actions.movementAccuracyDecreaseFactor * player.getVelocity().length();
float perturbationFactor = (1 - accuracy) / 8;
direction.x += rand.nextGaussian(0, perturbationFactor);
direction.y += rand.nextGaussian(0, perturbationFactor);
direction.z += rand.nextGaussian(0, perturbationFactor);
Vector3f vel = new Vector3f(direction).normalize()
.mul(200 * MOVEMENT_FACTOR)
.add(player.getVelocity());
ServerProjectile bullet = new ServerProjectile(id, new Vector3f(pos), vel, Projectile.Type.BULLET, player);
projectiles.put(bullet.getId(), bullet);
server.getPlayerManager().broadcastUdpMessage(bullet.toMessage(false));
}
}
public void tick(float dt) {
for (var projectile : projectiles.values()) {
tickProjectile(projectile, dt);
}
while (!removalQueue.isEmpty()) {
ServerProjectile projectile = removalQueue.remove();
projectiles.remove(projectile.getId());
}
}
private void tickProjectile(ServerProjectile projectile, float dt) {
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());
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()));
int soundVariant = ThreadLocalRandom.current().nextInt(1, 6);
server.getPlayerManager().broadcastUdpMessage(new SoundMessage("bullet_impact_" + soundVariant, 1, hit.rawPos()));
deleteProjectile(projectile);
} else if (playerHit != null && (hit == null || playerHitDist < worldHitDist)) {
// Bullet struck the player first.
float damage = 0.4f;
if (playerHitType == 1) damage *= 2;
hitPlayer.setHealth(hitPlayer.getHealth() - damage);
int soundVariant = ThreadLocalRandom.current().nextInt(1, 4);
server.getPlayerManager().broadcastUdpMessage(new SoundMessage("hurt_" + soundVariant, 1, hitPlayer.getPosition(), hitPlayer.getVelocity()));
if (hitPlayer.getHealth() == 0) {
System.out.println("Player killed!!!");
server.getPlayerManager().playerKilled(hitPlayer);
} else {
server.getPlayerManager().getHandler(hitPlayer).sendDatagramPacket(new ClientHealthMessage(hitPlayer.getHealth()));
}
deleteProjectile(projectile);
} else {
// 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));
}
}