Added bullets!

This commit is contained in:
Andrew Lalis 2022-07-21 12:52:45 +02:00
parent 3d695e0312
commit b1d3abc8c8
34 changed files with 468 additions and 42 deletions

View File

@ -12,6 +12,7 @@ import nl.andrewl.aos2_client.sound.SoundManager;
import nl.andrewl.aos2_client.sound.SoundSource; import nl.andrewl.aos2_client.sound.SoundSource;
import nl.andrewl.aos_core.config.Config; import nl.andrewl.aos_core.config.Config;
import nl.andrewl.aos_core.model.Player; import nl.andrewl.aos_core.model.Player;
import nl.andrewl.aos_core.model.Projectile;
import nl.andrewl.aos_core.model.Team; import nl.andrewl.aos_core.model.Team;
import nl.andrewl.aos_core.net.client.*; import nl.andrewl.aos_core.net.client.*;
import nl.andrewl.aos_core.net.world.ChunkDataMessage; import nl.andrewl.aos_core.net.world.ChunkDataMessage;
@ -43,12 +44,14 @@ public class Client implements Runnable {
private ClientWorld world; private ClientWorld world;
private ClientPlayer myPlayer; private ClientPlayer myPlayer;
private final Map<Integer, OtherPlayer> players; private final Map<Integer, OtherPlayer> players;
private final Map<Integer, Projectile> projectiles;
private Map<Integer, Team> teams; private Map<Integer, Team> teams;
public Client(ClientConfig config) { public Client(ClientConfig config) {
this.config = config; this.config = config;
this.players = new ConcurrentHashMap<>(); this.players = new ConcurrentHashMap<>();
this.teams = new ConcurrentHashMap<>(); this.teams = new ConcurrentHashMap<>();
this.projectiles = new ConcurrentHashMap<>();
this.communicationHandler = new CommunicationHandler(this); this.communicationHandler = new CommunicationHandler(this);
this.inputHandler = new InputHandler(this, communicationHandler); this.inputHandler = new InputHandler(this, communicationHandler);
} }
@ -96,6 +99,7 @@ public class Client implements Runnable {
world.processQueuedChunkUpdates(); world.processQueuedChunkUpdates();
gameRenderer.getCamera().interpolatePosition(dt); gameRenderer.getCamera().interpolatePosition(dt);
interpolatePlayers(dt); interpolatePlayers(dt);
interpolateProjectiles(dt);
gameRenderer.draw(); gameRenderer.draw();
lastFrameAt = now; lastFrameAt = now;
} }
@ -120,7 +124,9 @@ public class Client implements Runnable {
if (gameRenderer != null) { if (gameRenderer != null) {
gameRenderer.getCamera().setToPlayer(myPlayer); gameRenderer.getCamera().setToPlayer(myPlayer);
} }
soundManager.updateListener(myPlayer.getPosition(), myPlayer.getVelocity()); if (soundManager != null) {
soundManager.updateListener(myPlayer.getPosition(), myPlayer.getVelocity());
}
lastPlayerUpdate = playerUpdate.timestamp(); lastPlayerUpdate = playerUpdate.timestamp();
} else { } else {
OtherPlayer p = players.get(playerUpdate.clientId()); OtherPlayer p = players.get(playerUpdate.clientId());
@ -151,6 +157,18 @@ public class Client implements Runnable {
players.remove(leaveMessage.id()); players.remove(leaveMessage.id());
} else if (msg instanceof SoundMessage soundMessage) { } else if (msg instanceof SoundMessage soundMessage) {
playerSource.play(soundManager.getSoundBuffer(soundMessage.name())); playerSource.play(soundManager.getSoundBuffer(soundMessage.name()));
} else if (msg instanceof ProjectileMessage pm) {
Projectile p = projectiles.get(pm.id());
if (p == null && !pm.destroyed()) {
p = new Projectile(pm.id(), new Vector3f(pm.px(), pm.py(), pm.pz()), new Vector3f(pm.vx(), pm.vy(), pm.vz()), pm.type());
projectiles.put(p.getId(), p);
} else if (p != null) {
p.getPosition().set(pm.px(), pm.py(), pm.pz()); // Don't update position, it's too short of a timeframe to matter.
p.getVelocity().set(pm.vx(), pm.vy(), pm.vz());
if (pm.destroyed()) {
projectiles.remove(p.getId());
}
}
} }
} }
@ -174,6 +192,10 @@ public class Client implements Runnable {
return players; return players;
} }
public Map<Integer, Projectile> getProjectiles() {
return projectiles;
}
public void interpolatePlayers(float dt) { public void interpolatePlayers(float dt) {
Vector3f movement = new Vector3f(); Vector3f movement = new Vector3f();
for (var player : players.values()) { for (var player : players.values()) {
@ -183,6 +205,14 @@ public class Client implements Runnable {
} }
} }
public void interpolateProjectiles(float dt) {
Vector3f movement = new Vector3f();
for (var proj : projectiles.values()) {
movement.set(proj.getVelocity()).mul(dt);
proj.getPosition().add(movement);
}
}
public static void main(String[] args) throws IOException { public static void main(String[] args) throws IOException {
List<Path> configPaths = Config.getCommonConfigPaths(); List<Path> configPaths = Config.getCommonConfigPaths();
if (args.length > 0) { if (args.length > 0) {

View File

@ -12,8 +12,8 @@ public class ClientConfig {
} }
public static class DisplayConfig { public static class DisplayConfig {
public boolean fullscreen = true; public boolean fullscreen = false;
public boolean captureCursor = true; public boolean captureCursor = false;
public float fov = 70; public float fov = 70;
} }
} }

View File

@ -45,6 +45,7 @@ public class GameRenderer {
private Model playerModel; private Model playerModel;
private Model rifleModel; private Model rifleModel;
private Model blockModel; private Model blockModel;
private Model bulletModel;
// Standard GUI textures. // Standard GUI textures.
private GUITexture crosshairTexture; private GUITexture crosshairTexture;
@ -132,6 +133,7 @@ public class GameRenderer {
playerModel = new Model("model/player_simple.obj", "model/simple_player.png"); playerModel = new Model("model/player_simple.obj", "model/simple_player.png");
rifleModel = new Model("model/rifle.obj", "model/rifle.png"); rifleModel = new Model("model/rifle.obj", "model/rifle.png");
blockModel = new Model("model/block.obj", "model/block.png"); blockModel = new Model("model/block.obj", "model/block.png");
bulletModel = new Model("model/bullet.obj", "model/bullet.png");
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@ -216,6 +218,16 @@ public class GameRenderer {
} }
blockModel.unbind(); blockModel.unbind();
bulletModel.bind();
Matrix4f projectileTransform = new Matrix4f();
for (var projectile : client.getProjectiles().values()) {
projectileTransform.identity()
.translate(projectile.getPosition())
.rotateTowards(projectile.getVelocity(), Camera.UP);
modelRenderer.render(bulletModel, projectileTransform);
}
bulletModel.unbind();
modelRenderer.end(); modelRenderer.end();
// GUI rendering // GUI rendering

View File

@ -0,0 +1,13 @@
# Blender MTL File: 'bullet.blend'
# Material Count: 1
newmtl Material.001
Ns 225.000000
Ka 1.000000 1.000000 1.000000
Kd 0.800000 0.800000 0.800000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.450000
d 1.000000
illum 2
map_Kd /home/andrew/Programming/github-andrewlalis/ace-of-shades-2/client/src/main/resources/model/bullet.png

View File

@ -0,0 +1,140 @@
# Blender v2.82 (sub 7) OBJ File: 'bullet.blend'
# www.blender.org
mtllib bullet.mtl
o Cylinder
v 0.000000 0.020000 -0.033933
v 0.000000 0.020000 0.029329
v 0.017321 0.010000 -0.033933
v 0.017321 0.010000 0.029329
v 0.017321 -0.010000 -0.033933
v 0.017321 -0.010000 0.029329
v -0.000000 -0.020000 -0.033933
v -0.000000 -0.020000 0.029329
v -0.017321 -0.010000 -0.033933
v -0.017321 -0.010000 0.029329
v -0.017321 0.010000 -0.033933
v -0.017321 0.010000 0.029329
v -0.000000 0.015593 0.042620
v 0.013504 0.007796 0.042620
v 0.013504 -0.007796 0.042620
v -0.000000 -0.015593 0.042620
v -0.013504 -0.007796 0.042620
v -0.013504 0.007796 0.042620
v -0.000000 0.004471 0.055747
v 0.003872 0.002236 0.055747
v 0.003872 -0.002236 0.055747
v -0.000000 -0.004471 0.055747
v -0.003872 -0.002236 0.055747
v -0.003872 0.002236 0.055747
vt 0.436144 0.309159
vt 0.610602 1.000000
vt 0.436144 1.000000
vt 0.610602 0.309159
vt 0.697830 0.973380
vt 0.348915 0.717461
vt 0.261686 0.000000
vt 0.348915 0.026620
vt 0.261686 0.690841
vt 0.087229 0.000000
vt 0.242465 0.841851
vt 0.087229 0.690841
vt 0.000000 0.026620
vt 0.348915 0.282539
vt 0.348915 0.973380
vt 1.000000 0.110280
vt 0.848915 0.441121
vt 0.697831 0.110280
vt 0.815623 0.441121
vt 0.849399 0.588424
vt 0.815623 0.563770
vt 0.455366 0.158149
vt 0.106450 0.841851
vt 0.659387 0.137395
vt 0.697830 0.282539
vt 0.038443 0.862605
vt 0.000000 0.717461
vt 0.591380 0.158149
vt 0.781846 0.637733
vt 0.781846 0.588424
vt 0.697831 0.699058
vt 0.815623 0.662388
vt 0.815623 0.785037
vt 0.697831 0.527100
vt 0.503872 0.000000
vt 0.542874 0.000000
vt 0.849399 0.637733
vt 0.933415 0.699058
vt 0.154956 1.000000
vt 0.310472 0.862605
vt 0.848915 0.000000
vt 1.000000 0.330840
vt 0.697831 0.330840
vt 0.933415 0.527100
vt 0.387358 0.137395
vt 0.193959 1.000000
vn 0.5000 0.8660 -0.0000
vn 1.0000 0.0000 0.0000
vn 0.5000 -0.8660 0.0000
vn -0.5000 -0.8660 0.0000
vn 0.4806 -0.8324 0.2760
vn -1.0000 0.0000 0.0000
vn -0.5000 0.8660 -0.0000
vn -0.0000 0.0000 -1.0000
vn 0.8062 0.0000 0.5916
vn -0.4806 0.8324 0.2760
vn -0.4806 -0.8324 0.2760
vn 0.9612 0.0000 0.2760
vn -0.9612 0.0000 0.2760
vn 0.4806 0.8324 0.2760
vn 0.0000 0.0000 1.0000
vn -0.8062 0.0000 0.5916
vn 0.4031 -0.6982 0.5916
vn 0.4031 0.6982 0.5916
vn -0.4031 0.6982 0.5916
vn -0.4031 -0.6982 0.5916
usemtl Material.001
s off
f 2/1/1 3/2/1 1/3/1
f 4/4/2 5/5/2 3/2/2
f 6/6/3 7/7/3 5/8/3
f 8/9/4 9/10/4 7/7/4
f 6/6/5 16/11/5 8/9/5
f 10/12/6 11/13/6 9/10/6
f 12/14/7 1/3/7 11/15/7
f 3/16/8 7/17/8 11/18/8
f 15/19/9 20/20/9 21/21/9
f 12/14/10 13/22/10 2/1/10
f 8/9/11 17/23/11 10/12/11
f 4/4/12 15/24/12 6/25/12
f 10/12/13 18/26/13 12/27/13
f 4/4/14 13/22/14 14/28/14
f 23/29/15 22/30/15 21/21/15
f 17/31/16 24/32/16 18/33/16
f 16/34/17 21/21/17 22/30/17
f 14/28/18 19/35/18 20/36/18
f 18/33/19 19/37/19 13/38/19
f 16/11/20 23/39/20 17/23/20
f 2/1/1 4/4/1 3/2/1
f 4/4/2 6/25/2 5/5/2
f 6/6/3 8/9/3 7/7/3
f 8/9/4 10/12/4 9/10/4
f 6/6/5 15/40/5 16/11/5
f 10/12/6 12/27/6 11/13/6
f 12/14/7 2/1/7 1/3/7
f 11/18/8 1/41/8 3/16/8
f 3/16/8 5/42/8 7/17/8
f 7/17/8 9/43/8 11/18/8
f 15/19/9 14/44/9 20/20/9
f 12/14/10 18/45/10 13/22/10
f 8/9/11 16/11/11 17/23/11
f 4/4/12 14/28/12 15/24/12
f 10/12/13 17/23/13 18/26/13
f 4/4/14 2/1/14 13/22/14
f 21/21/15 20/20/15 23/29/15
f 20/20/15 19/37/15 23/29/15
f 19/37/15 24/32/15 23/29/15
f 17/31/16 23/29/16 24/32/16
f 16/34/17 15/19/17 21/21/17
f 14/28/18 13/22/18 19/35/18
f 18/33/19 24/32/19 19/37/19
f 16/11/20 22/46/20 23/39/20

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 B

View File

@ -43,6 +43,7 @@ public final class Net {
serializer.registerTypeSerializer(14, new ItemStackSerializer()); serializer.registerTypeSerializer(14, new ItemStackSerializer());
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);
} }
public static ExtendedDataInputStream getInputStream(InputStream in) { public static ExtendedDataInputStream getInputStream(InputStream in) {

View File

@ -0,0 +1,35 @@
package nl.andrewl.aos_core.model;
import org.joml.Vector3f;
/**
* Represents a flying projectile object in the world (usually a bullet).
* All projectiles have their orientation dependent on their velocity vector.
*/
public class Projectile {
protected final int id;
protected final Vector3f position;
protected final Vector3f velocity;
public enum Type {BULLET}
protected final Type type;
public Projectile(int id, Vector3f position, Vector3f velocity, Type type) {
this.id = id;
this.position = position;
this.velocity = velocity;
this.type = type;
}
public int getId() {
return id;
}
public Vector3f getPosition() {
return position;
}
public Vector3f getVelocity() {
return velocity;
}
}

View File

@ -1,5 +1,6 @@
package nl.andrewl.aos_core.model.item; package nl.andrewl.aos_core.model.item;
import nl.andrewl.aos_core.model.item.gun.Ak47;
import nl.andrewl.aos_core.model.item.gun.Rifle; import nl.andrewl.aos_core.model.item.gun.Rifle;
import java.util.HashMap; import java.util.HashMap;
@ -14,10 +15,12 @@ public final class ItemTypes {
public static final BlockItem BLOCK = new BlockItem(1); public static final BlockItem BLOCK = new BlockItem(1);
public static final Rifle RIFLE = new Rifle(2); public static final Rifle RIFLE = new Rifle(2);
public static final Ak47 AK_47 = new Ak47(3);
static { static {
registerType(BLOCK); registerType(BLOCK);
registerType(RIFLE); registerType(RIFLE);
registerType(AK_47);
} }
public static void registerType(Item type) { public static void registerType(Item type) {

View File

@ -0,0 +1,21 @@
package nl.andrewl.aos_core.model.item.gun;
import nl.andrewl.aos_core.model.item.Gun;
public class Ak47 extends Gun {
public Ak47(int id) {
super(
id,
"AK-47",
4,
30,
1,
0.95f,
0.1f,
1.2f,
40f,
30f,
true
);
}
}

View File

@ -129,7 +129,8 @@ public final class Worlds {
} }
} }
world.setSpawnPoint("first", new Vector3f(0.5f, 0f, 0.5f)); world.setSpawnPoint("A", new Vector3f(0.5f, 0f, 0.5f));
world.setSpawnPoint("B", new Vector3f(20.5f, 0f, 20.5f));
return world; return world;
} }

View File

@ -0,0 +1,16 @@
package nl.andrewl.aos_core.net.client;
import nl.andrewl.aos_core.model.Projectile;
import nl.andrewl.record_net.Message;
/**
* A simple message with an update about a bullet. Instead of having a separate
* message to indicate that a bullet has been destroyed, we add it to the
* normal bullet message.
*/
public record ProjectileMessage(
int id, Projectile.Type type,
float px, float py, float pz,
float vx, float vy, float vz,
boolean destroyed
) implements Message {}

View File

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

BIN
design/models/bullet.blend Normal file

Binary file not shown.

BIN
design/models/bullet.blend1 Normal file

Binary file not shown.

View File

@ -1,5 +1,6 @@
package nl.andrewl.aos2_server; package nl.andrewl.aos2_server;
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.item.ItemStack; import nl.andrewl.aos_core.model.item.ItemStack;
import nl.andrewl.aos_core.model.world.Chunk; import nl.andrewl.aos_core.model.world.Chunk;
@ -163,7 +164,7 @@ public class ClientCommunicationHandler {
WorldIO.write(server.getWorld(), out); WorldIO.write(server.getWorld(), out);
// Team data. // Team data.
var teams = server.getTeams().values(); var teams = server.getTeamManager().getTeams();
out.writeInt(teams.size()); out.writeInt(teams.size());
for (var team : teams) { for (var team : teams) {
out.writeInt(team.getId()); out.writeInt(team.getId());

View File

@ -1,5 +1,6 @@
package nl.andrewl.aos2_server; package nl.andrewl.aos2_server;
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.PlayerJoinMessage; import nl.andrewl.aos_core.net.client.PlayerJoinMessage;
@ -78,6 +79,15 @@ public class PlayerManager {
return Collections.unmodifiableCollection(clientHandlers.values()); return Collections.unmodifiableCollection(clientHandlers.values());
} }
public void tick(long currentTimeMillis, float dt) {
for (var player : players.values()) {
player.getActionManager().tick(currentTimeMillis, dt, server.getWorld(), server);
if (player.getActionManager().isUpdated()) {
broadcastUdpMessage(player.getUpdateMessage(currentTimeMillis));
}
}
}
/** /**
* Finds the team that's best suited for adding a new player. This is the * Finds the team that's best suited for adding a new player. This is the
* team that has the minimum (or tied for minimum) number of players. * team that has the minimum (or tied for minimum) number of players.
@ -86,7 +96,7 @@ public class PlayerManager {
private Team findBestTeamForNewPlayer() { private Team findBestTeamForNewPlayer() {
Team minTeam = null; Team minTeam = null;
int minCount = Integer.MAX_VALUE; int minCount = Integer.MAX_VALUE;
for (var team : server.getTeams().values()) { for (var team : server.getTeamManager().getTeams()) {
int playerCount = (int) players.values().stream() int playerCount = (int) players.values().stream()
.filter(p -> Objects.equals(p.getTeam(), team)) .filter(p -> Objects.equals(p.getTeam(), team))
.count(); .count();

View File

@ -0,0 +1,58 @@
package nl.andrewl.aos2_server;
import nl.andrewl.aos2_server.model.ServerPlayer;
import nl.andrewl.aos2_server.model.ServerProjectile;
import nl.andrewl.aos_core.model.Projectile;
import org.joml.Vector3f;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
public class ProjectileManager {
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 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);
ServerProjectile bullet = new ServerProjectile(id, 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;
// TODO: Check if bullet will hit anything, like blocks or players, if it follows current velocity.
Vector3f movement = new Vector3f(projectile.getVelocity()).mul(dt);
projectile.getPosition().add(movement);
if (projectile.getDistanceTravelled() > 500) {
removalQueue.add(projectile);
server.getPlayerManager().broadcastUdpMessage(projectile.toMessage(true));
} else {
server.getPlayerManager().broadcastUdpMessage(projectile.toMessage(false));
}
}
}

View File

@ -2,8 +2,8 @@ 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.aos2_server.logic.WorldUpdater;
import nl.andrewl.aos2_server.model.ServerPlayer;
import nl.andrewl.aos_core.config.Config; import nl.andrewl.aos_core.config.Config;
import nl.andrewl.aos_core.model.Team;
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;
import nl.andrewl.aos_core.net.UdpReceiver; import nl.andrewl.aos_core.net.UdpReceiver;
@ -18,7 +18,7 @@ import org.slf4j.LoggerFactory;
import java.io.IOException; import java.io.IOException;
import java.net.*; import java.net.*;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.*; import java.util.List;
import java.util.concurrent.ForkJoinPool; import java.util.concurrent.ForkJoinPool;
public class Server implements Runnable { public class Server implements Runnable {
@ -29,8 +29,8 @@ public class Server implements Runnable {
private volatile boolean running; private volatile boolean running;
private final ServerConfig config; private final ServerConfig config;
private final PlayerManager playerManager; private final PlayerManager playerManager;
private final Map<Integer, Team> teams; private final TeamManager teamManager;
private final Map<Team, String> teamSpawnPoints; private final ProjectileManager projectileManager;
private final World world; private final World world;
private final WorldUpdater worldUpdater; private final WorldUpdater worldUpdater;
@ -41,13 +41,14 @@ public class Server implements Runnable {
this.datagramSocket = new DatagramSocket(config.port); this.datagramSocket = new DatagramSocket(config.port);
this.datagramSocket.setReuseAddress(true); this.datagramSocket.setReuseAddress(true);
this.playerManager = new PlayerManager(this); this.playerManager = new PlayerManager(this);
this.teamManager = new TeamManager(this);
this.projectileManager = new ProjectileManager(this);
this.worldUpdater = new WorldUpdater(this, config.ticksPerSecond); this.worldUpdater = new WorldUpdater(this, config.ticksPerSecond);
this.world = Worlds.testingWorld(); this.world = Worlds.testingWorld();
this.teams = new HashMap<>();
this.teamSpawnPoints = new HashMap<>(); // TODO: Add some way to configure teams with config files or commands.
// TODO: Add some way to configure teams with config files. teamManager.addTeam("Red", new Vector3f(0.8f, 0, 0), "A");
teams.put(1, new Team(1, "Red", new Vector3f(0.8f, 0, 0), world.getSpawnPoint("first"))); teamManager.addTeam("Blue", new Vector3f(0, 0, 0.8f), "B");
teams.put(2, new Team(2, "Blue", new Vector3f(0, 0, 0.8f), world.getSpawnPoint("first")));
} }
@Override @Override
@ -121,16 +122,12 @@ public class Server implements Runnable {
return playerManager; return playerManager;
} }
public Map<Integer, Team> getTeams() { public TeamManager getTeamManager() {
return teams; return teamManager;
} }
public String getSpawnPoint(Team team) { public ProjectileManager getProjectileManager() {
return teamSpawnPoints.get(team); return projectileManager;
}
public Collection<ServerPlayer> getPlayersInTeam(Team team) {
return playerManager.getPlayers().stream().filter(p -> Objects.equals(p.getTeam(), team)).toList();
} }
public static void main(String[] args) throws IOException { public static void main(String[] args) throws IOException {

View File

@ -0,0 +1,49 @@
package nl.andrewl.aos2_server;
import nl.andrewl.aos2_server.model.ServerPlayer;
import nl.andrewl.aos_core.model.Team;
import org.joml.Vector3f;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* Component that manages the teams on a server.
*/
public class TeamManager {
private int nextTeamId = 1;
private final Server server;
private final Map<Integer, Team> teams;
public TeamManager(Server server) {
this.server = server;
this.teams = new HashMap<>();
}
public synchronized void addTeam(String name, Vector3f color, String spawnPoint) {
int id = nextTeamId++;
teams.put(id, new Team(id, name, color, server.getWorld().getSpawnPoint(spawnPoint)));
}
public Team getTeam(int id) {
return teams.get(id);
}
public Collection<Team> getTeams() {
return Collections.unmodifiableCollection(teams.values());
}
public Collection<ServerPlayer> getPlayers(Team team) {
return server.getPlayerManager().getPlayers().stream()
.filter(p -> p.getTeam().equals(team))
.toList();
}
public Collection<ServerPlayer> getPlayers(int id) {
Team team = getTeam(id);
if (team == null) return Collections.emptyList();
return getPlayers(team);
}
}

View File

@ -1,7 +1,7 @@
package nl.andrewl.aos2_server.logic; package nl.andrewl.aos2_server.logic;
import nl.andrewl.aos2_server.Server; import nl.andrewl.aos2_server.Server;
import nl.andrewl.aos2_server.ServerPlayer; import nl.andrewl.aos2_server.model.ServerPlayer;
import nl.andrewl.aos2_server.config.ServerConfig; import nl.andrewl.aos2_server.config.ServerConfig;
import nl.andrewl.aos_core.model.item.*; import nl.andrewl.aos_core.model.item.*;
import nl.andrewl.aos_core.model.world.World; import nl.andrewl.aos_core.model.world.World;
@ -18,7 +18,7 @@ import org.joml.Vector3i;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import static nl.andrewl.aos2_server.ServerPlayer.*; import static nl.andrewl.aos2_server.model.ServerPlayer.*;
/** /**
* Component that manages a server player's current actions and movement. * Component that manages a server player's current actions and movement.
@ -62,9 +62,8 @@ public class PlayerActionManager {
return updated; return updated;
} }
public void tick(float dt, World world, Server server) { public void tick(long now, float dt, World world, Server server) {
updated = false; // Reset the updated flag. This will be set to true if the player was updated in this tick. updated = false; // Reset the updated flag. This will be set to true if the player was updated in this tick.
long now = System.currentTimeMillis();
if (player.getInventory().getSelectedIndex() != lastInputState.selectedInventoryIndex()) { if (player.getInventory().getSelectedIndex() != lastInputState.selectedInventoryIndex()) {
player.getInventory().setSelectedIndex(lastInputState.selectedInventoryIndex()); player.getInventory().setSelectedIndex(lastInputState.selectedInventoryIndex());
// Tell the client that their inventory slot has been updated properly. // Tell the client that their inventory slot has been updated properly.
@ -100,12 +99,7 @@ public class PlayerActionManager {
now - gunLastShotAt > gun.getShotCooldownTime() * 1000 && now - gunLastShotAt > gun.getShotCooldownTime() * 1000 &&
(gun.isAutomatic() || !gunNeedsReCock) (gun.isAutomatic() || !gunNeedsReCock)
) { ) {
// TODO: trace a ray from gun to see if players intersect with it. server.getProjectileManager().spawnBullet(player);
var hit = world.getLookingAtPos(player.getEyePosition(), player.getViewVector(), 100);
if (hit != null) {
world.setBlockAt(hit.pos().x, hit.pos().y, hit.pos().z, (byte) 0);
server.getPlayerManager().broadcastUdpMessage(ChunkUpdateMessage.fromWorld(hit.pos(), world));
}
g.setBulletCount(g.getBulletCount() - 1); g.setBulletCount(g.getBulletCount() - 1);
gunLastShotAt = now; gunLastShotAt = now;
if (!gun.isAutomatic()) { if (!gun.isAutomatic()) {

View File

@ -52,12 +52,14 @@ public class WorldUpdater implements Runnable {
} }
} }
/**
* The main server update method, that runs once on each game tick, and
* performs all game state updates.
* @param currentTimeMillis The current timestamp for the tick. This may
* be needed for certain functions in logic.
*/
private void tick(long currentTimeMillis) { private void tick(long currentTimeMillis) {
for (var player : server.getPlayerManager().getPlayers()) { server.getPlayerManager().tick(currentTimeMillis, secondsPerTick);
player.getActionManager().tick(secondsPerTick, server.getWorld(), server); server.getProjectileManager().tick(secondsPerTick);
if (player.getActionManager().isUpdated()) {
server.getPlayerManager().broadcastUdpMessage(player.getUpdateMessage(currentTimeMillis));
}
}
} }
} }

View File

@ -1,4 +1,4 @@
package nl.andrewl.aos2_server; package nl.andrewl.aos2_server.model;
import nl.andrewl.aos2_server.logic.PlayerActionManager; import nl.andrewl.aos2_server.logic.PlayerActionManager;
import nl.andrewl.aos_core.model.Player; import nl.andrewl.aos_core.model.Player;
@ -26,8 +26,9 @@ public class ServerPlayer extends Player {
super(id, username); super(id, username);
this.inventory = new Inventory(new ArrayList<>(), 0); this.inventory = new Inventory(new ArrayList<>(), 0);
this.actionManager = new PlayerActionManager(this); this.actionManager = new PlayerActionManager(this);
inventory.getItemStacks().add(new GunItemStack(ItemTypes.get("Rifle"))); inventory.getItemStacks().add(new GunItemStack(ItemTypes.RIFLE));
inventory.getItemStacks().add(new BlockItemStack(ItemTypes.get("Block"), 50, (byte) 1)); inventory.getItemStacks().add(new BlockItemStack(ItemTypes.BLOCK, 50, (byte) 1));
inventory.getItemStacks().add(new GunItemStack(ItemTypes.AK_47));
} }
public PlayerActionManager getActionManager() { public PlayerActionManager getActionManager() {

View File

@ -0,0 +1,42 @@
package nl.andrewl.aos2_server.model;
import nl.andrewl.aos_core.model.Projectile;
import nl.andrewl.aos_core.net.client.ProjectileMessage;
import org.joml.Vector3f;
/**
* A bullet with extra information about who shot it, and from where it was
* shot, so we can properly delete the bullet and know who is responsible
* for damage it causes.
*/
public class ServerProjectile extends Projectile {
private final ServerPlayer player;
private final Vector3f origin;
public ServerProjectile(int id, Vector3f position, Vector3f velocity, Type type, ServerPlayer player) {
super(id, position, velocity, type);
this.player = player;
this.origin = new Vector3f(position);
}
public ServerPlayer getPlayer() {
return player;
}
public Vector3f getOrigin() {
return origin;
}
public float getDistanceTravelled() {
return origin.distance(position);
}
public ProjectileMessage toMessage(boolean destroyed) {
return new ProjectileMessage(
id, type,
position.x, position.y, position.z,
velocity.x, velocity.y, velocity.z,
destroyed
);
}
}