Updated with commands, improved UDP, etc. Still need to make UDP hole-punching thing.
This commit is contained in:
parent
fa001996ff
commit
09f5630a0c
|
@ -0,0 +1,81 @@
|
|||
package nl.andrewlalis.aos_client;
|
||||
|
||||
import nl.andrewlalis.aos_core.net.chat.ChatMessage;
|
||||
import nl.andrewlalis.aos_core.net.chat.PlayerChatMessage;
|
||||
import nl.andrewlalis.aos_core.net.data.Sound;
|
||||
import nl.andrewlalis.aos_core.net.data.SoundType;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class ChatManager {
|
||||
public static final int MAX_CHAT_MESSAGES = 10;
|
||||
|
||||
private final List<ChatMessage> chatMessages;
|
||||
private boolean chatting = false;
|
||||
private final StringBuilder chatBuffer;
|
||||
|
||||
private final SoundManager soundManager;
|
||||
private MessageTransceiver messageTransceiver;
|
||||
|
||||
public ChatManager(SoundManager soundManager) {
|
||||
this.soundManager = soundManager;
|
||||
this.chatMessages = new LinkedList<>();
|
||||
this.chatBuffer = new StringBuilder();
|
||||
}
|
||||
|
||||
public void bindTransceiver(MessageTransceiver messageTransceiver) {
|
||||
this.messageTransceiver = messageTransceiver;
|
||||
}
|
||||
|
||||
public void unbindTransceiver() {
|
||||
this.messageTransceiver = null;
|
||||
}
|
||||
|
||||
public synchronized void addChatMessage(ChatMessage message) {
|
||||
this.chatMessages.add(message);
|
||||
if (message.getClass() == PlayerChatMessage.class) {
|
||||
this.soundManager.play(new Sound(null, 1.0f, SoundType.CHAT));
|
||||
}
|
||||
while (this.chatMessages.size() > MAX_CHAT_MESSAGES) {
|
||||
this.chatMessages.remove(0);
|
||||
}
|
||||
}
|
||||
|
||||
public ChatMessage[] getLatestChatMessages() {
|
||||
return this.chatMessages.toArray(new ChatMessage[0]);
|
||||
}
|
||||
|
||||
public boolean isChatting() {
|
||||
return this.chatting;
|
||||
}
|
||||
|
||||
public void setChatting(boolean chatting) {
|
||||
this.chatting = chatting;
|
||||
if (this.chatting) {
|
||||
this.chatBuffer.setLength(0);
|
||||
}
|
||||
}
|
||||
|
||||
public void appendToChat(char c) {
|
||||
this.chatBuffer.append(c);
|
||||
}
|
||||
|
||||
public void backspaceChat() {
|
||||
if (this.chatBuffer.length() > 0) {
|
||||
this.chatBuffer.setLength(this.chatBuffer.length() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
public void sendChat() {
|
||||
String message = this.chatBuffer.toString().trim();
|
||||
if (!message.isBlank() && !message.equals("/") && this.messageTransceiver != null) {
|
||||
this.messageTransceiver.send(new ChatMessage(message));
|
||||
}
|
||||
this.setChatting(false);
|
||||
}
|
||||
|
||||
public String getCurrentChatBuffer() {
|
||||
return this.chatBuffer.toString();
|
||||
}
|
||||
}
|
|
@ -3,50 +3,39 @@ package nl.andrewlalis.aos_client;
|
|||
import nl.andrewlalis.aos_client.view.ConnectDialog;
|
||||
import nl.andrewlalis.aos_client.view.GameFrame;
|
||||
import nl.andrewlalis.aos_client.view.GamePanel;
|
||||
import nl.andrewlalis.aos_core.model.PlayerControlState;
|
||||
import nl.andrewlalis.aos_core.model.Player;
|
||||
import nl.andrewlalis.aos_core.model.World;
|
||||
import nl.andrewlalis.aos_core.net.PlayerControlStateMessage;
|
||||
import nl.andrewlalis.aos_core.net.chat.ChatMessage;
|
||||
import nl.andrewlalis.aos_core.net.chat.PlayerChatMessage;
|
||||
import nl.andrewlalis.aos_core.model.tools.Gun;
|
||||
import nl.andrewlalis.aos_core.net.data.DataTypes;
|
||||
import nl.andrewlalis.aos_core.net.data.PlayerDetailUpdate;
|
||||
import nl.andrewlalis.aos_core.net.data.WorldUpdate;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The main class for the client, which connects to a server to join and play.
|
||||
*/
|
||||
public class Client {
|
||||
public static final int MAX_CHAT_MESSAGES = 10;
|
||||
private final MessageTransceiver messageTransceiver;
|
||||
|
||||
private MessageTransceiver messageTransceiver;
|
||||
|
||||
private int playerId;
|
||||
private PlayerControlState playerControlState;
|
||||
private World world;
|
||||
|
||||
private final List<ChatMessage> chatMessages;
|
||||
private boolean chatting = false;
|
||||
private final StringBuilder chatBuffer;
|
||||
private Player myPlayer;
|
||||
|
||||
private final GameRenderer renderer;
|
||||
private final GamePanel gamePanel;
|
||||
private final SoundManager soundManager;
|
||||
private final ChatManager chatManager;
|
||||
|
||||
public Client() {
|
||||
this.chatMessages = new LinkedList<>();
|
||||
this.chatBuffer = new StringBuilder();
|
||||
public Client(String serverHost, int serverPort, String username) throws IOException {
|
||||
this.soundManager = new SoundManager();
|
||||
this.chatManager = new ChatManager(this.soundManager);
|
||||
this.gamePanel = new GamePanel(this);
|
||||
this.renderer = new GameRenderer(this, gamePanel);
|
||||
}
|
||||
|
||||
public void connect(String serverHost, int serverPort, String username) throws IOException, ClassNotFoundException {
|
||||
this.messageTransceiver = new MessageTransceiver(this);
|
||||
this.messageTransceiver.connectToServer(serverHost, serverPort, username);
|
||||
this.messageTransceiver = new MessageTransceiver(this, serverHost, serverPort, username);
|
||||
this.messageTransceiver.start();
|
||||
this.chatManager.bindTransceiver(this.messageTransceiver);
|
||||
|
||||
while (this.playerControlState == null || this.world == null) {
|
||||
while (this.myPlayer == null || this.world == null) {
|
||||
try {
|
||||
System.out.println("Waiting for server response and player registration...");
|
||||
Thread.sleep(100);
|
||||
|
@ -61,93 +50,75 @@ public class Client {
|
|||
this.renderer.start();
|
||||
}
|
||||
|
||||
public ChatManager getChatManager() {
|
||||
return chatManager;
|
||||
}
|
||||
|
||||
public World getWorld() {
|
||||
return world;
|
||||
}
|
||||
|
||||
public void updateWorld(WorldUpdate update) {
|
||||
if (this.world == null) return;
|
||||
this.world.getBullets().clear();
|
||||
for (var u : update.getBulletUpdates()) {
|
||||
this.world.getBullets().add(u.toBullet());
|
||||
}
|
||||
for (var p : update.getPlayerUpdates()) {
|
||||
Player player = this.world.getPlayers().get(p.getId());
|
||||
if (player != null) {
|
||||
player.setPosition(p.getPosition());
|
||||
player.setOrientation(p.getOrientation());
|
||||
player.setVelocity(p.getVelocity());
|
||||
player.setGun(Gun.forType(p.getGunType()));
|
||||
}
|
||||
}
|
||||
this.soundManager.play(update.getSoundsToPlay());
|
||||
}
|
||||
|
||||
public void setWorld(World world) {
|
||||
this.world = world;
|
||||
for (String sound : this.world.getSoundsToPlay()) {
|
||||
this.soundManager.play(sound);
|
||||
}
|
||||
}
|
||||
|
||||
public void initPlayerData(int playerId) {
|
||||
this.playerId = playerId;
|
||||
this.playerControlState = new PlayerControlState();
|
||||
this.playerControlState.setPlayerId(playerId);
|
||||
public void setPlayer(Player player) {
|
||||
this.myPlayer = player;
|
||||
}
|
||||
|
||||
public int getPlayerId() {
|
||||
return playerId;
|
||||
public Player getPlayer() {
|
||||
return myPlayer;
|
||||
}
|
||||
|
||||
public PlayerControlState getPlayerState() {
|
||||
return playerControlState;
|
||||
public void updatePlayer(PlayerDetailUpdate update) {
|
||||
if (this.myPlayer == null) return;
|
||||
this.myPlayer.setHealth(update.getHealth());
|
||||
this.myPlayer.setReloading(update.isReloading());
|
||||
this.myPlayer.setGun(Gun.forType(
|
||||
this.myPlayer.getGun().getType(),
|
||||
update.getGunMaxClipCount(),
|
||||
update.getGunClipSize(),
|
||||
update.getGunBulletsPerRound(),
|
||||
update.getGunCurrentClipBulletCount(),
|
||||
update.getGunClipCount()
|
||||
));
|
||||
}
|
||||
|
||||
public void sendPlayerState() {
|
||||
try {
|
||||
this.messageTransceiver.send(new PlayerControlStateMessage(this.playerControlState));
|
||||
this.messageTransceiver.sendData(DataTypes.PLAYER_CONTROL_STATE, myPlayer.getId(), myPlayer.getState().toBytes());
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void addChatMessage(ChatMessage message) {
|
||||
this.chatMessages.add(message);
|
||||
if (message.getClass() == PlayerChatMessage.class) {
|
||||
this.soundManager.play("chat.wav");
|
||||
}
|
||||
while (this.chatMessages.size() > MAX_CHAT_MESSAGES) {
|
||||
this.chatMessages.remove(0);
|
||||
}
|
||||
}
|
||||
|
||||
public ChatMessage[] getLatestChatMessages() {
|
||||
return this.chatMessages.toArray(new ChatMessage[0]);
|
||||
}
|
||||
|
||||
public boolean isChatting() {
|
||||
return this.chatting;
|
||||
}
|
||||
|
||||
public void setChatting(boolean chatting) {
|
||||
this.chatting = chatting;
|
||||
if (this.chatting) {
|
||||
this.chatBuffer.setLength(0);
|
||||
}
|
||||
}
|
||||
|
||||
public void appendToChat(char c) {
|
||||
this.chatBuffer.append(c);
|
||||
}
|
||||
|
||||
public void backspaceChat() {
|
||||
if (this.chatBuffer.length() > 0) {
|
||||
this.chatBuffer.setLength(this.chatBuffer.length() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
public void sendChat() {
|
||||
String message = this.chatBuffer.toString().trim();
|
||||
if (!message.isBlank() && !message.equals("/")) {
|
||||
try {
|
||||
this.messageTransceiver.send(new PlayerChatMessage(this.playerId, message));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
this.setChatting(false);
|
||||
}
|
||||
|
||||
public String getCurrentChatBuffer() {
|
||||
return this.chatBuffer.toString();
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
this.chatManager.unbindTransceiver();
|
||||
System.out.println("Chat manager shutdown.");
|
||||
this.messageTransceiver.shutdown();
|
||||
System.out.println("Message transceiver shutdown.");
|
||||
this.renderer.shutdown();
|
||||
System.out.println("Renderer shutdown.");
|
||||
this.soundManager.close();
|
||||
System.out.println("Sound manager closed.");
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
package nl.andrewlalis.aos_client;
|
||||
|
||||
import nl.andrewlalis.aos_core.net.data.DataTypes;
|
||||
import nl.andrewlalis.aos_core.net.data.PlayerDetailUpdate;
|
||||
import nl.andrewlalis.aos_core.net.data.WorldUpdate;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetAddress;
|
||||
import java.net.SocketException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class DataTransceiver extends Thread {
|
||||
private final Client client;
|
||||
private final DatagramSocket socket;
|
||||
|
||||
private volatile boolean running;
|
||||
|
||||
public DataTransceiver(Client client) throws SocketException {
|
||||
this.client = client;
|
||||
this.socket = new DatagramSocket();
|
||||
}
|
||||
|
||||
public int getLocalPort() {
|
||||
return this.socket.getLocalPort();
|
||||
}
|
||||
|
||||
public void send(byte[] bytes, InetAddress address, int port) throws IOException {
|
||||
if (this.socket.isClosed()) return;
|
||||
var packet = new DatagramPacket(bytes, bytes.length, address, port);
|
||||
this.socket.send(packet);
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
this.running = false;
|
||||
if (!this.socket.isClosed()) {
|
||||
this.socket.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
this.running = true;
|
||||
System.out.println("Datagram socket opened on " + this.socket.getLocalAddress() + ":" + this.socket.getLocalPort());
|
||||
byte[] buffer = new byte[1400];
|
||||
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
|
||||
while (this.running) {
|
||||
try {
|
||||
this.socket.receive(packet);
|
||||
ByteBuffer b = ByteBuffer.wrap(packet.getData(), 0, packet.getLength());
|
||||
byte type = b.get();
|
||||
if (type == DataTypes.WORLD_DATA) {
|
||||
byte[] worldData = new byte[b.remaining()];
|
||||
b.get(worldData);
|
||||
WorldUpdate update = WorldUpdate.fromBytes(worldData);
|
||||
this.client.updateWorld(update);
|
||||
} else if (type == DataTypes.PLAYER_DETAIL) {
|
||||
byte[] detailData = new byte[b.remaining()];
|
||||
b.get(detailData);
|
||||
PlayerDetailUpdate update = PlayerDetailUpdate.fromBytes(detailData);
|
||||
this.client.updatePlayer(update);
|
||||
}
|
||||
} catch (SocketException e) {
|
||||
if (!e.getMessage().equals("Socket closed")) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
this.shutdown();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -35,7 +35,7 @@ public class GameRenderer extends Thread {
|
|||
long now = System.currentTimeMillis();
|
||||
long msSinceLastFrame = now - lastFrame;
|
||||
if (msSinceLastFrame >= MS_PER_FRAME) {
|
||||
double elapsedSeconds = msSinceLastFrame / 1000.0;
|
||||
float elapsedSeconds = msSinceLastFrame / 1000.0f;
|
||||
this.gamePanel.repaint();
|
||||
this.updateWorld(elapsedSeconds);
|
||||
lastFrame = now;
|
||||
|
@ -52,7 +52,7 @@ public class GameRenderer extends Thread {
|
|||
}
|
||||
}
|
||||
|
||||
private void updateWorld(double t) {
|
||||
private void updateWorld(float t) {
|
||||
World world = this.client.getWorld();
|
||||
for (Player p : world.getPlayers().values()) {
|
||||
p.setPosition(p.getPosition().add(p.getVelocity().mul(t)));
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
package nl.andrewlalis.aos_client;
|
||||
|
||||
import nl.andrewlalis.aos_core.model.World;
|
||||
import nl.andrewlalis.aos_core.net.*;
|
||||
import nl.andrewlalis.aos_core.net.chat.ChatMessage;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* This thread is responsible for handling TCP message communication with the
|
||||
|
@ -15,61 +18,84 @@ import java.net.SocketException;
|
|||
public class MessageTransceiver extends Thread {
|
||||
private final Client client;
|
||||
|
||||
private Socket socket;
|
||||
private ObjectOutputStream out;
|
||||
private ObjectInputStream in;
|
||||
private final Socket socket;
|
||||
private final DataTransceiver dataTransceiver;
|
||||
private final ObjectOutputStream out;
|
||||
private final ObjectInputStream in;
|
||||
private final ExecutorService writeService = Executors.newFixedThreadPool(1);
|
||||
|
||||
private volatile boolean running = true;
|
||||
|
||||
public MessageTransceiver(Client client) {
|
||||
public MessageTransceiver(Client client, String serverHost, int serverPort, String username) throws IOException {
|
||||
this.client = client;
|
||||
}
|
||||
|
||||
public void connectToServer(String serverHost, int serverPort, String username) throws IOException {
|
||||
this.socket = new Socket(serverHost, serverPort);
|
||||
this.dataTransceiver = new DataTransceiver(client);
|
||||
this.out = new ObjectOutputStream(this.socket.getOutputStream());
|
||||
this.in = new ObjectInputStream(this.socket.getInputStream());
|
||||
this.send(new IdentMessage(username));
|
||||
this.send(new IdentMessage(username, this.dataTransceiver.getLocalPort()));
|
||||
System.out.println("Sent identification packet.");
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
this.running = false;
|
||||
if (this.socket != null) {
|
||||
this.dataTransceiver.shutdown();
|
||||
this.writeService.shutdown();
|
||||
try {
|
||||
this.out.close();
|
||||
this.in.close();
|
||||
this.socket.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void send(Message message) throws IOException {
|
||||
public void send(Message message) {
|
||||
if (this.socket.isClosed()) return;
|
||||
this.writeService.submit(() -> {
|
||||
try {
|
||||
this.out.reset();
|
||||
this.out.writeObject(message);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void sendData(byte type, int playerId, byte[] data) throws IOException {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(1 + Integer.BYTES + data.length);
|
||||
buffer.put(type);
|
||||
buffer.putInt(playerId);
|
||||
buffer.put(data);
|
||||
this.dataTransceiver.send(buffer.array(), this.socket.getInetAddress(), this.socket.getPort());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
this.dataTransceiver.start();
|
||||
while (this.running) {
|
||||
try {
|
||||
Message msg = (Message) this.in.readObject();
|
||||
if (msg.getType() == Type.PLAYER_REGISTERED) {
|
||||
System.out.println("Received player registration response from server.");
|
||||
PlayerRegisteredMessage prm = (PlayerRegisteredMessage) msg;
|
||||
this.client.initPlayerData(prm.getPlayerId());
|
||||
this.client.setPlayer(prm.getPlayer());
|
||||
this.client.setWorld(prm.getWorld());
|
||||
} else if (msg.getType() == Type.CHAT) {
|
||||
this.client.addChatMessage((ChatMessage) msg);
|
||||
} else if (msg.getType() == Type.WORLD_UPDATE) {
|
||||
World world = ((WorldUpdateMessage) msg).getWorld();
|
||||
this.client.setWorld(world);
|
||||
this.client.getChatManager().addChatMessage((ChatMessage) msg);
|
||||
} else if (msg.getType() == Type.PLAYER_JOINED && this.client.getWorld() != null) {
|
||||
PlayerUpdateMessage pum = (PlayerUpdateMessage) msg;
|
||||
this.client.getWorld().getPlayers().put(pum.getPlayer().getId(), pum.getPlayer());
|
||||
} else if (msg.getType() == Type.PLAYER_LEFT && this.client.getWorld() != null) {
|
||||
PlayerUpdateMessage pum = (PlayerUpdateMessage) msg;
|
||||
this.client.getWorld().getPlayers().remove(pum.getPlayer().getId());
|
||||
}
|
||||
} catch (StreamCorruptedException | EOFException e) {
|
||||
e.printStackTrace();
|
||||
this.running = false;
|
||||
this.shutdown();
|
||||
} catch (SocketException e) {
|
||||
if (!e.getMessage().equalsIgnoreCase("Socket closed")) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
this.shutdown();
|
||||
} catch (IOException | ClassNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
|
|
@ -1,54 +1,82 @@
|
|||
package nl.andrewlalis.aos_client;
|
||||
|
||||
import nl.andrewlalis.aos_core.net.data.Sound;
|
||||
|
||||
import javax.sound.sampled.*;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class SoundManager {
|
||||
private final Map<String, byte[]> soundData = new HashMap<>();
|
||||
private static final int CLIP_COUNT = 10;
|
||||
private final Map<String, List<Clip>> soundData = new HashMap<>();
|
||||
private final Map<String, Integer> clipIndexes = new HashMap<>();
|
||||
|
||||
public void play(String sound) {
|
||||
var clip = this.getClip(sound);
|
||||
if (clip != null) {
|
||||
public void play(List<Sound> sounds) {
|
||||
for (Sound sound : sounds) {
|
||||
this.play(sound);
|
||||
}
|
||||
}
|
||||
|
||||
public void play(Sound sound) {
|
||||
var clip = this.getClip(sound.getType().getSoundName());
|
||||
if (clip == null) {
|
||||
return;
|
||||
}
|
||||
clip.setFramePosition(0);
|
||||
setVolume(clip, sound.getVolume());
|
||||
clip.start();
|
||||
}
|
||||
|
||||
private void setVolume(Clip clip, float volume) {
|
||||
volume = Math.max(Math.min(volume, 1.0f), 0.0f);
|
||||
FloatControl gainControl = (FloatControl) clip.getControl(FloatControl.Type.MASTER_GAIN);
|
||||
gainControl.setValue(20f * (float) Math.log10(volume));
|
||||
}
|
||||
|
||||
private Clip getClip(String sound) {
|
||||
var soundBytes = this.soundData.get(sound);
|
||||
if (soundBytes == null) {
|
||||
var clips = this.soundData.get(sound);
|
||||
if (clips == null) {
|
||||
InputStream is = Client.class.getResourceAsStream("/nl/andrewlalis/aos_client/sound/" + sound);
|
||||
if (is == null) {
|
||||
System.err.println("Could not load sound: " + sound);
|
||||
return null;
|
||||
}
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
try {
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
is.transferTo(bos);
|
||||
soundBytes = bos.toByteArray();
|
||||
this.soundData.put(sound, soundBytes);
|
||||
} catch (IOException e) {
|
||||
byte[] data = bos.toByteArray();
|
||||
clips = new ArrayList<>(CLIP_COUNT);
|
||||
for (int i = 0; i < CLIP_COUNT; i++) {
|
||||
var ais = AudioSystem.getAudioInputStream(new ByteArrayInputStream(data));
|
||||
var clip = AudioSystem.getClip();
|
||||
clip.open(ais);
|
||||
ais.close();
|
||||
clips.add(clip);
|
||||
}
|
||||
this.soundData.put(sound, clips);
|
||||
this.clipIndexes.put(sound, 0);
|
||||
} catch (UnsupportedAudioFileException | LineUnavailableException | IOException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
try {
|
||||
var ais = AudioSystem.getAudioInputStream(new ByteArrayInputStream(soundBytes));
|
||||
var clip = AudioSystem.getClip();
|
||||
clip.addLineListener(event -> {
|
||||
if (event.getType() == LineEvent.Type.STOP) {
|
||||
int index = this.clipIndexes.get(sound);
|
||||
if (index >= CLIP_COUNT) {
|
||||
index = 0;
|
||||
}
|
||||
Clip clip = clips.get(index);
|
||||
this.clipIndexes.put(sound, index + 1);
|
||||
return clip;
|
||||
}
|
||||
|
||||
public void close() {
|
||||
for (var c : this.soundData.values()) {
|
||||
for (var clip : c) {
|
||||
clip.close();
|
||||
}
|
||||
});
|
||||
clip.open(ais);
|
||||
return clip;
|
||||
} catch (UnsupportedAudioFileException | IOException | LineUnavailableException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,12 +9,10 @@ public class Tester {
|
|||
};
|
||||
|
||||
public static void main(String[] args) {
|
||||
for (int i = 0; i < 2; i++) {
|
||||
Client client = new Client();
|
||||
for (int i = 0; i < 6; i++) {
|
||||
try {
|
||||
client.connect("localhost", 8035, names[ThreadLocalRandom.current().nextInt(names.length)]);
|
||||
} catch (IOException | ClassNotFoundException e) {
|
||||
client.shutdown();
|
||||
new Client("localhost", 8035, names[ThreadLocalRandom.current().nextInt(names.length)]);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package nl.andrewlalis.aos_client.control;
|
||||
|
||||
import nl.andrewlalis.aos_client.ChatManager;
|
||||
import nl.andrewlalis.aos_client.Client;
|
||||
|
||||
import java.awt.event.KeyAdapter;
|
||||
|
@ -7,36 +8,38 @@ import java.awt.event.KeyEvent;
|
|||
|
||||
public class PlayerKeyListener extends KeyAdapter {
|
||||
private final Client client;
|
||||
private final ChatManager chatManager;
|
||||
|
||||
public PlayerKeyListener(Client client) {
|
||||
this.client = client;
|
||||
this.chatManager = client.getChatManager();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyTyped(KeyEvent e) {
|
||||
if (!this.client.isChatting()) {
|
||||
if (!this.chatManager.isChatting()) {
|
||||
if ((e.getKeyChar() == 't' || e.getKeyChar() == '/')) {
|
||||
this.client.setChatting(true);
|
||||
if (e.getKeyChar() == '/') this.client.appendToChat('/');
|
||||
this.chatManager.setChatting(true);
|
||||
if (e.getKeyChar() == '/') this.chatManager.appendToChat('/');
|
||||
}
|
||||
} else if (this.client.isChatting()) {
|
||||
} else if (this.chatManager.isChatting()) {
|
||||
char c = e.getKeyChar();
|
||||
if (c >= ' ' && c <= '~') {
|
||||
this.client.appendToChat(c);
|
||||
this.chatManager.appendToChat(c);
|
||||
} else if (e.getKeyChar() == 8) {
|
||||
this.client.backspaceChat();
|
||||
this.chatManager.backspaceChat();
|
||||
} else if (e.getKeyChar() == 10) {
|
||||
this.client.sendChat();
|
||||
this.chatManager.sendChat();
|
||||
} else if (e.getKeyChar() == 27) {
|
||||
this.client.setChatting(false);
|
||||
this.chatManager.setChatting(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void keyPressed(KeyEvent e) {
|
||||
if (client.isChatting()) return;
|
||||
var state = client.getPlayerState();
|
||||
if (this.chatManager.isChatting()) return;
|
||||
var state = client.getPlayer().getState();
|
||||
if (e.getKeyCode() == KeyEvent.VK_W) {
|
||||
state.setMovingForward(true);
|
||||
} else if (e.getKeyCode() == KeyEvent.VK_S) {
|
||||
|
@ -53,8 +56,8 @@ public class PlayerKeyListener extends KeyAdapter {
|
|||
|
||||
@Override
|
||||
public void keyReleased(KeyEvent e) {
|
||||
if (client.isChatting()) return;
|
||||
var state = client.getPlayerState();
|
||||
if (this.chatManager.isChatting()) return;
|
||||
var state = client.getPlayer().getState();
|
||||
if (e.getKeyCode() == KeyEvent.VK_W) {
|
||||
state.setMovingForward(false);
|
||||
} else if (e.getKeyCode() == KeyEvent.VK_S) {
|
||||
|
|
|
@ -9,9 +9,14 @@ import java.awt.event.MouseEvent;
|
|||
import java.awt.event.MouseWheelEvent;
|
||||
|
||||
public class PlayerMouseListener extends MouseInputAdapter {
|
||||
private static final float MOUSE_UPDATES_PER_SECOND = 30.0f;
|
||||
private static final long MS_PER_MOUSE_UPDATE = (long) (1000.0f / MOUSE_UPDATES_PER_SECOND);
|
||||
|
||||
private final Client client;
|
||||
private final GamePanel gamePanel;
|
||||
|
||||
private long lastMouseMove = 0L;
|
||||
|
||||
public PlayerMouseListener(Client client, GamePanel gamePanel) {
|
||||
this.client = client;
|
||||
this.gamePanel = gamePanel;
|
||||
|
@ -20,7 +25,7 @@ public class PlayerMouseListener extends MouseInputAdapter {
|
|||
@Override
|
||||
public void mousePressed(MouseEvent e) {
|
||||
if (e.getButton() == MouseEvent.BUTTON1) {
|
||||
client.getPlayerState().setShooting(true);
|
||||
client.getPlayer().getState().setShooting(true);
|
||||
client.sendPlayerState();
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +33,7 @@ public class PlayerMouseListener extends MouseInputAdapter {
|
|||
@Override
|
||||
public void mouseReleased(MouseEvent e) {
|
||||
if (e.getButton() == MouseEvent.BUTTON1) {
|
||||
client.getPlayerState().setShooting(false);
|
||||
client.getPlayer().getState().setShooting(false);
|
||||
client.sendPlayerState();
|
||||
}
|
||||
}
|
||||
|
@ -44,18 +49,26 @@ public class PlayerMouseListener extends MouseInputAdapter {
|
|||
|
||||
@Override
|
||||
public void mouseMoved(MouseEvent e) {
|
||||
Vec2 c = new Vec2(this.gamePanel.getWidth() / 2.0, this.gamePanel.getHeight() / 2.0);
|
||||
Vec2 c = new Vec2(this.gamePanel.getWidth() / 2.0f, this.gamePanel.getHeight() / 2.0f);
|
||||
Vec2 centeredMouseLocation = new Vec2(e.getX(), e.getY()).sub(c);
|
||||
client.getPlayerState().setMouseLocation(centeredMouseLocation);
|
||||
client.getPlayer().getState().setMouseLocation(centeredMouseLocation);
|
||||
long now = System.currentTimeMillis();
|
||||
if (now - this.lastMouseMove > MS_PER_MOUSE_UPDATE) {
|
||||
client.sendPlayerState();
|
||||
this.lastMouseMove = now;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseDragged(MouseEvent e) {
|
||||
Vec2 c = new Vec2(this.gamePanel.getWidth() / 2.0, this.gamePanel.getHeight() / 2.0);
|
||||
Vec2 c = new Vec2(this.gamePanel.getWidth() / 2.0f, this.gamePanel.getHeight() / 2.0f);
|
||||
Vec2 centeredMouseLocation = new Vec2(e.getX(), e.getY()).sub(c);
|
||||
client.getPlayerState().setMouseLocation(centeredMouseLocation);
|
||||
client.getPlayerState().setShooting(true);
|
||||
client.getPlayer().getState().setMouseLocation(centeredMouseLocation);
|
||||
client.getPlayer().getState().setShooting(true);
|
||||
long now = System.currentTimeMillis();
|
||||
if (now - this.lastMouseMove > MS_PER_MOUSE_UPDATE) {
|
||||
client.sendPlayerState();
|
||||
this.lastMouseMove = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -77,6 +77,9 @@ public class ConnectDialog extends JDialog {
|
|||
if (usernameField.getText() == null || usernameField.getText().isBlank()) {
|
||||
warnings.add("Username must not be empty.");
|
||||
}
|
||||
if (usernameField.getText() != null && usernameField.getText().length() > 16) {
|
||||
warnings.add("Username is too long.");
|
||||
}
|
||||
if (addressField.getText() != null && !addressPattern.matcher(addressField.getText()).matches()) {
|
||||
warnings.add("Address must be in the form HOST:PORT.");
|
||||
}
|
||||
|
@ -97,11 +100,9 @@ public class ConnectDialog extends JDialog {
|
|||
String host = parts[0].trim();
|
||||
int port = Integer.parseInt(parts[1]);
|
||||
String username = usernameField.getText();
|
||||
Client client = new Client();
|
||||
try {
|
||||
client.connect(host, port, username);
|
||||
} catch (IOException | ClassNotFoundException ex) {
|
||||
client.shutdown();
|
||||
new Client(host, port, username);
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
JOptionPane.showMessageDialog(null, "Could not connect:\n" + ex.getMessage(), "Connection Error", JOptionPane.WARNING_MESSAGE);
|
||||
}
|
||||
|
|
|
@ -56,7 +56,7 @@ public class GamePanel extends JPanel {
|
|||
}
|
||||
|
||||
private void drawWorld(Graphics2D g2, World world) {
|
||||
Player myPlayer = world.getPlayers().get(this.client.getPlayerId());
|
||||
Player myPlayer = client.getPlayer();
|
||||
if (myPlayer == null) return;
|
||||
double scale = this.scales[this.scaleIndex];
|
||||
AffineTransform pre = g2.getTransform();
|
||||
|
@ -195,7 +195,8 @@ public class GamePanel extends JPanel {
|
|||
private void drawChat(Graphics2D g2, World world) {
|
||||
int height = g2.getFontMetrics().getHeight();
|
||||
int y = height;
|
||||
for (ChatMessage message : this.client.getLatestChatMessages()) {
|
||||
var cm = this.client.getChatManager();
|
||||
for (ChatMessage message : cm.getLatestChatMessages()) {
|
||||
Color color = Color.WHITE;
|
||||
String text = message.getText();
|
||||
if (message instanceof SystemChatMessage sysMsg) {
|
||||
|
@ -219,14 +220,14 @@ public class GamePanel extends JPanel {
|
|||
y += height;
|
||||
}
|
||||
|
||||
if (this.client.isChatting()) {
|
||||
if (cm.isChatting()) {
|
||||
g2.setColor(Color.WHITE);
|
||||
g2.drawString("> " + this.client.getCurrentChatBuffer(), 5, height * 11);
|
||||
g2.drawString("> " + cm.getCurrentChatBuffer(), 5, height * 11);
|
||||
}
|
||||
}
|
||||
|
||||
private void drawStatus(Graphics2D g2, World world) {
|
||||
Player myPlayer = world.getPlayers().get(this.client.getPlayerId());
|
||||
Player myPlayer = this.client.getPlayer();
|
||||
if (myPlayer == null) return;
|
||||
|
||||
g2.setColor(Color.WHITE);
|
||||
|
|
|
@ -1,8 +1,13 @@
|
|||
module aos_core {
|
||||
requires java.desktop;
|
||||
|
||||
exports nl.andrewlalis.aos_core.net to aos_server, aos_client;
|
||||
exports nl.andrewlalis.aos_core.model to aos_server, aos_client;
|
||||
exports nl.andrewlalis.aos_core.geom to aos_server, aos_client;
|
||||
exports nl.andrewlalis.aos_core.net.chat to aos_client, aos_server;
|
||||
exports nl.andrewlalis.aos_core.net.data to aos_server, aos_client;
|
||||
|
||||
exports nl.andrewlalis.aos_core.model to aos_server, aos_client;
|
||||
exports nl.andrewlalis.aos_core.model.tools to aos_client, aos_server;
|
||||
|
||||
exports nl.andrewlalis.aos_core.geom to aos_server, aos_client;
|
||||
exports nl.andrewlalis.aos_core.util to aos_server, aos_client;
|
||||
}
|
|
@ -1,13 +1,15 @@
|
|||
package nl.andrewlalis.aos_core.geom;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
public record Vec2(double x, double y) implements Serializable {
|
||||
public record Vec2(float x, float y) implements Serializable {
|
||||
|
||||
public double mag() {
|
||||
return Math.sqrt(x * x + y * y);
|
||||
public float mag() {
|
||||
return (float) Math.sqrt(x * x + y * y);
|
||||
}
|
||||
|
||||
public Vec2 add(Vec2 other) {
|
||||
|
@ -18,16 +20,16 @@ public record Vec2(double x, double y) implements Serializable {
|
|||
return new Vec2(this.x - other.x, this.y - other.y);
|
||||
}
|
||||
|
||||
public Vec2 mul(double factor) {
|
||||
public Vec2 mul(float factor) {
|
||||
return new Vec2(this.x * factor, this.y * factor);
|
||||
}
|
||||
|
||||
public Vec2 unit() {
|
||||
double mag = this.mag();
|
||||
float mag = this.mag();
|
||||
return new Vec2(this.x / mag, this.y / mag);
|
||||
}
|
||||
|
||||
public double dot(Vec2 other) {
|
||||
public float dot(Vec2 other) {
|
||||
return this.x * other.x + this.y * other.y;
|
||||
}
|
||||
|
||||
|
@ -39,14 +41,14 @@ public record Vec2(double x, double y) implements Serializable {
|
|||
return new Vec2(this.y, -this.x);
|
||||
}
|
||||
|
||||
public double dist(Vec2 other) {
|
||||
public float dist(Vec2 other) {
|
||||
return other.sub(this).mag();
|
||||
}
|
||||
|
||||
public Vec2 rotate(double theta) {
|
||||
return new Vec2(
|
||||
this.x * Math.cos(theta) - this.y * Math.sin(theta),
|
||||
this.x * Math.sin(theta) + this.y * Math.cos(theta)
|
||||
(float) (this.x * Math.cos(theta) - this.y * Math.sin(theta)),
|
||||
(float) (this.x * Math.sin(theta) + this.y * Math.cos(theta))
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -59,10 +61,14 @@ public record Vec2(double x, double y) implements Serializable {
|
|||
return "[ " + x + ", " + y + " ]";
|
||||
}
|
||||
|
||||
public static Vec2 random(double min, double max) {
|
||||
public static Vec2 random(float min, float max) {
|
||||
Random r = ThreadLocalRandom.current();
|
||||
double x = r.nextDouble() * (max - min) + min;
|
||||
double y = r.nextDouble() * (max - min) + min;
|
||||
float x = r.nextFloat() * (max - min) + min;
|
||||
float y = r.nextFloat() * (max - min) + min;
|
||||
return new Vec2(x, y);
|
||||
}
|
||||
|
||||
public static Vec2 read(DataInputStream in) throws IOException {
|
||||
return new Vec2(in.readFloat(), in.readFloat());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ public class Barricade implements Serializable {
|
|||
this.size = size;
|
||||
}
|
||||
|
||||
public Barricade(double x, double y, double w, double h) {
|
||||
public Barricade(float x, float y, float w, float h) {
|
||||
this(new Vec2(x, y), new Vec2(w, h));
|
||||
}
|
||||
|
||||
|
|
|
@ -13,17 +13,21 @@ public class Bullet extends PhysicsObject {
|
|||
public Bullet(Player player) {
|
||||
this.playerId = player.getId();
|
||||
this.setPosition(player.getPosition()
|
||||
.add(player.getOrientation().mul(1.5))
|
||||
.add(player.getOrientation().mul(1.5f))
|
||||
.add(player.getOrientation().perp().mul(Player.RADIUS))
|
||||
);
|
||||
this.setOrientation(player.getOrientation());
|
||||
|
||||
Random r = ThreadLocalRandom.current();
|
||||
Vec2 perturbation = new Vec2((r.nextDouble() - 0.5) * 2, (r.nextDouble() - 0.5) * 2).mul(player.getGun().getAccuracy());
|
||||
Vec2 perturbation = Vec2.random(-1, 1).mul(player.getGun().getAccuracy());
|
||||
this.setVelocity(this.getOrientation().add(perturbation).mul(player.getGun().getBulletSpeed()));
|
||||
this.gun = player.getGun();
|
||||
}
|
||||
|
||||
public Bullet(Vec2 position, Vec2 velocity) {
|
||||
super(position, new Vec2(0, -1), velocity);
|
||||
this.playerId = -1;
|
||||
this.gun = null;
|
||||
}
|
||||
|
||||
public int getPlayerId() {
|
||||
return playerId;
|
||||
}
|
||||
|
|
|
@ -6,9 +6,9 @@ import nl.andrewlalis.aos_core.model.tools.Gun;
|
|||
import java.util.Objects;
|
||||
|
||||
public class Player extends PhysicsObject implements Comparable<Player> {
|
||||
public static final double MOVEMENT_SPEED = 10; // Movement speed, in m/s
|
||||
public static final double RADIUS = 0.5; // Collision radius, in meters.
|
||||
public static final double RESUPPLY_COOLDOWN = 30; // Seconds between allowing resupply.
|
||||
public static final float MOVEMENT_SPEED = 10; // Movement speed, in m/s
|
||||
public static final float RADIUS = 0.5f; // Collision radius, in meters.
|
||||
public static final float RESUPPLY_COOLDOWN = 30; // Seconds between allowing resupply.
|
||||
public static final float MAX_HEALTH = 100.0f;
|
||||
|
||||
private final int id;
|
||||
|
@ -28,10 +28,10 @@ public class Player extends PhysicsObject implements Comparable<Player> {
|
|||
this.name = name;
|
||||
this.team = team;
|
||||
this.state = new PlayerControlState();
|
||||
this.state.setPlayerId(this.id);
|
||||
this.gun = Gun.winchester();
|
||||
this.gun = Gun.ak47();
|
||||
this.health = MAX_HEALTH;
|
||||
this.useWeapon();
|
||||
this.lastShot = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
|
@ -66,8 +66,12 @@ public class Player extends PhysicsObject implements Comparable<Player> {
|
|||
this.gun = gun;
|
||||
}
|
||||
|
||||
public long getLastShot() {
|
||||
return lastShot;
|
||||
public void setHealth(float health) {
|
||||
this.health = health;
|
||||
}
|
||||
|
||||
public void setReloading(boolean reloading) {
|
||||
this.reloading = reloading;
|
||||
}
|
||||
|
||||
public boolean canUseWeapon() {
|
||||
|
@ -75,7 +79,7 @@ public class Player extends PhysicsObject implements Comparable<Player> {
|
|||
!this.state.isReloading() &&
|
||||
!this.reloading &&
|
||||
this.gun.getCurrentClipBulletCount() > 0 &&
|
||||
this.lastShot + this.gun.getShotCooldownTime() * 1000 < System.currentTimeMillis() &&
|
||||
this.lastShot + ((long) (this.gun.getShotCooldownTime() * 1000)) < System.currentTimeMillis() &&
|
||||
(this.getTeam() == null || this.getTeam().getSpawnPoint().dist(this.getPosition()) > Team.SPAWN_RADIUS);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
package nl.andrewlalis.aos_core.model;
|
||||
|
||||
import nl.andrewlalis.aos_core.geom.Vec2;
|
||||
import nl.andrewlalis.aos_core.net.data.DataTypes;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class PlayerControlState implements Serializable {
|
||||
private int playerId;
|
||||
|
||||
boolean movingLeft;
|
||||
boolean movingRight;
|
||||
boolean movingForward;
|
||||
|
@ -17,14 +17,6 @@ public class PlayerControlState implements Serializable {
|
|||
|
||||
Vec2 mouseLocation;
|
||||
|
||||
public int getPlayerId() {
|
||||
return playerId;
|
||||
}
|
||||
|
||||
public void setPlayerId(int playerId) {
|
||||
this.playerId = playerId;
|
||||
}
|
||||
|
||||
public boolean isMovingLeft() {
|
||||
return movingLeft;
|
||||
}
|
||||
|
@ -80,4 +72,46 @@ public class PlayerControlState implements Serializable {
|
|||
public void setMouseLocation(Vec2 mouseLocation) {
|
||||
this.mouseLocation = mouseLocation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PlayerControlState{" +
|
||||
"movingLeft=" + movingLeft +
|
||||
", movingRight=" + movingRight +
|
||||
", movingForward=" + movingForward +
|
||||
", movingBackward=" + movingBackward +
|
||||
", shooting=" + shooting +
|
||||
", reloading=" + reloading +
|
||||
", mouseLocation=" + mouseLocation +
|
||||
'}';
|
||||
}
|
||||
|
||||
public byte[] toBytes() {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES + 2 * Float.BYTES);
|
||||
int flags = 0;
|
||||
if (this.movingLeft) flags |= 1;
|
||||
if (this.movingRight) flags |= 2;
|
||||
if (this.movingForward) flags |= 4;
|
||||
if (this.movingBackward) flags |= 8;
|
||||
if (this.shooting) flags |= 16;
|
||||
if (this.reloading) flags |= 32;
|
||||
buffer.putInt(flags);
|
||||
buffer.putFloat(this.mouseLocation.x());
|
||||
buffer.putFloat(this.mouseLocation.y());
|
||||
return buffer.array();
|
||||
}
|
||||
|
||||
public static PlayerControlState fromBytes(byte[] bytes) {
|
||||
var s = new PlayerControlState();
|
||||
ByteBuffer buffer = ByteBuffer.wrap(bytes);
|
||||
int flags = buffer.getInt();
|
||||
s.movingLeft = (flags & 1) > 0;
|
||||
s.movingRight = (flags & 2) > 0;
|
||||
s.movingForward = (flags & 4) > 0;
|
||||
s.movingBackward = (flags & 8) > 0;
|
||||
s.shooting = (flags & 16) > 0;
|
||||
s.reloading = (flags & 32) > 0;
|
||||
s.mouseLocation = new Vec2(buffer.getFloat(), buffer.getFloat());
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,8 +8,8 @@ import java.util.ArrayList;
|
|||
import java.util.List;
|
||||
|
||||
public class Team implements Serializable {
|
||||
public static final double SPAWN_RADIUS = 3;
|
||||
public static final double SUPPLY_POINT_RADIUS = 2;
|
||||
public static final float SPAWN_RADIUS = 3;
|
||||
public static final float SUPPLY_POINT_RADIUS = 2;
|
||||
|
||||
private final String name;
|
||||
private final java.awt.Color color;
|
||||
|
|
|
@ -7,6 +7,8 @@ import java.util.ArrayList;
|
|||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
/**
|
||||
* The main game world, consisting of all players and other objects in the game.
|
||||
|
@ -24,8 +26,8 @@ public class World implements Serializable {
|
|||
public World(Vec2 size) {
|
||||
this.size = size;
|
||||
this.teams = new ArrayList<>();
|
||||
this.players = new HashMap<>();
|
||||
this.bullets = new ArrayList<>();
|
||||
this.players = new ConcurrentHashMap<>();
|
||||
this.bullets = new CopyOnWriteArrayList<>();
|
||||
this.barricades = new ArrayList<>();
|
||||
this.soundsToPlay = new ArrayList<>();
|
||||
}
|
||||
|
|
|
@ -24,27 +24,27 @@ public class Gun implements Serializable {
|
|||
/**
|
||||
* How accurate shots from this gun are.
|
||||
*/
|
||||
private final double accuracy;
|
||||
private final float accuracy;
|
||||
|
||||
/**
|
||||
* How long (in seconds) to wait after each shot, before another is shot.
|
||||
*/
|
||||
private final double shotCooldownTime;
|
||||
private final float shotCooldownTime;
|
||||
|
||||
/**
|
||||
* How long (in seconds) for reloading a new clip.
|
||||
*/
|
||||
private final double reloadTime;
|
||||
private final float reloadTime;
|
||||
|
||||
/**
|
||||
* How fast the bullet travels (in m/s).
|
||||
*/
|
||||
private final double bulletSpeed;
|
||||
private final float bulletSpeed;
|
||||
|
||||
/**
|
||||
* How much damage the bullet does for a direct hit.
|
||||
*/
|
||||
private final double baseDamage;
|
||||
private final float baseDamage;
|
||||
|
||||
/**
|
||||
* Number of bullets left in the current clip.
|
||||
|
@ -55,7 +55,7 @@ public class Gun implements Serializable {
|
|||
*/
|
||||
private int clipCount;
|
||||
|
||||
private Gun(GunType type, int maxClipCount, int clipSize, int bulletsPerRound, double accuracy, double shotCooldownTime, double reloadTime, double bulletSpeed, double baseDamage) {
|
||||
private Gun(GunType type, int maxClipCount, int clipSize, int bulletsPerRound, float accuracy, float shotCooldownTime, float reloadTime, float bulletSpeed, float baseDamage) {
|
||||
this.type = type;
|
||||
this.maxClipCount = maxClipCount;
|
||||
this.clipSize = clipSize;
|
||||
|
@ -86,23 +86,23 @@ public class Gun implements Serializable {
|
|||
return bulletsPerRound;
|
||||
}
|
||||
|
||||
public double getAccuracy() {
|
||||
public float getAccuracy() {
|
||||
return accuracy;
|
||||
}
|
||||
|
||||
public double getShotCooldownTime() {
|
||||
public float getShotCooldownTime() {
|
||||
return shotCooldownTime;
|
||||
}
|
||||
|
||||
public double getReloadTime() {
|
||||
public float getReloadTime() {
|
||||
return reloadTime;
|
||||
}
|
||||
|
||||
public double getBulletSpeed() {
|
||||
public float getBulletSpeed() {
|
||||
return bulletSpeed;
|
||||
}
|
||||
|
||||
public double getBaseDamage() {
|
||||
public float getBaseDamage() {
|
||||
return baseDamage;
|
||||
}
|
||||
|
||||
|
@ -137,15 +137,32 @@ public class Gun implements Serializable {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to obtain a "dummy" gun for client-side rendering.
|
||||
* TODO: Improve cleanliness so this isn't necessary.
|
||||
* @param type The type of gun.
|
||||
* @return The gun.
|
||||
*/
|
||||
public static Gun forType(GunType type) {
|
||||
return new Gun(type, -1, -1, -1, -1, -1, -1, -1, -1);
|
||||
}
|
||||
|
||||
public static Gun forType(GunType type, int maxClipCount, int clipSize, int bulletsPerRound, int currentClipBulletCount, int clipCount) {
|
||||
Gun g = new Gun(type, maxClipCount, clipSize, bulletsPerRound, -1, -1, -1, -1, -1);
|
||||
g.currentClipBulletCount = currentClipBulletCount;
|
||||
g.clipCount = clipCount;
|
||||
return g;
|
||||
}
|
||||
|
||||
public static Gun ak47() {
|
||||
return new Gun(GunType.SMG, 4, 30, 1, 0.10, 0.05, 1.2, 90, 40);
|
||||
return new Gun(GunType.SMG, 4, 30, 1, 0.10f, 0.05f, 1.2f, 90, 40);
|
||||
}
|
||||
|
||||
public static Gun m1Garand() {
|
||||
return new Gun(GunType.RIFLE, 6, 8, 1, 0.02, 0.75, 1.5, 150, 100);
|
||||
return new Gun(GunType.RIFLE, 6, 8, 1, 0.02f, 0.75f, 1.5f, 150, 100);
|
||||
}
|
||||
|
||||
public static Gun winchester() {
|
||||
return new Gun(GunType.SHOTGUN, 8, 4, 3, 0.15, 0.5, 2.0, 75, 60);
|
||||
return new Gun(GunType.SHOTGUN, 8, 4, 3, 0.15f, 0.5f, 2.0f, 75, 60);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,24 @@
|
|||
package nl.andrewlalis.aos_core.model.tools;
|
||||
|
||||
public enum GunType {
|
||||
SHOTGUN,
|
||||
SMG,
|
||||
RIFLE
|
||||
SHOTGUN(0),
|
||||
SMG(1),
|
||||
RIFLE(2);
|
||||
|
||||
private final byte code;
|
||||
|
||||
GunType(int code) {
|
||||
this.code = (byte) code;
|
||||
}
|
||||
|
||||
public byte getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public static GunType get(byte code) {
|
||||
for (var val : values()) {
|
||||
if (val.code == code) return val;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,12 +3,22 @@ package nl.andrewlalis.aos_core.net;
|
|||
public class IdentMessage extends Message {
|
||||
private final String name;
|
||||
|
||||
public IdentMessage(String name) {
|
||||
/**
|
||||
* The port that the client will use to send and receive UDP packets.
|
||||
*/
|
||||
private final int udpPort;
|
||||
|
||||
public IdentMessage(String name, int udpPort) {
|
||||
super(Type.IDENT);
|
||||
this.name = name;
|
||||
this.udpPort = udpPort;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public int getUdpPort() {
|
||||
return this.udpPort;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,23 @@
|
|||
package nl.andrewlalis.aos_core.net;
|
||||
|
||||
import nl.andrewlalis.aos_core.model.Player;
|
||||
import nl.andrewlalis.aos_core.model.World;
|
||||
|
||||
public class PlayerRegisteredMessage extends Message {
|
||||
private final int playerId;
|
||||
private final Player player;
|
||||
private final World world;
|
||||
|
||||
public PlayerRegisteredMessage(int playerId) {
|
||||
public PlayerRegisteredMessage(Player player, World world) {
|
||||
super(Type.PLAYER_REGISTERED);
|
||||
this.playerId = playerId;
|
||||
this.player = player;
|
||||
this.world = world;
|
||||
}
|
||||
|
||||
public int getPlayerId() {
|
||||
return playerId;
|
||||
public Player getPlayer() {
|
||||
return player;
|
||||
}
|
||||
|
||||
public World getWorld() {
|
||||
return world;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
package nl.andrewlalis.aos_core.net;
|
||||
|
||||
import nl.andrewlalis.aos_core.model.Player;
|
||||
|
||||
public class PlayerUpdateMessage extends Message {
|
||||
private final Player player;
|
||||
|
||||
public PlayerUpdateMessage(Type type, Player player) {
|
||||
super(type);
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
public Player getPlayer() {
|
||||
return player;
|
||||
}
|
||||
}
|
|
@ -2,9 +2,10 @@ package nl.andrewlalis.aos_core.net;
|
|||
|
||||
public enum Type {
|
||||
IDENT,
|
||||
ACK,
|
||||
PLAYER_REGISTERED,
|
||||
CHAT,
|
||||
PLAYER_CONTROL_STATE,
|
||||
WORLD_UPDATE
|
||||
PLAYER_JOINED,
|
||||
PLAYER_LEFT,
|
||||
PLAYER_TEAM_CHANGE
|
||||
}
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
package nl.andrewlalis.aos_core.net;
|
||||
|
||||
import nl.andrewlalis.aos_core.model.World;
|
||||
|
||||
public class WorldUpdateMessage extends Message {
|
||||
private final World world;
|
||||
public WorldUpdateMessage(World world) {
|
||||
super(Type.WORLD_UPDATE);
|
||||
this.world = world;
|
||||
}
|
||||
|
||||
public World getWorld() {
|
||||
return world;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
package nl.andrewlalis.aos_core.net.data;
|
||||
|
||||
import nl.andrewlalis.aos_core.geom.Vec2;
|
||||
import nl.andrewlalis.aos_core.model.Bullet;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
public class BulletUpdate {
|
||||
public static final int BYTES = 4 * Float.BYTES;
|
||||
|
||||
private final Vec2 position;
|
||||
private final Vec2 velocity;
|
||||
public BulletUpdate(Bullet bullet) {
|
||||
this.position = bullet.getPosition();
|
||||
this.velocity = bullet.getVelocity();
|
||||
}
|
||||
|
||||
private BulletUpdate(Vec2 position, Vec2 velocity) {
|
||||
this.position = position;
|
||||
this.velocity = velocity;
|
||||
}
|
||||
|
||||
public Vec2 getPosition() {
|
||||
return position;
|
||||
}
|
||||
|
||||
public Vec2 getVelocity() {
|
||||
return velocity;
|
||||
}
|
||||
|
||||
public Bullet toBullet() {
|
||||
return new Bullet(this.position, this.velocity);
|
||||
}
|
||||
|
||||
public void write(DataOutputStream out) throws IOException {
|
||||
out.writeFloat(this.position.x());
|
||||
out.writeFloat(this.position.y());
|
||||
out.writeFloat(this.velocity.x());
|
||||
out.writeFloat(this.velocity.y());
|
||||
}
|
||||
|
||||
public static BulletUpdate read(DataInputStream in) throws IOException {
|
||||
return new BulletUpdate(
|
||||
Vec2.read(in),
|
||||
Vec2.read(in)
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package nl.andrewlalis.aos_core.net.data;
|
||||
|
||||
public class DataTypes {
|
||||
public static final byte PLAYER_CONTROL_STATE = 1;
|
||||
public static final byte WORLD_DATA = 2;
|
||||
public static final byte PLAYER_DETAIL = 3;
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
package nl.andrewlalis.aos_core.net.data;
|
||||
|
||||
import nl.andrewlalis.aos_core.model.Player;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class PlayerDetailUpdate {
|
||||
public static final int BYTES = Float.BYTES + 1 + 5 * Integer.BYTES;
|
||||
|
||||
private final float health;
|
||||
private final boolean reloading;
|
||||
|
||||
private final int gunMaxClipCount;
|
||||
private final int gunClipSize;
|
||||
private final int gunBulletsPerRound;
|
||||
private final int gunCurrentClipBulletCount;
|
||||
private final int gunClipCount;
|
||||
|
||||
public PlayerDetailUpdate(Player player) {
|
||||
this.health = player.getHealth();
|
||||
this.reloading = player.isReloading();
|
||||
|
||||
this.gunMaxClipCount = player.getGun().getMaxClipCount();
|
||||
this.gunClipSize = player.getGun().getClipSize();
|
||||
this.gunBulletsPerRound = player.getGun().getBulletsPerRound();
|
||||
this.gunCurrentClipBulletCount = player.getGun().getCurrentClipBulletCount();
|
||||
this.gunClipCount = player.getGun().getClipCount();
|
||||
}
|
||||
|
||||
private PlayerDetailUpdate(float health, boolean reloading, int gunMaxClipCount, int gunClipSize, int gunBulletsPerRound, int gunCurrentClipBulletCount, int gunClipCount) {
|
||||
this.health = health;
|
||||
this.reloading = reloading;
|
||||
this.gunMaxClipCount = gunMaxClipCount;
|
||||
this.gunClipSize = gunClipSize;
|
||||
this.gunBulletsPerRound = gunBulletsPerRound;
|
||||
this.gunCurrentClipBulletCount = gunCurrentClipBulletCount;
|
||||
this.gunClipCount = gunClipCount;
|
||||
}
|
||||
|
||||
public float getHealth() {
|
||||
return health;
|
||||
}
|
||||
|
||||
public boolean isReloading() {
|
||||
return reloading;
|
||||
}
|
||||
|
||||
public int getGunMaxClipCount() {
|
||||
return gunMaxClipCount;
|
||||
}
|
||||
|
||||
public int getGunClipSize() {
|
||||
return gunClipSize;
|
||||
}
|
||||
|
||||
public int getGunBulletsPerRound() {
|
||||
return gunBulletsPerRound;
|
||||
}
|
||||
|
||||
public int getGunCurrentClipBulletCount() {
|
||||
return gunCurrentClipBulletCount;
|
||||
}
|
||||
|
||||
public int getGunClipCount() {
|
||||
return gunClipCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "PlayerDetailUpdate{" +
|
||||
"health=" + health +
|
||||
", reloading=" + reloading +
|
||||
", gunMaxClipCount=" + gunMaxClipCount +
|
||||
", gunClipSize=" + gunClipSize +
|
||||
", gunBulletsPerRound=" + gunBulletsPerRound +
|
||||
", gunCurrentClipBulletCount=" + gunCurrentClipBulletCount +
|
||||
", gunClipCount=" + gunClipCount +
|
||||
'}';
|
||||
}
|
||||
|
||||
public byte[] toBytes() {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(BYTES);
|
||||
buffer.putFloat(health);
|
||||
buffer.put((byte) (this.reloading ? 1 : 0));
|
||||
buffer.putInt(this.gunMaxClipCount);
|
||||
buffer.putInt(this.gunClipSize);
|
||||
buffer.putInt(this.gunBulletsPerRound);
|
||||
buffer.putInt(this.gunCurrentClipBulletCount);
|
||||
buffer.putInt(this.gunClipCount);
|
||||
return buffer.array();
|
||||
}
|
||||
|
||||
public static PlayerDetailUpdate fromBytes(byte[] bytes) {
|
||||
ByteBuffer buffer = ByteBuffer.wrap(bytes);
|
||||
return new PlayerDetailUpdate(
|
||||
buffer.getFloat(),
|
||||
buffer.get() == 1,
|
||||
buffer.getInt(),
|
||||
buffer.getInt(),
|
||||
buffer.getInt(),
|
||||
buffer.getInt(),
|
||||
buffer.getInt()
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
package nl.andrewlalis.aos_core.net.data;
|
||||
|
||||
import nl.andrewlalis.aos_core.geom.Vec2;
|
||||
import nl.andrewlalis.aos_core.model.Player;
|
||||
import nl.andrewlalis.aos_core.model.tools.GunType;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
public class PlayerUpdate {
|
||||
public static final int BYTES = Integer.BYTES + 6 * Float.BYTES + 1;
|
||||
|
||||
private final int id;
|
||||
private final Vec2 position;
|
||||
private final Vec2 orientation;
|
||||
private final Vec2 velocity;
|
||||
private final GunType gunType;
|
||||
|
||||
public PlayerUpdate(Player player) {
|
||||
this.id = player.getId();
|
||||
this.position = player.getPosition();
|
||||
this.orientation = player.getOrientation();
|
||||
this.velocity = player.getVelocity();
|
||||
this.gunType = player.getGun().getType();
|
||||
}
|
||||
|
||||
public PlayerUpdate(int id, Vec2 position, Vec2 orientation, Vec2 velocity, GunType gunType) {
|
||||
this.id = id;
|
||||
this.position = position;
|
||||
this.orientation = orientation;
|
||||
this.velocity = velocity;
|
||||
this.gunType = gunType;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public Vec2 getPosition() {
|
||||
return position;
|
||||
}
|
||||
|
||||
public Vec2 getOrientation() {
|
||||
return orientation;
|
||||
}
|
||||
|
||||
public Vec2 getVelocity() {
|
||||
return velocity;
|
||||
}
|
||||
|
||||
public GunType getGunType() {
|
||||
return gunType;
|
||||
}
|
||||
|
||||
public void write(DataOutputStream out) throws IOException {
|
||||
out.writeInt(this.id);
|
||||
out.writeFloat(this.position.x());
|
||||
out.writeFloat(this.position.y());
|
||||
out.writeFloat(this.orientation.x());
|
||||
out.writeFloat(this.orientation.y());
|
||||
out.writeFloat(this.velocity.x());
|
||||
out.writeFloat(this.velocity.y());
|
||||
out.writeByte(this.gunType.getCode());
|
||||
}
|
||||
|
||||
public static PlayerUpdate read(DataInputStream in) throws IOException {
|
||||
return new PlayerUpdate(
|
||||
in.readInt(),
|
||||
Vec2.read(in),
|
||||
Vec2.read(in),
|
||||
Vec2.read(in),
|
||||
GunType.get(in.readByte())
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
package nl.andrewlalis.aos_core.net.data;
|
||||
|
||||
import nl.andrewlalis.aos_core.geom.Vec2;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
public class Sound {
|
||||
public static final int BYTES = 3 * Float.BYTES + 1;
|
||||
|
||||
private final Vec2 position;
|
||||
private final float volume;
|
||||
private final SoundType type;
|
||||
|
||||
public Sound(Vec2 position, float volume, SoundType type) {
|
||||
this.position = position;
|
||||
this.volume = volume;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public Vec2 getPosition() {
|
||||
return position;
|
||||
}
|
||||
|
||||
public float getVolume() {
|
||||
return volume;
|
||||
}
|
||||
|
||||
public SoundType getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
public void write(DataOutputStream out) throws IOException {
|
||||
out.writeFloat(this.position.x());
|
||||
out.writeFloat(this.position.y());
|
||||
out.writeFloat(this.volume);
|
||||
out.writeByte(this.type.getCode());
|
||||
}
|
||||
|
||||
public static Sound read(DataInputStream in) throws IOException {
|
||||
return new Sound(
|
||||
Vec2.read(in),
|
||||
in.readFloat(),
|
||||
SoundType.get(in.readByte())
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
package nl.andrewlalis.aos_core.net.data;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Encoding of all server-initiated game sounds with a unique byte value, for
|
||||
* efficient transmission to clients.
|
||||
*/
|
||||
public enum SoundType {
|
||||
SHOT_SMG(0, "ak47shot1.wav"),
|
||||
SHOT_RIFLE(1, "m1garand-shot1.wav"),
|
||||
SHOT_SHOTGUN(2, "shotgun-shot1.wav"),
|
||||
RELOAD(3, "reload.wav"),
|
||||
CHAT(4, "chat.wav"),
|
||||
DEATH(5, "death.wav"),
|
||||
BULLET_IMPACT_1(6, "bullet_impact_1.wav"),
|
||||
BULLET_IMPACT_2(7, "bullet_impact_2.wav"),
|
||||
BULLET_IMPACT_3(8, "bullet_impact_3.wav"),
|
||||
BULLET_IMPACT_4(9, "bullet_impact_4.wav"),
|
||||
BULLET_IMPACT_5(10, "bullet_impact_5.wav");
|
||||
|
||||
private final byte code;
|
||||
private final String soundName;
|
||||
|
||||
SoundType(int code, String soundName) {
|
||||
this.code = (byte) code;
|
||||
this.soundName = soundName;
|
||||
}
|
||||
|
||||
public byte getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getSoundName() {
|
||||
return soundName;
|
||||
}
|
||||
|
||||
private static final Map<Byte, SoundType> typeIndex = new HashMap<>();
|
||||
static {
|
||||
for (var val : values()) {
|
||||
typeIndex.put(val.getCode(), val);
|
||||
}
|
||||
}
|
||||
|
||||
public static SoundType get(byte code) {
|
||||
return typeIndex.get(code);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
package nl.andrewlalis.aos_core.net.data;
|
||||
|
||||
import nl.andrewlalis.aos_core.model.Bullet;
|
||||
import nl.andrewlalis.aos_core.model.Player;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The minimal data that's sent to each client after every game tick. This
|
||||
* contains the most basic information on updates to bullets, object movement,
|
||||
* and sounds that need to be played, and other simple things.
|
||||
* <p>
|
||||
* This update doesn't contain all data about players and the world, and
|
||||
* this extra data is sent periodically to keep clients up-to-date without
|
||||
* sending too much data.
|
||||
* </p>
|
||||
*/
|
||||
public class WorldUpdate {
|
||||
private final List<PlayerUpdate> playerUpdates;
|
||||
private final List<BulletUpdate> bulletUpdates;
|
||||
private final List<Sound> soundsToPlay;
|
||||
|
||||
public WorldUpdate() {
|
||||
this(new ArrayList<>(), new ArrayList<>(), new ArrayList<>());
|
||||
}
|
||||
|
||||
private WorldUpdate(List<PlayerUpdate> playerUpdates, List<BulletUpdate> bulletUpdates, List<Sound> soundsToPlay) {
|
||||
this.playerUpdates = playerUpdates;
|
||||
this.bulletUpdates = bulletUpdates;
|
||||
this.soundsToPlay = soundsToPlay;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
this.playerUpdates.clear();
|
||||
this.bulletUpdates.clear();
|
||||
this.soundsToPlay.clear();
|
||||
}
|
||||
|
||||
public void addPlayer(Player p) {
|
||||
this.playerUpdates.add(new PlayerUpdate(p));
|
||||
}
|
||||
|
||||
public void addBullet(Bullet b) {
|
||||
this.bulletUpdates.add(new BulletUpdate(b));
|
||||
}
|
||||
|
||||
public void addSound(Sound sound) {
|
||||
this.soundsToPlay.add(sound);
|
||||
}
|
||||
|
||||
public List<PlayerUpdate> getPlayerUpdates() {
|
||||
return playerUpdates;
|
||||
}
|
||||
|
||||
public List<BulletUpdate> getBulletUpdates() {
|
||||
return bulletUpdates;
|
||||
}
|
||||
|
||||
public List<Sound> getSoundsToPlay() {
|
||||
return soundsToPlay;
|
||||
}
|
||||
|
||||
public byte[] toBytes() throws IOException {
|
||||
int size = 3 * Integer.BYTES + // List size integers.
|
||||
this.playerUpdates.size() * PlayerUpdate.BYTES +
|
||||
this.bulletUpdates.size() * BulletUpdate.BYTES +
|
||||
this.soundsToPlay.size() * Sound.BYTES;
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream(size);
|
||||
DataOutputStream dataOut = new DataOutputStream(out);
|
||||
dataOut.writeInt(this.playerUpdates.size());
|
||||
for (var u : this.playerUpdates) {
|
||||
u.write(dataOut);
|
||||
}
|
||||
dataOut.writeInt(this.bulletUpdates.size());
|
||||
for (var u : this.bulletUpdates) {
|
||||
u.write(dataOut);
|
||||
}
|
||||
dataOut.writeInt(this.soundsToPlay.size());
|
||||
for (var u : this.soundsToPlay) {
|
||||
u.write(dataOut);
|
||||
}
|
||||
byte[] data = out.toByteArray();
|
||||
dataOut.close();
|
||||
return data;
|
||||
}
|
||||
|
||||
public static WorldUpdate fromBytes(byte[] data) throws IOException {
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(data);
|
||||
DataInputStream dataIn = new DataInputStream(in);
|
||||
|
||||
int players = dataIn.readInt();
|
||||
List<PlayerUpdate> playerUpdates = new ArrayList<>(players);
|
||||
for (int i = 0; i < players; i++) {
|
||||
playerUpdates.add(PlayerUpdate.read(dataIn));
|
||||
}
|
||||
int bullets = dataIn.readInt();
|
||||
List<BulletUpdate> bulletUpdates = new ArrayList<>(bullets);
|
||||
for (int i = 0; i < bullets; i++) {
|
||||
bulletUpdates.add(BulletUpdate.read(dataIn));
|
||||
}
|
||||
int sounds = dataIn.readInt();
|
||||
List<Sound> soundsToPlay = new ArrayList<>(sounds);
|
||||
for (int i = 0; i < sounds; i++) {
|
||||
soundsToPlay.add(Sound.read(dataIn));
|
||||
}
|
||||
var obj = new WorldUpdate(playerUpdates, bulletUpdates, soundsToPlay);
|
||||
dataIn.close();
|
||||
return obj;
|
||||
}
|
||||
}
|
|
@ -45,4 +45,11 @@ public class ByteUtils {
|
|||
if (n != length) throw new IOException("Could not read enough bytes to read string.");
|
||||
return new String(strBytes, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
public static byte[] prefix(byte pre, byte[] data) {
|
||||
byte[] full = new byte[data.length + 1];
|
||||
full[0] = pre;
|
||||
System.arraycopy(data, 0, full, 1, data.length);
|
||||
return full;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
package nl.andrewlalis.aos_server;
|
||||
|
||||
import nl.andrewlalis.aos_core.model.Player;
|
||||
import nl.andrewlalis.aos_core.net.chat.ChatMessage;
|
||||
import nl.andrewlalis.aos_core.net.chat.PlayerChatMessage;
|
||||
import nl.andrewlalis.aos_server.command.ResetCommand;
|
||||
import nl.andrewlalis.aos_server.command.chat.ChatCommand;
|
||||
import nl.andrewlalis.aos_server.command.chat.GunCommand;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* This chat manager is responsible for dealing with incoming player chats and
|
||||
* potentially executing commands, or simply relaying the chats on to the rest
|
||||
* of the players.
|
||||
*/
|
||||
public class ChatManager {
|
||||
private final Server server;
|
||||
private final Map<String, ChatCommand> chatCommands;
|
||||
|
||||
public ChatManager(Server server) {
|
||||
this.server = server;
|
||||
this.chatCommands = new ConcurrentHashMap<>();
|
||||
this.chatCommands.put("gun", new GunCommand());
|
||||
this.chatCommands.put("reset", new ResetCommand(server));
|
||||
}
|
||||
|
||||
public void handlePlayerChat(ClientHandler handler, Player player, ChatMessage msg) {
|
||||
if (player == null) return;
|
||||
if (msg.getText().startsWith("/")) {
|
||||
String[] words = msg.getText().substring(1).split("\\s+");
|
||||
if (words.length == 0) return;
|
||||
String command = words[0];
|
||||
ChatCommand cmd = this.chatCommands.get(command);
|
||||
if (cmd != null) {
|
||||
cmd.execute(handler, player, Arrays.copyOfRange(words, 1, words.length));
|
||||
}
|
||||
} else {
|
||||
this.server.broadcastMessage(new PlayerChatMessage(player.getId(), msg.getText()));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,15 +1,19 @@
|
|||
package nl.andrewlalis.aos_server;
|
||||
|
||||
import nl.andrewlalis.aos_core.model.Player;
|
||||
import nl.andrewlalis.aos_core.net.IdentMessage;
|
||||
import nl.andrewlalis.aos_core.net.Message;
|
||||
import nl.andrewlalis.aos_core.net.PlayerControlStateMessage;
|
||||
import nl.andrewlalis.aos_core.net.PlayerRegisteredMessage;
|
||||
import nl.andrewlalis.aos_core.net.Type;
|
||||
import nl.andrewlalis.aos_core.net.chat.ChatMessage;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
|
@ -23,29 +27,41 @@ public class ClientHandler extends Thread {
|
|||
private final ObjectOutputStream out;
|
||||
private final ObjectInputStream in;
|
||||
|
||||
private int playerId;
|
||||
private Player player;
|
||||
private final InetAddress clientAddress;
|
||||
private int clientUdpPort = -1;
|
||||
|
||||
private volatile boolean running = true;
|
||||
|
||||
public ClientHandler(Server server, Socket socket) throws IOException {
|
||||
this.server = server;
|
||||
this.socket = socket;
|
||||
this.clientAddress = this.socket.getInetAddress();
|
||||
this.out = new ObjectOutputStream(socket.getOutputStream());
|
||||
this.in = new ObjectInputStream(socket.getInputStream());
|
||||
}
|
||||
|
||||
public int getPlayerId() {
|
||||
return playerId;
|
||||
public Player getPlayer() {
|
||||
return player;
|
||||
}
|
||||
|
||||
public InetAddress getClientAddress() {
|
||||
return clientAddress;
|
||||
}
|
||||
|
||||
public int getClientUdpPort() {
|
||||
return clientUdpPort;
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
this.running = false;
|
||||
this.sendingQueue.shutdown();
|
||||
try {
|
||||
this.in.close();
|
||||
this.out.close();
|
||||
this.socket.close();
|
||||
} catch (IOException e) {
|
||||
System.err.println("Could not close streams when shutting down client handler for player " + this.playerId + ": " + e.getMessage());
|
||||
System.err.println("Could not close streams when shutting down client handler for player " + this.player.getId() + ": " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -63,29 +79,29 @@ public class ClientHandler extends Thread {
|
|||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
while (this.running) {
|
||||
try {
|
||||
Message msg = (Message) this.in.readObject();
|
||||
if (msg.getType() == Type.IDENT) {
|
||||
IdentMessage ident = (IdentMessage) msg;
|
||||
this.playerId = this.server.registerNewPlayer(ident.getName(), this);
|
||||
this.player = this.server.registerNewPlayer(ident.getName());
|
||||
this.clientUdpPort = ident.getUdpPort();
|
||||
this.send(new PlayerRegisteredMessage(this.player, this.server.getWorld()));
|
||||
} else if (msg.getType() == Type.CHAT) {
|
||||
this.server.handlePlayerChat(this, this.playerId, (ChatMessage) msg);
|
||||
} else if (msg.getType() == Type.PLAYER_CONTROL_STATE) {
|
||||
this.server.updatePlayerState(((PlayerControlStateMessage) msg).getPlayerControlState());
|
||||
this.server.getChatManager().handlePlayerChat(this, this.player, (ChatMessage) msg);
|
||||
}
|
||||
} catch (ClassNotFoundException e) {
|
||||
} catch (SocketException e) {
|
||||
if (e.getMessage().equals("Socket closed")) {
|
||||
this.shutdown();
|
||||
} else {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// Ignore this exception, consider the client disconnected.
|
||||
}
|
||||
try {
|
||||
this.socket.close();
|
||||
} catch (IOException e) {
|
||||
} catch (EOFException e) {
|
||||
this.shutdown();
|
||||
} catch (IOException | ClassNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
this.shutdown();
|
||||
}
|
||||
}
|
||||
this.server.clientDisconnected(this);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
package nl.andrewlalis.aos_server;
|
||||
|
||||
import nl.andrewlalis.aos_core.model.PlayerControlState;
|
||||
import nl.andrewlalis.aos_core.net.data.DataTypes;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.DatagramPacket;
|
||||
import java.net.DatagramSocket;
|
||||
import java.net.InetAddress;
|
||||
import java.net.SocketException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class DataTransceiver extends Thread {
|
||||
private final DatagramSocket socket;
|
||||
private final Server server;
|
||||
|
||||
private volatile boolean running;
|
||||
|
||||
public DataTransceiver(Server server, int port) throws SocketException {
|
||||
this.socket = new DatagramSocket(port);
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
public void send(byte[] bytes, InetAddress address, int port) {
|
||||
DatagramPacket packet = new DatagramPacket(bytes, bytes.length, address, port);
|
||||
try {
|
||||
this.socket.send(packet);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
this.running = false;
|
||||
if (!this.socket.isClosed()) {
|
||||
this.socket.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
this.running = true;
|
||||
byte[] buffer = new byte[1400];
|
||||
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
|
||||
while (this.running) {
|
||||
try {
|
||||
this.socket.receive(packet);
|
||||
ByteBuffer b = ByteBuffer.wrap(packet.getData(), 0, packet.getLength());
|
||||
byte type = b.get();
|
||||
int playerId = b.getInt();
|
||||
if (type == DataTypes.PLAYER_CONTROL_STATE) {
|
||||
if (playerId < 1) continue;
|
||||
byte[] stateBuffer = new byte[b.remaining()];
|
||||
b.get(stateBuffer);
|
||||
this.server.updatePlayerState(playerId, PlayerControlState.fromBytes(stateBuffer));
|
||||
}
|
||||
} catch (SocketException e) {
|
||||
if (!e.getMessage().equals("Socket closed")) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,20 +2,20 @@ package nl.andrewlalis.aos_server;
|
|||
|
||||
import nl.andrewlalis.aos_core.geom.Vec2;
|
||||
import nl.andrewlalis.aos_core.model.*;
|
||||
import nl.andrewlalis.aos_core.model.tools.Gun;
|
||||
import nl.andrewlalis.aos_core.net.Message;
|
||||
import nl.andrewlalis.aos_core.net.PlayerRegisteredMessage;
|
||||
import nl.andrewlalis.aos_core.net.WorldUpdateMessage;
|
||||
import nl.andrewlalis.aos_core.net.chat.ChatMessage;
|
||||
import nl.andrewlalis.aos_core.net.chat.PlayerChatMessage;
|
||||
import nl.andrewlalis.aos_core.net.PlayerUpdateMessage;
|
||||
import nl.andrewlalis.aos_core.net.Type;
|
||||
import nl.andrewlalis.aos_core.net.chat.SystemChatMessage;
|
||||
import nl.andrewlalis.aos_core.net.data.DataTypes;
|
||||
import nl.andrewlalis.aos_core.net.data.PlayerDetailUpdate;
|
||||
import nl.andrewlalis.aos_core.net.data.WorldUpdate;
|
||||
import nl.andrewlalis.aos_core.util.ByteUtils;
|
||||
|
||||
import java.awt.*;
|
||||
import java.io.IOException;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Scanner;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
@ -26,19 +26,23 @@ public class Server {
|
|||
|
||||
private final List<ClientHandler> clientHandlers;
|
||||
private final ServerSocket serverSocket;
|
||||
private final DataTransceiver dataTransceiver;
|
||||
private final World world;
|
||||
private final WorldUpdater worldUpdater;
|
||||
private final ServerCli cli;
|
||||
private final ChatManager chatManager;
|
||||
|
||||
private volatile boolean running;
|
||||
|
||||
public Server(int port) throws IOException {
|
||||
this.clientHandlers = new CopyOnWriteArrayList<>();
|
||||
this.serverSocket = new ServerSocket(port);
|
||||
this.dataTransceiver = new DataTransceiver(this, port);
|
||||
this.cli = new ServerCli(this);
|
||||
this.world = new World(new Vec2(50, 70));
|
||||
this.initWorld();
|
||||
this.worldUpdater = new WorldUpdater(this, this.world);
|
||||
this.chatManager = new ChatManager(this);
|
||||
}
|
||||
|
||||
private void initWorld() {
|
||||
|
@ -68,6 +72,10 @@ public class Server {
|
|||
return world;
|
||||
}
|
||||
|
||||
public ChatManager getChatManager() {
|
||||
return chatManager;
|
||||
}
|
||||
|
||||
public void acceptClientConnection() {
|
||||
try {
|
||||
Socket socket = this.serverSocket.accept();
|
||||
|
@ -82,7 +90,7 @@ public class Server {
|
|||
}
|
||||
}
|
||||
|
||||
public int registerNewPlayer(String name, ClientHandler handler) {
|
||||
public Player registerNewPlayer(String name) {
|
||||
int id = ThreadLocalRandom.current().nextInt(1, Integer.MAX_VALUE);
|
||||
Team team = null;
|
||||
for (Team t : this.world.getTeams()) {
|
||||
|
@ -94,11 +102,10 @@ public class Server {
|
|||
}
|
||||
Player p = new Player(id, name, team);
|
||||
this.world.getPlayers().put(p.getId(), p);
|
||||
handler.send(new PlayerRegisteredMessage(id));
|
||||
String message = p.getName() + " connected.";
|
||||
this.broadcastMessage(new SystemChatMessage(SystemChatMessage.Level.INFO, message));
|
||||
System.out.println(message);
|
||||
p.setPosition(new Vec2(this.world.getSize().x() / 2.0, this.world.getSize().y() / 2.0));
|
||||
p.setPosition(new Vec2(this.world.getSize().x() / 2.0f, this.world.getSize().y() / 2.0f));
|
||||
if (team != null) {
|
||||
team.getPlayers().add(p);
|
||||
p.setPosition(team.getSpawnPoint());
|
||||
|
@ -106,11 +113,12 @@ public class Server {
|
|||
message = name + " joined team " + team.getName() + ".";
|
||||
this.broadcastMessage(new SystemChatMessage(SystemChatMessage.Level.INFO, message));
|
||||
}
|
||||
return id;
|
||||
this.broadcastMessage(new PlayerUpdateMessage(Type.PLAYER_JOINED, p));
|
||||
return p;
|
||||
}
|
||||
|
||||
public void clientDisconnected(ClientHandler clientHandler) {
|
||||
Player player = this.world.getPlayers().get(clientHandler.getPlayerId());
|
||||
Player player = clientHandler.getPlayer();
|
||||
this.clientHandlers.remove(clientHandler);
|
||||
clientHandler.shutdown();
|
||||
this.world.getPlayers().remove(player.getId());
|
||||
|
@ -120,25 +128,37 @@ public class Server {
|
|||
String message = player.getName() + " disconnected.";
|
||||
this.broadcastMessage(new SystemChatMessage(SystemChatMessage.Level.INFO, message));
|
||||
System.out.println(message);
|
||||
this.broadcastMessage(new PlayerUpdateMessage(Type.PLAYER_LEFT, player));
|
||||
}
|
||||
|
||||
public void kickPlayer(Player player) {
|
||||
for (ClientHandler handler : this.clientHandlers) {
|
||||
if (handler.getPlayerId() == player.getId()) {
|
||||
if (handler.getPlayer().getId() == player.getId()) {
|
||||
handler.shutdown();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void sendWorldToClients() {
|
||||
public void sendWorldUpdate(WorldUpdate update) {
|
||||
try {
|
||||
byte[] data = update.toBytes();
|
||||
byte[] finalData = new byte[data.length + 1];
|
||||
finalData[0] = DataTypes.WORLD_DATA;
|
||||
System.arraycopy(data, 0, finalData, 1, data.length);
|
||||
for (ClientHandler handler : this.clientHandlers) {
|
||||
handler.send(new WorldUpdateMessage(this.world));
|
||||
if (handler.getClientUdpPort() == -1) continue;
|
||||
this.dataTransceiver.send(finalData, handler.getClientAddress(), handler.getClientUdpPort());
|
||||
byte[] detailData = ByteUtils.prefix(DataTypes.PLAYER_DETAIL, new PlayerDetailUpdate(handler.getPlayer()).toBytes());
|
||||
this.dataTransceiver.send(detailData, handler.getClientAddress(), handler.getClientUdpPort());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void updatePlayerState(PlayerControlState state) {
|
||||
Player p = this.world.getPlayers().get(state.getPlayerId());
|
||||
public void updatePlayerState(int playerId, PlayerControlState state) {
|
||||
Player p = this.world.getPlayers().get(playerId);
|
||||
if (p != null) {
|
||||
p.setState(state);
|
||||
}
|
||||
|
@ -151,6 +171,7 @@ public class Server {
|
|||
p.respawn();
|
||||
}
|
||||
}
|
||||
broadcastMessage(new SystemChatMessage(SystemChatMessage.Level.INFO, "Game has been reset."));
|
||||
}
|
||||
|
||||
public void broadcastMessage(Message message) {
|
||||
|
@ -159,40 +180,6 @@ public class Server {
|
|||
}
|
||||
}
|
||||
|
||||
public void handlePlayerChat(ClientHandler handler, int playerId, ChatMessage msg) {
|
||||
Player p = this.world.getPlayers().get(playerId);
|
||||
if (p == null) return;
|
||||
if (msg.getText().startsWith("/")) {
|
||||
String[] words = msg.getText().substring(1).split("\\s+");
|
||||
if (words.length == 0) return;
|
||||
String command = words[0];
|
||||
String[] args = Arrays.copyOfRange(words, 1, words.length);
|
||||
this.handleCommand(handler, p, command, args);
|
||||
} else {
|
||||
this.broadcastMessage(new PlayerChatMessage(p.getId(), msg.getText()));
|
||||
}
|
||||
}
|
||||
|
||||
public void handleCommand(ClientHandler handler, Player player, String command, String[] args) {
|
||||
if (command.equalsIgnoreCase("gun")) {
|
||||
if (args.length < 1) {
|
||||
return;
|
||||
}
|
||||
String gunName = args[0];
|
||||
if (gunName.equalsIgnoreCase("smg")) {
|
||||
player.setGun(Gun.ak47());
|
||||
} else if (gunName.equalsIgnoreCase("rifle")) {
|
||||
player.setGun(Gun.m1Garand());
|
||||
} else if (gunName.equalsIgnoreCase("shotgun")) {
|
||||
player.setGun(Gun.winchester());
|
||||
}
|
||||
handler.send(new SystemChatMessage(SystemChatMessage.Level.INFO, "Changed gun to " + player.getGun().getType().name() + "."));
|
||||
} else if (command.equalsIgnoreCase("reset")) {
|
||||
this.resetGame();
|
||||
this.broadcastMessage(new SystemChatMessage(SystemChatMessage.Level.INFO, "Game has been reset."));
|
||||
}
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
this.running = false;
|
||||
try {
|
||||
|
@ -207,17 +194,21 @@ public class Server {
|
|||
|
||||
public void run() {
|
||||
this.running = true;
|
||||
this.dataTransceiver.start();
|
||||
this.worldUpdater.start();
|
||||
this.cli.start();
|
||||
System.out.println("Started AOS-Server TCP on port " + this.serverSocket.getLocalPort() + "; now accepting connections.");
|
||||
while (this.running) {
|
||||
this.acceptClientConnection();
|
||||
}
|
||||
this.shutdown();
|
||||
System.out.println("Stopped accepting new client connections.");
|
||||
this.worldUpdater.shutdown();
|
||||
System.out.println("Stopped world updater.");
|
||||
this.cli.shutdown();
|
||||
System.out.println("Stopped CLI interface.");
|
||||
this.dataTransceiver.shutdown();
|
||||
System.out.println("Stopped data transceiver.");
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -4,6 +4,9 @@ import nl.andrewlalis.aos_core.geom.Vec2;
|
|||
import nl.andrewlalis.aos_core.model.*;
|
||||
import nl.andrewlalis.aos_core.model.tools.GunType;
|
||||
import nl.andrewlalis.aos_core.net.chat.SystemChatMessage;
|
||||
import nl.andrewlalis.aos_core.net.data.Sound;
|
||||
import nl.andrewlalis.aos_core.net.data.SoundType;
|
||||
import nl.andrewlalis.aos_core.net.data.WorldUpdate;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
@ -15,11 +18,15 @@ public class WorldUpdater extends Thread {
|
|||
|
||||
private final Server server;
|
||||
private final World world;
|
||||
|
||||
private final WorldUpdate worldUpdate;
|
||||
|
||||
private volatile boolean running = true;
|
||||
|
||||
public WorldUpdater(Server server, World world) {
|
||||
this.server = server;
|
||||
this.world = world;
|
||||
this.worldUpdate = new WorldUpdate();
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
|
@ -33,7 +40,7 @@ public class WorldUpdater extends Thread {
|
|||
long now = System.currentTimeMillis();
|
||||
long msSinceLastTick = now - lastTick;
|
||||
if (msSinceLastTick >= MS_PER_TICK) {
|
||||
double elapsedSeconds = msSinceLastTick / 1000.0;
|
||||
float elapsedSeconds = msSinceLastTick / 1000.0f;
|
||||
this.tick(elapsedSeconds);
|
||||
lastTick = now;
|
||||
}
|
||||
|
@ -48,21 +55,22 @@ public class WorldUpdater extends Thread {
|
|||
}
|
||||
}
|
||||
|
||||
private void tick(double t) {
|
||||
world.getSoundsToPlay().clear();
|
||||
private void tick(float t) {
|
||||
this.worldUpdate.clear();
|
||||
this.updateBullets(t);
|
||||
this.updatePlayers(t);
|
||||
this.server.sendWorldToClients();
|
||||
this.server.sendWorldUpdate(this.worldUpdate);
|
||||
}
|
||||
|
||||
private void updatePlayers(double t) {
|
||||
private void updatePlayers(float t) {
|
||||
for (Player p : this.world.getPlayers().values()) {
|
||||
this.updatePlayerMovement(p, t);
|
||||
this.updatePlayerShooting(p);
|
||||
this.worldUpdate.addPlayer(p);
|
||||
}
|
||||
}
|
||||
|
||||
private void updatePlayerMovement(Player p, double t) {
|
||||
private void updatePlayerMovement(Player p, float t) {
|
||||
if (p.getState().getMouseLocation() != null && p.getState().getMouseLocation().mag() > 0) {
|
||||
Vec2 newOrientation = p.getState().getMouseLocation().unit();
|
||||
if (p.getTeam() != null) {
|
||||
|
@ -71,8 +79,8 @@ public class WorldUpdater extends Thread {
|
|||
}
|
||||
p.setOrientation(newOrientation);
|
||||
}
|
||||
double vx = 0;
|
||||
double vy = 0;
|
||||
float vx = 0;
|
||||
float vy = 0;
|
||||
if (p.getState().isMovingForward()) vy += Player.MOVEMENT_SPEED;
|
||||
if (p.getState().isMovingBackward()) vy -= Player.MOVEMENT_SPEED;
|
||||
if (p.getState().isMovingLeft()) vx -= Player.MOVEMENT_SPEED;
|
||||
|
@ -83,15 +91,15 @@ public class WorldUpdater extends Thread {
|
|||
}
|
||||
Vec2 leftVector = forwardVector.perp();
|
||||
Vec2 newPos = p.getPosition().add(forwardVector.mul(vy * t)).add(leftVector.mul(vx * t));
|
||||
double nx = newPos.x();
|
||||
double ny = newPos.y();
|
||||
float nx = newPos.x();
|
||||
float ny = newPos.y();
|
||||
|
||||
for (Barricade b : world.getBarricades()) {
|
||||
// TODO: Improve barricade collision smoothness.
|
||||
double x1 = b.getPosition().x();
|
||||
double x2 = x1 + b.getSize().x();
|
||||
double y1 = b.getPosition().y();
|
||||
double y2 = y1 + b.getSize().y();
|
||||
float x1 = b.getPosition().x();
|
||||
float x2 = x1 + b.getSize().x();
|
||||
float y1 = b.getPosition().y();
|
||||
float y2 = y1 + b.getSize().y();
|
||||
if (nx + Player.RADIUS > x1 && nx - Player.RADIUS < x2 && ny + Player.RADIUS > y1 && ny - Player.RADIUS < y2) {
|
||||
double distanceLeft = Math.abs(nx - x1);
|
||||
double distanceRight = Math.abs(nx - x2);
|
||||
|
@ -123,15 +131,17 @@ public class WorldUpdater extends Thread {
|
|||
private void updatePlayerShooting(Player p) {
|
||||
if (p.canUseWeapon()) {
|
||||
for (int i = 0; i < p.getGun().getBulletsPerRound(); i++) {
|
||||
this.world.getBullets().add(new Bullet(p));
|
||||
Bullet b = new Bullet(p);
|
||||
this.world.getBullets().add(b);
|
||||
this.worldUpdate.addBullet(b);
|
||||
}
|
||||
String sound = "ak47shot1.wav";
|
||||
SoundType soundType = SoundType.SHOT_SMG;
|
||||
if (p.getGun().getType() == GunType.RIFLE) {
|
||||
sound = "m1garand-shot1.wav";
|
||||
soundType = SoundType.SHOT_RIFLE;
|
||||
} else if (p.getGun().getType() == GunType.SHOTGUN) {
|
||||
sound = "shotgun-shot1.wav";
|
||||
soundType = SoundType.SHOT_SHOTGUN;
|
||||
}
|
||||
this.world.getSoundsToPlay().add(sound);
|
||||
this.worldUpdate.addSound(new Sound(p.getPosition(), 1.0f, soundType));
|
||||
p.useWeapon();
|
||||
}
|
||||
if (p.getState().isReloading() && !p.isReloading() && p.getGun().canReload()) {
|
||||
|
@ -139,11 +149,11 @@ public class WorldUpdater extends Thread {
|
|||
}
|
||||
if (p.isReloading() && p.isReloadingComplete()) {
|
||||
p.finishReloading();
|
||||
this.world.getSoundsToPlay().add("reload.wav");
|
||||
this.worldUpdate.addSound(new Sound(p.getPosition(), 1.0f, SoundType.RELOAD));
|
||||
}
|
||||
}
|
||||
|
||||
private void updateBullets(double t) {
|
||||
private void updateBullets(float t) {
|
||||
List<Bullet> bulletsToRemove = new ArrayList<>();
|
||||
for (Bullet b : this.world.getBullets()) {
|
||||
Vec2 oldPos = b.getPosition();
|
||||
|
@ -151,26 +161,30 @@ public class WorldUpdater extends Thread {
|
|||
Vec2 pos = b.getPosition();
|
||||
if (pos.x() < 0 || pos.y() < 0 || pos.x() > this.world.getSize().x() || pos.y() > this.world.getSize().y()) {
|
||||
bulletsToRemove.add(b);
|
||||
continue;
|
||||
}
|
||||
boolean removed = false;
|
||||
for (Barricade bar : this.world.getBarricades()) {
|
||||
if (
|
||||
pos.x() > bar.getPosition().x() && pos.x() < bar.getPosition().x() + bar.getSize().x() &&
|
||||
pos.y() > bar.getPosition().y() && pos.y() < bar.getPosition().y() + bar.getSize().y()
|
||||
) {
|
||||
int n = ThreadLocalRandom.current().nextInt(1, 6);
|
||||
this.world.getSoundsToPlay().add("bullet_impact_" + n + ".wav");
|
||||
int code = ThreadLocalRandom.current().nextInt(SoundType.BULLET_IMPACT_1.getCode(), SoundType.BULLET_IMPACT_5.getCode() + 1);
|
||||
this.worldUpdate.addSound(new Sound(b.getPosition(), 1.0f, SoundType.get((byte) code)));
|
||||
bulletsToRemove.add(b);
|
||||
removed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (removed) continue;
|
||||
|
||||
double x1 = oldPos.x();
|
||||
double x2 = b.getPosition().x();
|
||||
double y1 = oldPos.y();
|
||||
double y2 = b.getPosition().y();
|
||||
double lineDist = oldPos.dist(b.getPosition());
|
||||
float x1 = oldPos.x();
|
||||
float x2 = b.getPosition().x();
|
||||
float y1 = oldPos.y();
|
||||
float y2 = b.getPosition().y();
|
||||
float lineDist = oldPos.dist(b.getPosition());
|
||||
for (Player p : this.world.getPlayers().values()) {
|
||||
double n = ((p.getPosition().x() - x1) * (x2 - x1) + (p.getPosition().y() - y1) * (y2 - y1)) / lineDist;
|
||||
float n = ((p.getPosition().x() - x1) * (x2 - x1) + (p.getPosition().y() - y1) * (y2 - y1)) / lineDist;
|
||||
n = Math.max(Math.min(n, 1), 0);
|
||||
double dist = p.getPosition().dist(new Vec2(x1 + n * (x2 - x1), y1 + n * (y2 - y1)));
|
||||
if (dist < Player.RADIUS && (p.getTeam() == null || p.getTeam().getSpawnPoint().dist(p.getPosition()) > Team.SPAWN_RADIUS)) {
|
||||
|
@ -181,7 +195,7 @@ public class WorldUpdater extends Thread {
|
|||
if (p.getHealth() == 0.0f) {
|
||||
Player shooter = this.world.getPlayers().get(b.getPlayerId());
|
||||
this.server.broadcastMessage(new SystemChatMessage(SystemChatMessage.Level.SEVERE, p.getName() + " was shot by " + shooter.getName() + "."));
|
||||
world.getSoundsToPlay().add("death.wav");
|
||||
this.worldUpdate.addSound(new Sound(p.getPosition(), 1.0f, SoundType.DEATH));
|
||||
if (shooter.getTeam() != null) {
|
||||
shooter.getTeam().incrementScore();
|
||||
}
|
||||
|
@ -189,6 +203,7 @@ public class WorldUpdater extends Thread {
|
|||
}
|
||||
}
|
||||
}
|
||||
this.worldUpdate.addBullet(b);
|
||||
}
|
||||
this.world.getBullets().removeAll(bulletsToRemove);
|
||||
}
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
package nl.andrewlalis.aos_server.command;
|
||||
|
||||
import nl.andrewlalis.aos_core.model.Player;
|
||||
import nl.andrewlalis.aos_server.ClientHandler;
|
||||
import nl.andrewlalis.aos_server.Server;
|
||||
import nl.andrewlalis.aos_server.command.chat.ChatCommand;
|
||||
|
||||
public class ResetCommand implements Command {
|
||||
public class ResetCommand implements Command, ChatCommand {
|
||||
private final Server server;
|
||||
|
||||
public ResetCommand(Server server) {
|
||||
|
@ -14,4 +17,9 @@ public class ResetCommand implements Command {
|
|||
this.server.resetGame();
|
||||
System.out.println("Reset the game.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(ClientHandler handler, Player player, String[] args) {
|
||||
this.server.resetGame();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
package nl.andrewlalis.aos_server.command.chat;
|
||||
|
||||
import nl.andrewlalis.aos_core.model.Player;
|
||||
import nl.andrewlalis.aos_server.ClientHandler;
|
||||
|
||||
public interface ChatCommand {
|
||||
void execute(ClientHandler handler, Player player, String[] args);
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package nl.andrewlalis.aos_server.command.chat;
|
||||
|
||||
import nl.andrewlalis.aos_core.model.Player;
|
||||
import nl.andrewlalis.aos_core.model.tools.Gun;
|
||||
import nl.andrewlalis.aos_core.net.chat.SystemChatMessage;
|
||||
import nl.andrewlalis.aos_server.ClientHandler;
|
||||
|
||||
public class GunCommand implements ChatCommand {
|
||||
@Override
|
||||
public void execute(ClientHandler handler, Player player, String[] args) {
|
||||
if (args.length < 1) {
|
||||
return;
|
||||
}
|
||||
String gunName = args[0];
|
||||
if (gunName.equalsIgnoreCase("smg")) {
|
||||
player.setGun(Gun.ak47());
|
||||
} else if (gunName.equalsIgnoreCase("rifle")) {
|
||||
player.setGun(Gun.m1Garand());
|
||||
} else if (gunName.equalsIgnoreCase("shotgun")) {
|
||||
player.setGun(Gun.winchester());
|
||||
}
|
||||
handler.send(new SystemChatMessage(SystemChatMessage.Level.INFO, "Changed gun to " + player.getGun().getType().name() + "."));
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue