diff --git a/core/src/main/java/nl/andrewl/aos_core/model/PlayerMode.java b/core/src/main/java/nl/andrewl/aos_core/model/PlayerMode.java
new file mode 100644
index 0000000..46bc71c
--- /dev/null
+++ b/core/src/main/java/nl/andrewl/aos_core/model/PlayerMode.java
@@ -0,0 +1,27 @@
+package nl.andrewl.aos_core.model;
+
+/**
+ * Represents the different modes that a player can be in.
+ *
+ * -
+ * In normal mode, the player acts as a usual competitive player that
+ * has an inventory, can shoot weapons, and must traverse the world by
+ * walking around.
+ *
+ * -
+ * In creative mode, the player can fly, but still collides with the
+ * world's objects. The player also has unlimited ammunition and
+ * blocks.
+ *
+ * -
+ * In spectator mode, the player can fly freely throughout the world,
+ * limited by nothing. The player can't interact with the world in any
+ * way other than simply observing it through sight and sound.
+ *
+ *
+ */
+public enum PlayerMode {
+ NORMAL,
+ CREATIVE,
+ SPECTATOR
+}
diff --git a/server/src/main/java/nl/andrewl/aos2_server/ProjectileManager.java b/server/src/main/java/nl/andrewl/aos2_server/ProjectileManager.java
index 7b95d6f..6b59bf5 100644
--- a/server/src/main/java/nl/andrewl/aos2_server/ProjectileManager.java
+++ b/server/src/main/java/nl/andrewl/aos2_server/ProjectileManager.java
@@ -13,6 +13,7 @@ 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 org.joml.Vector3fc;
import org.joml.Vector3i;
import java.util.*;
@@ -103,10 +104,9 @@ public class ProjectileManager {
// Check for if the bullet will move close enough to a player to hit them.
Vector3f movement = new Vector3f(projectile.getVelocity()).mul(dt);
+ Vector3f direction = new Vector3f(projectile.getVelocity()).normalize();
// 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;
@@ -121,23 +121,25 @@ public class ProjectileManager {
) continue;
Vector3f headPos = player.getEyePosition();
- Vector3f bodyPos = new Vector3f(player.getPosition());
- bodyPos.y += 1.0f;
+ float headRadius = Player.RADIUS;
+ Vector3f bodyPos = new Vector3f(player.getPosition().x, player.getPosition().y + 1, player.getPosition().z);
+ float bodyRadius = Player.RADIUS;
- // 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);
+ Vector3f headIntersect = checkSphereIntersection(projectile.getPosition(), direction, headPos, headRadius);
+ if (headIntersect != null) {
+ playerHitType = 1;
+ playerHit = headIntersect;
+ hitPlayer = player;
+ break;
+ }
+
+ // Check body intersection.
+ Vector3f bodyIntersect = checkSphereIntersection(projectile.getPosition(), direction, bodyPos, bodyRadius);
+ if (bodyIntersect != null) {
+ playerHitType = 2;
+ playerHit = bodyIntersect;
+ hitPlayer = player;
+ break;
}
}
@@ -168,6 +170,37 @@ public class ProjectileManager {
}
}
+ /**
+ * Checks for and returns the point at which a ray with origin p and
+ * normalized direction d intersects with a sphere whose center is c, and
+ * whose radius is r.
+ * @param p The ray origin.
+ * @param d The ray's normalized direction.
+ * @param c The sphere's center.
+ * @param r The radius of the sphere.
+ * @return The point of intersection, if one exists.
+ */
+ private Vector3f checkSphereIntersection(Vector3fc p, Vector3fc d, Vector3fc c, float r) {
+ Vector3f vpc = new Vector3f(c).sub(p);
+ if (vpc.dot(d) >= 0) {// Only check for intersections ahead of us.
+ Vector3f puv = new Vector3f(d).mul(d.dot(vpc) / d.length());
+ Vector3f pc = new Vector3f(p).add(puv);
+ Vector3f diff = new Vector3f(c).sub(pc);
+ if (diff.length() <= r) {
+ Vector3f pcToC = new Vector3f(pc).sub(c);
+ float dist = (float) Math.sqrt(r * r - pcToC.lengthSquared());
+ float distanceToFirstIntersection;
+ if (vpc.length() > r) {
+ distanceToFirstIntersection = pcToC.length() - dist;
+ } else {
+ distanceToFirstIntersection = pcToC.length() + dist;
+ }
+ return new Vector3f(d).mul(distanceToFirstIntersection).add(p);
+ }
+ }
+ return null;
+ }
+
private void handleProjectileBlockHit(Hit hit, ServerProjectile projectile, long now) {
if (!server.getTeamManager().isProtected(hit.pos())) {
Gun gun = (Gun) projectile.getSourceItem();
diff --git a/server/src/main/java/nl/andrewl/aos2_server/logic/PlayerActionManager.java b/server/src/main/java/nl/andrewl/aos2_server/logic/PlayerActionManager.java
index 18010e6..e683f3a 100644
--- a/server/src/main/java/nl/andrewl/aos2_server/logic/PlayerActionManager.java
+++ b/server/src/main/java/nl/andrewl/aos2_server/logic/PlayerActionManager.java
@@ -102,7 +102,7 @@ public class PlayerActionManager {
}
tickMovement(dt, server, world, server.getConfig().physics);
- input.reset();
+ input.reset(); // Reset our input state after processing this tick's player input.
}
private void tickGunAction(long now, Server server, GunItemStack g) {
@@ -115,58 +115,17 @@ public class PlayerActionManager {
(gun.isAutomatic() || !gunNeedsReCock) &&
!server.getTeamManager().isProtected(player) // Don't allow players to shoot from within their own team's protected zones.
) {
- server.getProjectileManager().spawnBullets(player, gun);
- g.setBulletCount(g.getBulletCount() - 1);
- gunLastShotAt = now;
- if (!gun.isAutomatic()) {
- gunNeedsReCock = true;
- }
- server.getPlayerManager().getHandler(player.getId()).sendDatagramPacket(new ItemStackMessage(player.getInventory()));
- // Apply recoil!
- float recoilFactor = 10f; // Maximum number of degrees to recoil.
- if (isScopeEnabled()) recoilFactor *= 0.1f;
- float recoil = recoilFactor * gun.getRecoil() + (float) ThreadLocalRandom.current().nextGaussian(0, 0.01);
- server.getPlayerManager().getHandler(player.getId()).sendDatagramPacket(new ClientRecoilMessage(0, Math.toRadians(recoil)));
- // Play sound!
- String shotSound = null;
- if (gun instanceof Rifle) {
- shotSound = "shot_m1-garand_1";
- } else if (gun instanceof Ak47) {
- shotSound = "shot_ak-47_1";
- } else if (gun instanceof Winchester) {
- shotSound = "shot_winchester_1";
- }
- Vector3f soundLocation = new Vector3f(player.getPosition());
- soundLocation.y += 1.4f;
- soundLocation.add(player.getViewVector());
- server.getPlayerManager().broadcastUdpMessage(new SoundMessage(shotSound, 1, soundLocation, player.getVelocity()));
+ shootGun(now, server, gun, g);
}
- if (// Check to see if the player is reloading.
- input.reloading() &&
- !gunReloading &&
- g.getClipCount() > 0
- ) {
- g.setClipCount(g.getClipCount() - 1);
- gunReloadingStartedAt = now;
- gunReloading = true;
- reloadingItemStack = g;
- server.getPlayerManager().getHandler(player.getId()).sendDatagramPacket(new ItemStackMessage(player.getInventory()));
- server.getPlayerManager().broadcastUdpMessage(new SoundMessage("reload", 1, player.getPosition(), player.getVelocity()));
+ // Check to see if the player is reloading.
+ if (input.reloading() && !gunReloading && g.getClipCount() > 0) {
+ reloadGun(now, server, g);
}
- if (// Check to see if reloading is done.
- gunReloading &&
- reloadingItemStack != null &&
- now - gunReloadingStartedAt > gun.getReloadTime() * 1000
- ) {
- reloadingItemStack.setBulletCount(gun.getMaxBulletCount());
- int idx = player.getInventory().getIndex(reloadingItemStack);
- if (idx != -1) {
- server.getPlayerManager().getHandler(player.getId()).sendDatagramPacket(new ItemStackMessage(idx, reloadingItemStack));
- }
- gunReloading = false;
- reloadingItemStack = null;
+ // Check to see if reloading is done.
+ if (gunReloading && reloadingItemStack != null && now - gunReloadingStartedAt > gun.getReloadTime() * 1000) {
+ reloadingComplete(server, gun);
}
// Check to see if the player released the trigger, for non-automatic weapons.
@@ -334,26 +293,6 @@ public class PlayerActionManager {
return points;
}
- private boolean isSpaceOccupied(Vector3i pos) {
- var playerPos = player.getPosition();
- float playerBodyMinZ = playerPos.z - RADIUS;
- float playerBodyMaxZ = playerPos.z + RADIUS;
- float playerBodyMinX = playerPos.x - RADIUS;
- float playerBodyMaxX = playerPos.x + RADIUS;
- float playerBodyMinY = playerPos.y;
- float playerBodyMaxY = playerPos.y + player.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);
-
- return pos.x >= minX && pos.x <= maxX && pos.y >= minY && pos.y <= maxY && pos.z >= minZ && pos.z <= maxZ;
- }
-
private void checkBlockCollisions(Vector3f movement, Server server, World world) {
var position = player.getPosition();
var velocity = player.getVelocity();
@@ -501,4 +440,51 @@ public class PlayerActionManager {
}
}
}
+
+ private void shootGun(long now, Server server, Gun gun, GunItemStack g) {
+ server.getProjectileManager().spawnBullets(player, gun);
+ g.setBulletCount(g.getBulletCount() - 1);
+ gunLastShotAt = now;
+ if (!gun.isAutomatic()) {
+ gunNeedsReCock = true;
+ }
+ server.getPlayerManager().getHandler(player.getId()).sendDatagramPacket(new ItemStackMessage(player.getInventory()));
+ // Apply recoil!
+ float recoilFactor = 10f; // Maximum number of degrees to recoil.
+ if (isScopeEnabled()) recoilFactor *= 0.1f;
+ float recoil = recoilFactor * gun.getRecoil() + (float) ThreadLocalRandom.current().nextGaussian(0, 0.01);
+ server.getPlayerManager().getHandler(player.getId()).sendDatagramPacket(new ClientRecoilMessage(0, Math.toRadians(recoil)));
+ // Play sound!
+ String shotSound = null;
+ if (gun instanceof Rifle) {
+ shotSound = "shot_m1-garand_1";
+ } else if (gun instanceof Ak47) {
+ shotSound = "shot_ak-47_1";
+ } else if (gun instanceof Winchester) {
+ shotSound = "shot_winchester_1";
+ }
+ Vector3f soundLocation = new Vector3f(player.getPosition());
+ soundLocation.y += 1.4f;
+ soundLocation.add(player.getViewVector());
+ server.getPlayerManager().broadcastUdpMessage(new SoundMessage(shotSound, 1, soundLocation, player.getVelocity()));
+ }
+
+ private void reloadGun(long now, Server server, GunItemStack g) {
+ g.setClipCount(g.getClipCount() - 1);
+ gunReloadingStartedAt = now;
+ gunReloading = true;
+ reloadingItemStack = g;
+ server.getPlayerManager().getHandler(player.getId()).sendDatagramPacket(new ItemStackMessage(player.getInventory()));
+ server.getPlayerManager().broadcastUdpMessage(new SoundMessage("reload", 1, player.getPosition(), player.getVelocity()));
+ }
+
+ private void reloadingComplete(Server server, Gun gun) {
+ reloadingItemStack.setBulletCount(gun.getMaxBulletCount());
+ int idx = player.getInventory().getIndex(reloadingItemStack);
+ if (idx != -1) {
+ server.getPlayerManager().getHandler(player.getId()).sendDatagramPacket(new ItemStackMessage(idx, reloadingItemStack));
+ }
+ gunReloading = false;
+ reloadingItemStack = null;
+ }
}