Added better client input handling.
This commit is contained in:
parent
7df8120c16
commit
e321979c3c
|
@ -7,7 +7,6 @@ import nl.andrewl.aos2_client.model.ClientPlayer;
|
|||
import nl.andrewl.aos2_client.model.OtherPlayer;
|
||||
import nl.andrewl.aos2_client.render.GameRenderer;
|
||||
import nl.andrewl.aos2_client.sound.SoundManager;
|
||||
import nl.andrewl.aos_core.FileUtils;
|
||||
import nl.andrewl.aos_core.config.Config;
|
||||
import nl.andrewl.aos_core.model.Player;
|
||||
import nl.andrewl.aos_core.model.Projectile;
|
||||
|
@ -137,7 +136,7 @@ public class Client implements Runnable {
|
|||
gameRenderer.getCamera().setToPlayer(myPlayer);
|
||||
}
|
||||
if (soundManager != null) {
|
||||
soundManager.updateListener(myPlayer.getPosition(), myPlayer.getVelocity());
|
||||
soundManager.updateListener(myPlayer.getEyePosition(), myPlayer.getVelocity());
|
||||
}
|
||||
lastPlayerUpdate = playerUpdate.timestamp();
|
||||
} else {
|
||||
|
@ -206,7 +205,7 @@ public class Client implements Runnable {
|
|||
} else if (msg instanceof ChatMessage chatMessage) {
|
||||
chat.chatReceived(chatMessage);
|
||||
if (soundManager != null) {
|
||||
soundManager.play("chat", 1, myPlayer.getPosition(), myPlayer.getVelocity());
|
||||
soundManager.play("chat", 1, myPlayer.getEyePosition(), myPlayer.getVelocity());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -268,10 +267,11 @@ public class Client implements Runnable {
|
|||
|
||||
public static void main(String[] args) throws IOException {
|
||||
List<Path> configPaths = Config.getCommonConfigPaths();
|
||||
configPaths.add(0, Path.of("client.yaml")); // Add this first so we create client.yaml if needed.
|
||||
if (args.length > 0) {
|
||||
configPaths.add(Path.of(args[0].trim()));
|
||||
}
|
||||
ClientConfig clientConfig = Config.loadConfig(ClientConfig.class, configPaths, new ClientConfig(), FileUtils.readClasspathFile("default-config.yaml"));
|
||||
ClientConfig clientConfig = Config.loadConfig(ClientConfig.class, configPaths, new ClientConfig(), "default-config.yaml");
|
||||
Client client = new Client(clientConfig);
|
||||
client.run();
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package nl.andrewl.aos_core.config;
|
||||
|
||||
import nl.andrewl.aos_core.FileUtils;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -19,7 +20,7 @@ public final class Config {
|
|||
* @param paths The paths to load from.
|
||||
* @param fallback A default configuration object to use if no config could
|
||||
* be loaded from any of the paths.
|
||||
* @param defaultConfigFile The default config file to save.
|
||||
* @param defaultConfigFile The default config file resource to save.
|
||||
* @return The configuration object.
|
||||
* @param <T> The type of the configuration object.
|
||||
*/
|
||||
|
@ -35,25 +36,13 @@ public final class Config {
|
|||
}
|
||||
Path outputPath = paths.size() > 0 ? paths.get(0) : Path.of("config.yaml");
|
||||
try (var writer = Files.newBufferedWriter(outputPath)) {
|
||||
writer.write(defaultConfigFile);
|
||||
writer.write(FileUtils.readClasspathFile(defaultConfigFile));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
public static <T> T loadConfig(Class<T> configType, List<Path> paths, String defaultConfigFile) {
|
||||
var cfg = loadConfig(configType, paths, null, defaultConfigFile);
|
||||
if (cfg == null) {
|
||||
throw new RuntimeException("Could not load config from any of the supplied paths.");
|
||||
}
|
||||
return cfg;
|
||||
}
|
||||
|
||||
public static <T> T loadConfig(Class<T> configType, T fallback, String defaultConfigFile, Path... paths) {
|
||||
return loadConfig(configType, List.of(paths), fallback, defaultConfigFile);
|
||||
}
|
||||
|
||||
public static List<Path> getCommonConfigPaths() {
|
||||
List<Path> paths = new ArrayList<>();
|
||||
paths.add(Path.of("config.yaml"));
|
||||
|
|
|
@ -17,7 +17,7 @@ import java.util.Map;
|
|||
* that players can interact in.
|
||||
*/
|
||||
public class World {
|
||||
private static final float DELTA = 0.01f;
|
||||
private static final float DELTA = 0.001f;
|
||||
|
||||
protected final Map<Vector3ic, Chunk> chunkMap = new HashMap<>();
|
||||
protected ColorPalette palette;
|
||||
|
@ -164,8 +164,12 @@ public class World {
|
|||
public Hit getLookingAtPos(Vector3f eyePos, Vector3f eyeDir, float limit) {
|
||||
if (eyeDir.lengthSquared() == 0 || limit <= 0) return null;
|
||||
Vector3f pos = new Vector3f(eyePos);
|
||||
Vector3f previousPos = new Vector3f();
|
||||
while (pos.distance(eyePos) < limit) {
|
||||
previousPos.set(pos);
|
||||
stepToNextBlock(pos, eyeDir);
|
||||
// If for some reason we couldn't advance to the next block, exit null, so we don't infinitely loop.
|
||||
if (pos.equals(previousPos)) return null;
|
||||
if (getBlockAt(pos) > 0) {
|
||||
Vector3i hitPos = new Vector3i(
|
||||
(int) Math.floor(pos.x),
|
||||
|
@ -234,7 +238,7 @@ public class World {
|
|||
// Testing code!
|
||||
if (diff == 0) {
|
||||
System.out.printf("n = %.8f, nextValue = %.8f, floor(n) - DELTA = %.8f%n", n, nextValue, Math.floor(n) - DELTA);
|
||||
throw new RuntimeException("EEK");
|
||||
return Float.MAX_VALUE;
|
||||
}
|
||||
return Math.abs(diff / dir);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
package nl.andrewl.aos_core.net.client;
|
||||
|
||||
import nl.andrewl.record_net.Message;
|
||||
|
||||
/**
|
||||
* A message that the server sends to clients, to tell them to update their
|
||||
* player's orientation to the specified values.
|
||||
*/
|
||||
public record ClientOrientationUpdateMessage(
|
||||
float x, float y
|
||||
) implements Message {}
|
|
@ -177,10 +177,11 @@ public class Server implements Runnable {
|
|||
|
||||
public static void main(String[] args) throws IOException {
|
||||
List<Path> configPaths = Config.getCommonConfigPaths();
|
||||
configPaths.add(0, Path.of("server.yaml"));
|
||||
if (args.length > 0) {
|
||||
configPaths.add(Path.of(args[0].trim()));
|
||||
}
|
||||
ServerConfig cfg = Config.loadConfig(ServerConfig.class, configPaths, new ServerConfig(), null);
|
||||
ServerConfig cfg = Config.loadConfig(ServerConfig.class, configPaths, new ServerConfig(), "default-config.yaml");
|
||||
Server server = new Server(cfg);
|
||||
new Thread(server).start();
|
||||
ServerCli.start(server);
|
||||
|
|
|
@ -29,8 +29,8 @@ import static nl.andrewl.aos2_server.model.ServerPlayer.RADIUS;
|
|||
*/
|
||||
public class PlayerActionManager {
|
||||
private final ServerPlayer player;
|
||||
private final PlayerInputTracker input;
|
||||
|
||||
private ClientInputState lastInputState;
|
||||
private long lastBlockRemovedAt = 0;
|
||||
private long lastBlockPlacedAt = 0;
|
||||
|
||||
|
@ -45,23 +45,15 @@ public class PlayerActionManager {
|
|||
|
||||
public PlayerActionManager(ServerPlayer player) {
|
||||
this.player = player;
|
||||
lastInputState = new ClientInputState(
|
||||
player.getId(),
|
||||
false, false, false, false,
|
||||
false, false, false,
|
||||
false, false, false,
|
||||
player.getInventory().getSelectedIndex()
|
||||
);
|
||||
this.input = new PlayerInputTracker(player);
|
||||
}
|
||||
|
||||
public ClientInputState getLastInputState() {
|
||||
return lastInputState;
|
||||
public PlayerInputTracker getInput() {
|
||||
return input;
|
||||
}
|
||||
|
||||
public boolean setLastInputState(ClientInputState lastInputState) {
|
||||
boolean change = !lastInputState.equals(this.lastInputState);
|
||||
if (change) this.lastInputState = lastInputState;
|
||||
return change;
|
||||
return input.setLastInputState(lastInputState);
|
||||
}
|
||||
|
||||
public boolean isUpdated() {
|
||||
|
@ -70,8 +62,8 @@ public class PlayerActionManager {
|
|||
|
||||
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.
|
||||
if (player.getInventory().getSelectedIndex() != lastInputState.selectedInventoryIndex()) {
|
||||
player.getInventory().setSelectedIndex(lastInputState.selectedInventoryIndex());
|
||||
if (player.getInventory().getSelectedIndex() != input.selectedInventoryIndex()) {
|
||||
player.getInventory().setSelectedIndex(input.selectedInventoryIndex());
|
||||
// Tell the client that their inventory slot has been updated properly.
|
||||
server.getPlayerManager().getHandler(player.getId()).sendDatagramPacket(new InventorySelectedStackMessage(player.getInventory().getSelectedIndex()));
|
||||
updated = true; // Tell everyone else that this player's selected item has changed.
|
||||
|
@ -81,7 +73,7 @@ public class PlayerActionManager {
|
|||
if (selectedStack instanceof BlockItemStack b) {
|
||||
tickBlockAction(now, server, world, b);
|
||||
} else if (selectedStack instanceof GunItemStack g) {
|
||||
tickGunAction(now, server, world, g);
|
||||
tickGunAction(now, server, g);
|
||||
}
|
||||
|
||||
if (
|
||||
|
@ -93,18 +85,19 @@ public class PlayerActionManager {
|
|||
lastResupplyAt = now;
|
||||
}
|
||||
|
||||
if (player.isCrouching() != lastInputState.crouching()) {
|
||||
player.setCrouching(lastInputState.crouching());
|
||||
if (player.isCrouching() != input.crouching()) {
|
||||
player.setCrouching(input.crouching());
|
||||
updated = true;
|
||||
}
|
||||
|
||||
tickMovement(dt, server, world, server.getConfig().physics);
|
||||
input.reset();
|
||||
}
|
||||
|
||||
private void tickGunAction(long now, Server server, World world, GunItemStack g) {
|
||||
private void tickGunAction(long now, Server server, GunItemStack g) {
|
||||
Gun gun = (Gun) g.getType();
|
||||
if (// Check to see if the player is shooting.
|
||||
lastInputState.hitting() &&
|
||||
input.hitting() &&
|
||||
g.getBulletCount() > 0 &&
|
||||
!gunReloading &&
|
||||
now - gunLastShotAt > gun.getShotCooldownTime() * 1000 &&
|
||||
|
@ -126,11 +119,14 @@ public class PlayerActionManager {
|
|||
} else if (gun instanceof Winchester) {
|
||||
shotSound = "shot_winchester_1";
|
||||
}
|
||||
server.getPlayerManager().broadcastUdpMessage(new SoundMessage(shotSound, 1, player.getPosition(), player.getVelocity()));
|
||||
Vector3f soundLocation = new Vector3f(player.getPosition());
|
||||
soundLocation.y += 1.4f;
|
||||
soundLocation.add(player.getViewVector());
|
||||
server.getPlayerManager().broadcastUdpMessage(new SoundMessage(shotSound, 1, soundLocation, player.getVelocity()));
|
||||
}
|
||||
|
||||
if (// Check to see if the player is reloading.
|
||||
lastInputState.reloading() &&
|
||||
input.reloading() &&
|
||||
!gunReloading &&
|
||||
g.getClipCount() > 0
|
||||
) {
|
||||
|
@ -151,7 +147,7 @@ public class PlayerActionManager {
|
|||
}
|
||||
|
||||
// Check to see if the player released the trigger, for non-automatic weapons.
|
||||
if (!gun.isAutomatic() && gunNeedsReCock && !lastInputState.hitting()) {
|
||||
if (!gun.isAutomatic() && gunNeedsReCock && !input.hitting()) {
|
||||
gunNeedsReCock = false;
|
||||
}
|
||||
}
|
||||
|
@ -159,7 +155,7 @@ public class PlayerActionManager {
|
|||
private void tickBlockAction(long now, Server server, World world, BlockItemStack stack) {
|
||||
// Check for breaking blocks.
|
||||
if (
|
||||
lastInputState.hitting() &&
|
||||
input.hitting() &&
|
||||
stack.getAmount() < stack.getType().getMaxAmount() &&
|
||||
now - lastBlockRemovedAt > server.getConfig().actions.blockBreakCooldown * 1000
|
||||
) {
|
||||
|
@ -175,7 +171,7 @@ public class PlayerActionManager {
|
|||
}
|
||||
// Check for placing blocks.
|
||||
if (
|
||||
lastInputState.interacting() &&
|
||||
input.interacting() &&
|
||||
stack.getAmount() > 0 &&
|
||||
now - lastBlockPlacedAt > server.getConfig().actions.blockPlaceCooldown * 1000
|
||||
) {
|
||||
|
@ -202,8 +198,8 @@ public class PlayerActionManager {
|
|||
tickHorizontalVelocity(config, grounded);
|
||||
|
||||
if (isGrounded(world)) {
|
||||
if (lastInputState.jumping()) {
|
||||
velocity.y = config.jumpVerticalSpeed * (lastInputState.sprinting() ? 1.25f : 1f);
|
||||
if (input.jumping()) {
|
||||
velocity.y = config.jumpVerticalSpeed * (input.sprinting() ? 1.25f : 1f);
|
||||
updated = true;
|
||||
}
|
||||
} else {
|
||||
|
@ -239,19 +235,19 @@ public class PlayerActionManager {
|
|||
velocity.z == velocity.z ? velocity.z : 0f
|
||||
);
|
||||
Vector3f acceleration = new Vector3f(0);
|
||||
if (lastInputState.forward()) acceleration.z -= 1;
|
||||
if (lastInputState.backward()) acceleration.z += 1;
|
||||
if (lastInputState.left()) acceleration.x -= 1;
|
||||
if (lastInputState.right()) acceleration.x += 1;
|
||||
if (input.forward()) acceleration.z -= 1;
|
||||
if (input.backward()) acceleration.z += 1;
|
||||
if (input.left()) acceleration.x -= 1;
|
||||
if (input.right()) acceleration.x += 1;
|
||||
if (acceleration.lengthSquared() > 0) {
|
||||
acceleration.normalize();
|
||||
acceleration.rotateAxis(orientation.x, 0, 1, 0);
|
||||
acceleration.mul(config.movementAcceleration);
|
||||
horizontalVelocity.add(acceleration);
|
||||
final float maxSpeed;
|
||||
if (lastInputState.crouching()) {
|
||||
if (input.crouching()) {
|
||||
maxSpeed = config.crouchingSpeed;
|
||||
} else if (lastInputState.sprinting()) {
|
||||
} else if (input.sprinting()) {
|
||||
maxSpeed = config.sprintingSpeed;
|
||||
} else {
|
||||
maxSpeed = config.walkingSpeed;
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
package nl.andrewl.aos2_server.logic;
|
||||
|
||||
import nl.andrewl.aos_core.net.client.ClientInputState;
|
||||
|
||||
public class PlayerImpulses {
|
||||
public boolean forward;
|
||||
public boolean backward;
|
||||
public boolean left;
|
||||
public boolean right;
|
||||
public boolean jumping;
|
||||
public boolean crouching;
|
||||
public boolean sprinting;
|
||||
public boolean hitting;
|
||||
public boolean interacting;
|
||||
public boolean reloading;
|
||||
|
||||
public void update(ClientInputState s) {
|
||||
forward = forward || s.forward();
|
||||
backward = backward || s.backward();
|
||||
left = left || s.left();
|
||||
right = right || s.right();
|
||||
jumping = jumping || s.jumping();
|
||||
crouching = crouching || s.crouching();
|
||||
sprinting = sprinting || s.sprinting();
|
||||
hitting = hitting || s.hitting();
|
||||
interacting = interacting || s.interacting();
|
||||
reloading = reloading || s.reloading();
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
forward = false;
|
||||
backward = false;
|
||||
left = false;
|
||||
right = false;
|
||||
jumping = false;
|
||||
crouching = false;
|
||||
sprinting = false;
|
||||
hitting = false;
|
||||
interacting = false;
|
||||
reloading = false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
package nl.andrewl.aos2_server.logic;
|
||||
|
||||
import nl.andrewl.aos2_server.model.ServerPlayer;
|
||||
import nl.andrewl.aos_core.net.client.ClientInputState;
|
||||
|
||||
/**
|
||||
* Wrapper around the various information we have about a player's input state,
|
||||
* including their last known state, and any impulses they've made since the
|
||||
* last tick.
|
||||
*/
|
||||
public class PlayerInputTracker {
|
||||
private ClientInputState lastInputState;
|
||||
private final PlayerImpulses impulsesSinceLastTick;
|
||||
|
||||
public PlayerInputTracker(ServerPlayer player) {
|
||||
lastInputState = new ClientInputState(
|
||||
player.getId(),
|
||||
false, false, false, false,
|
||||
false, false, false,
|
||||
false, false, false,
|
||||
player.getInventory().getSelectedIndex()
|
||||
);
|
||||
this.impulsesSinceLastTick = new PlayerImpulses();
|
||||
}
|
||||
|
||||
public boolean setLastInputState(ClientInputState lastInputState) {
|
||||
boolean updated = !lastInputState.equals(this.lastInputState);
|
||||
if (updated) {
|
||||
this.lastInputState = lastInputState;
|
||||
impulsesSinceLastTick.update(lastInputState);
|
||||
}
|
||||
return updated;
|
||||
}
|
||||
|
||||
public void reset() {
|
||||
impulsesSinceLastTick.reset();
|
||||
}
|
||||
|
||||
public boolean forward() {
|
||||
return lastInputState.forward() || impulsesSinceLastTick.forward;
|
||||
}
|
||||
|
||||
public boolean backward() {
|
||||
return lastInputState.backward() || impulsesSinceLastTick.backward;
|
||||
}
|
||||
|
||||
public boolean left() {
|
||||
return lastInputState.left() || impulsesSinceLastTick.left;
|
||||
}
|
||||
|
||||
public boolean right() {
|
||||
return lastInputState.right() || impulsesSinceLastTick.right;
|
||||
}
|
||||
|
||||
public boolean jumping() {
|
||||
return lastInputState.jumping() || impulsesSinceLastTick.jumping;
|
||||
}
|
||||
|
||||
public boolean crouching() {
|
||||
return lastInputState.crouching() || impulsesSinceLastTick.crouching;
|
||||
}
|
||||
|
||||
public boolean sprinting() {
|
||||
return lastInputState.sprinting() || impulsesSinceLastTick.sprinting;
|
||||
}
|
||||
|
||||
public boolean hitting() {
|
||||
return lastInputState.hitting() || impulsesSinceLastTick.hitting;
|
||||
}
|
||||
|
||||
public boolean interacting() {
|
||||
return lastInputState.interacting() || impulsesSinceLastTick.interacting;
|
||||
}
|
||||
|
||||
public boolean reloading() {
|
||||
return lastInputState.reloading() || impulsesSinceLastTick.reloading;
|
||||
}
|
||||
|
||||
public int selectedInventoryIndex() {
|
||||
return lastInputState.selectedInventoryIndex();
|
||||
}
|
||||
}
|
|
@ -6,7 +6,6 @@ import nl.andrewl.aos_core.model.item.BlockItemStack;
|
|||
import nl.andrewl.aos_core.model.item.GunItemStack;
|
||||
import nl.andrewl.aos_core.model.item.Inventory;
|
||||
import nl.andrewl.aos_core.model.item.ItemTypes;
|
||||
import nl.andrewl.aos_core.model.item.gun.Winchester;
|
||||
import nl.andrewl.aos_core.net.client.PlayerUpdateMessage;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -69,7 +68,7 @@ public class ServerPlayer extends Player {
|
|||
position.x, position.y, position.z,
|
||||
velocity.x, velocity.y, velocity.z,
|
||||
orientation.x, orientation.y,
|
||||
actionManager.getLastInputState().crouching(),
|
||||
actionManager.getInput().crouching(),
|
||||
inventory.getSelectedItemStack().getType().getId()
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
# Ace of Shades 2 Server Configuration
|
||||
port: 25565
|
||||
connectionBacklog: 5
|
||||
ticksPerSecond: 20.0
|
||||
world: worlds.redfort
|
||||
teams:
|
||||
- name: Red
|
||||
color: [0.8, 0, 0]
|
||||
spawnPoint: A
|
||||
- name: Blue
|
||||
color: [0, 0, 0.8]
|
||||
spawnPoint: B
|
||||
physics:
|
||||
gravity: 29.43
|
||||
walkingSpeed: 4
|
||||
crouchingSpeed: 1.5
|
||||
sprintingSpeed: 9
|
||||
movementAcceleration: 2
|
||||
movementDeceleration: 1
|
||||
jumpVerticalSpeed: 8
|
||||
actions:
|
||||
blockBreakCooldown: 0.25
|
||||
blockPlaceCooldown: 0.1
|
||||
blockBreakReach: 5
|
||||
blockPlaceReach: 5
|
||||
blockBulletDamageResistance: 3
|
||||
blockBulletDamageCooldown: 10
|
||||
resupplyCooldown: 30
|
||||
resupplyRadius: 3
|
||||
teamSpawnProtection: 10
|
||||
movementAccuracyDecreaseFactor: 0.01
|
||||
friendlyFire: false
|
Loading…
Reference in New Issue