Added better sound system.
This commit is contained in:
parent
0526527fff
commit
5814d92e54
|
@ -9,7 +9,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.aos2_client.sound.SoundSource;
|
||||
import nl.andrewl.aos_core.config.Config;
|
||||
import nl.andrewl.aos_core.model.Player;
|
||||
import nl.andrewl.aos_core.model.Projectile;
|
||||
|
@ -38,14 +37,13 @@ public class Client implements Runnable {
|
|||
private final InputHandler inputHandler;
|
||||
private GameRenderer gameRenderer;
|
||||
private SoundManager soundManager;
|
||||
private SoundSource playerSource;
|
||||
private long lastPlayerUpdate = 0;
|
||||
|
||||
private ClientWorld world;
|
||||
private ClientPlayer myPlayer;
|
||||
private final Map<Integer, OtherPlayer> players;
|
||||
private final Map<Integer, Projectile> projectiles;
|
||||
private Map<Integer, Team> teams;
|
||||
private final Map<Integer, Team> teams;
|
||||
|
||||
public Client(ClientConfig config) {
|
||||
this.config = config;
|
||||
|
@ -89,17 +87,18 @@ public class Client implements Runnable {
|
|||
new PlayerInputMouseClickCallback(inputHandler)
|
||||
);
|
||||
soundManager = new SoundManager();
|
||||
soundManager.load("rifle", "sound/m1garand-shot1.wav");
|
||||
playerSource = new SoundSource();
|
||||
log.debug("Sound system initialized.");
|
||||
|
||||
long lastFrameAt = System.currentTimeMillis();
|
||||
while (!gameRenderer.windowShouldClose() && !communicationHandler.isDone()) {
|
||||
long now = System.currentTimeMillis();
|
||||
float dt = (now - lastFrameAt) / 1000f;
|
||||
world.processQueuedChunkUpdates();
|
||||
soundManager.updateListener(myPlayer.getPosition(), myPlayer.getVelocity());
|
||||
gameRenderer.getCamera().interpolatePosition(dt);
|
||||
interpolatePlayers(dt);
|
||||
interpolatePlayers(now, dt);
|
||||
interpolateProjectiles(dt);
|
||||
soundManager.playWalkingSounds(myPlayer, now);
|
||||
gameRenderer.draw();
|
||||
lastFrameAt = now;
|
||||
}
|
||||
|
@ -156,7 +155,7 @@ public class Client implements Runnable {
|
|||
} else if (msg instanceof PlayerLeaveMessage leaveMessage) {
|
||||
players.remove(leaveMessage.id());
|
||||
} else if (msg instanceof SoundMessage soundMessage) {
|
||||
playerSource.play(soundManager.getSoundBuffer(soundMessage.name()));
|
||||
soundManager.play(soundMessage.name(), soundMessage.gain(), new Vector3f(soundMessage.px(), soundMessage.py(), soundMessage.pz()));
|
||||
} else if (msg instanceof ProjectileMessage pm) {
|
||||
Projectile p = projectiles.get(pm.id());
|
||||
if (p == null && !pm.destroyed()) {
|
||||
|
@ -186,10 +185,6 @@ public class Client implements Runnable {
|
|||
return teams;
|
||||
}
|
||||
|
||||
public void setTeams(Map<Integer, Team> teams) {
|
||||
this.teams = teams;
|
||||
}
|
||||
|
||||
public Map<Integer, OtherPlayer> getPlayers() {
|
||||
return players;
|
||||
}
|
||||
|
@ -198,12 +193,13 @@ public class Client implements Runnable {
|
|||
return projectiles;
|
||||
}
|
||||
|
||||
public void interpolatePlayers(float dt) {
|
||||
public void interpolatePlayers(long now, float dt) {
|
||||
Vector3f movement = new Vector3f();
|
||||
for (var player : players.values()) {
|
||||
movement.set(player.getVelocity()).mul(dt);
|
||||
player.getPosition().add(movement);
|
||||
player.updateModelTransform();
|
||||
soundManager.playWalkingSounds(player, now);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,31 +1,81 @@
|
|||
package nl.andrewl.aos2_client.sound;
|
||||
|
||||
import nl.andrewl.aos_core.model.Player;
|
||||
import org.joml.Vector3f;
|
||||
import org.lwjgl.openal.AL;
|
||||
import org.lwjgl.openal.ALC;
|
||||
import org.lwjgl.openal.ALCCapabilities;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.IntBuffer;
|
||||
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.ThreadLocalRandom;
|
||||
|
||||
import static org.lwjgl.openal.AL10.*;
|
||||
import static org.lwjgl.openal.AL11.*;
|
||||
import static org.lwjgl.openal.ALC10.*;
|
||||
|
||||
/**
|
||||
* Main class for managing the OpenAL audio interface.
|
||||
*/
|
||||
public class SoundManager {
|
||||
private static final Logger log = LoggerFactory.getLogger(SoundManager.class);
|
||||
|
||||
private static final int SOURCE_COUNT = 16;
|
||||
|
||||
private final long alContext;
|
||||
private final Map<String, Integer> audioBuffers = new HashMap<>();
|
||||
|
||||
/**
|
||||
* A set of pre-allocated sound sources that can be utilized when short
|
||||
* sounds need to be played.
|
||||
*/
|
||||
private final List<SoundSource> availableSources = new ArrayList<>(SOURCE_COUNT);
|
||||
|
||||
private final Map<Player, Long> lastPlayerWalkingSounds = new ConcurrentHashMap<>();
|
||||
|
||||
public SoundManager() {
|
||||
long device = alcOpenDevice((ByteBuffer) null);
|
||||
ALCCapabilities capabilities = ALC.createCapabilities(device);
|
||||
alContext = alcCreateContext(device, (IntBuffer) null);
|
||||
alcMakeContextCurrent(alContext);
|
||||
AL.createCapabilities(capabilities);
|
||||
|
||||
alDistanceModel(AL_EXPONENT_DISTANCE);
|
||||
|
||||
for (int i = 0; i < SOURCE_COUNT; i++) {
|
||||
SoundSource source = new SoundSource();
|
||||
alSourcef(source.getId(), AL_ROLLOFF_FACTOR, 1);
|
||||
alSourcef(source.getId(), AL_REFERENCE_DISTANCE, 10);
|
||||
alSourcef(source.getId(), AL_MAX_DISTANCE, 20);
|
||||
availableSources.add(source);
|
||||
}
|
||||
|
||||
loadSounds();
|
||||
}
|
||||
|
||||
private void loadSounds() {
|
||||
load("footsteps_1", "sound/m_footsteps_1.wav");
|
||||
load("footsteps_2", "sound/m_footsteps_2.wav");
|
||||
load("footsteps_3", "sound/m_footsteps_3.wav");
|
||||
load("footsteps_4", "sound/m_footsteps_4.wav");
|
||||
load("shot_m1-garand_1", "sound/m_shot_m1-garand_1.wav");
|
||||
load("shot_ak-47_1", "sound/m_shot_ak-47_1.wav");
|
||||
load("bullet_impact_1", "sound/m_bullet_impact_1.wav");
|
||||
load("bullet_impact_2", "sound/m_bullet_impact_2.wav");
|
||||
load("bullet_impact_3", "sound/m_bullet_impact_3.wav");
|
||||
load("bullet_impact_4", "sound/m_bullet_impact_4.wav");
|
||||
load("bullet_impact_5", "sound/m_bullet_impact_5.wav");
|
||||
load("reload", "sound/m_reload.wav");
|
||||
load("death", "sound/m_death.wav");
|
||||
load("hurt_1", "sound/m_hurt_1.wav");
|
||||
load("hurt_2", "sound/m_hurt_2.wav");
|
||||
load("hurt_3", "sound/m_hurt_3.wav");
|
||||
}
|
||||
|
||||
public void load(String name, String resource) {
|
||||
|
@ -41,14 +91,58 @@ public class SoundManager {
|
|||
alListener3f(AL_VELOCITY, velocity.x(), velocity.y(), velocity.z());
|
||||
}
|
||||
|
||||
public int getSoundBuffer(String name) {
|
||||
public Integer getSoundBuffer(String name) {
|
||||
return audioBuffers.get(name);
|
||||
}
|
||||
|
||||
public void play(String soundName, float gain, Vector3f position, Vector3f velocity) {
|
||||
Integer bufferId = getSoundBuffer(soundName);
|
||||
if (bufferId == null) {
|
||||
log.warn("Attempted to play unknown sound \"{}\"", soundName);
|
||||
} else {
|
||||
SoundSource source = getNextAvailableSoundSource();
|
||||
if (source != null) {
|
||||
source.setPosition(position);
|
||||
source.setVelocity(velocity);
|
||||
source.setGain(gain);
|
||||
source.play(bufferId);
|
||||
} else {
|
||||
log.warn("Couldn't get an available sound source to play sound \"{}\"", soundName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void play(String soundName, float gain, Vector3f position) {
|
||||
play(soundName, gain, position, new Vector3f(0, 0, 0));
|
||||
}
|
||||
|
||||
public void playWalkingSounds(Player player, long now) {
|
||||
if (player.getVelocity().length() <= 0) return;
|
||||
long lastSoundAt = lastPlayerWalkingSounds.computeIfAbsent(player, p -> 0L);
|
||||
long delay = 500; // Delay in ms between footfalls.
|
||||
if (player.getVelocity().length() > 5) delay -= 150;
|
||||
if (player.getVelocity().length() < 3) delay += 150;
|
||||
if (lastSoundAt + delay < now) {
|
||||
int choice = ThreadLocalRandom.current().nextInt(1, 5);
|
||||
play("footsteps_" + choice, 0.5f, player.getPosition(), player.getVelocity());
|
||||
lastPlayerWalkingSounds.put(player, now);
|
||||
}
|
||||
}
|
||||
|
||||
private SoundSource getNextAvailableSoundSource() {
|
||||
for (var source : availableSources) {
|
||||
if (!source.isPlaying()) return source;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void free() {
|
||||
for (var bufferId : audioBuffers.values()) {
|
||||
alDeleteBuffers(bufferId);
|
||||
}
|
||||
for (var source : availableSources) {
|
||||
source.free();
|
||||
}
|
||||
alcDestroyContext(alContext);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package nl.andrewl.aos2_client.sound;
|
||||
|
||||
import org.joml.Vector3f;
|
||||
|
||||
import static org.lwjgl.openal.AL10.*;
|
||||
|
||||
public class SoundSource {
|
||||
|
@ -17,6 +19,30 @@ public class SoundSource {
|
|||
alSourcePlay(sourceId);
|
||||
}
|
||||
|
||||
public void setPosition(Vector3f pos) {
|
||||
alSource3f(sourceId, AL_POSITION, pos.x, pos.y, pos.z);
|
||||
}
|
||||
|
||||
public void setVelocity(Vector3f vel) {
|
||||
alSource3f(sourceId, AL_VELOCITY, vel.x, vel.y, vel.z);
|
||||
}
|
||||
|
||||
public void setDirection(Vector3f dir) {
|
||||
alSource3f(sourceId, AL_DIRECTION, dir.x, dir.y, dir.z);
|
||||
}
|
||||
|
||||
public void setGain(float gain) {
|
||||
alSourcef(sourceId, AL_GAIN, gain);
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return sourceId;
|
||||
}
|
||||
|
||||
public boolean isPlaying() {
|
||||
return alGetSourcei(sourceId, AL_SOURCE_STATE) == AL_PLAYING;
|
||||
}
|
||||
|
||||
public void free() {
|
||||
alDeleteSources(sourceId);
|
||||
}
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -84,6 +84,16 @@ public class World {
|
|||
setBlockAt(new Vector3f(x, y, z), block);
|
||||
}
|
||||
|
||||
public void setBlocksAt(int x1, int y1, int z1, int x2, int y2, int z2, byte block) {
|
||||
for (int x = x1; x <= x2; x++) {
|
||||
for (int y = y1; y <= y2; y++) {
|
||||
for (int z = z1; z <= z2; z++) {
|
||||
setBlockAt(x, y, z, block);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Chunk getChunkAt(Vector3i chunkPos) {
|
||||
return chunkMap.get(chunkPos);
|
||||
}
|
||||
|
|
|
@ -134,4 +134,21 @@ public final class Worlds {
|
|||
|
||||
return world;
|
||||
}
|
||||
|
||||
public static World flatWorld() {
|
||||
World world = new World(ColorPalette.rainbow());
|
||||
for (int x = 0; x < 5; x++) {
|
||||
for (int y = 0; y < 5; y++) {
|
||||
for (int z = 0; z < 12; z++) {
|
||||
world.addChunk(new Chunk(x, y, z));
|
||||
}
|
||||
}
|
||||
}
|
||||
world.setBlocksAt(0, 0, 0, 5 * Chunk.SIZE, 2 * Chunk.SIZE, 12 * Chunk.SIZE, (byte) 80);
|
||||
|
||||
world.setSpawnPoint("A", new Vector3f(5, 34, 5));
|
||||
world.setSpawnPoint("B", new Vector3f(5 * Chunk.SIZE - 5, 34, 12 * Chunk.SIZE - 5));
|
||||
|
||||
return world;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package nl.andrewl.aos_core.net.client;
|
||||
|
||||
import nl.andrewl.record_net.Message;
|
||||
import org.joml.Vector3f;
|
||||
|
||||
/**
|
||||
* A message that indicates that a sound has been emitted somewhere in the
|
||||
|
@ -8,5 +9,28 @@ import nl.andrewl.record_net.Message;
|
|||
*/
|
||||
public record SoundMessage(
|
||||
String name,
|
||||
float px, float py, float pz
|
||||
) implements Message {}
|
||||
float gain,
|
||||
float px, float py, float pz,
|
||||
float vx, float vy, float vz
|
||||
) implements Message {
|
||||
public SoundMessage(String name, float gain, Vector3f position, Vector3f velocity) {
|
||||
this(
|
||||
name,
|
||||
gain,
|
||||
position.x, position.y, position.z,
|
||||
velocity.x, velocity.y, velocity.z
|
||||
);
|
||||
}
|
||||
|
||||
public SoundMessage(String name, float gain, Vector3f position) {
|
||||
this(name, gain, position.x, position.y, position.z, 0, 0, 0);
|
||||
}
|
||||
|
||||
public Vector3f positionAsVec() {
|
||||
return new Vector3f(px, py, pz);
|
||||
}
|
||||
|
||||
public Vector3f velocityAsVec() {
|
||||
return new Vector3f(vx, vy, vz);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,10 +7,7 @@ import nl.andrewl.aos_core.model.item.BlockItemStack;
|
|||
import nl.andrewl.aos_core.model.item.Gun;
|
||||
import nl.andrewl.aos_core.model.item.GunItemStack;
|
||||
import nl.andrewl.aos_core.model.item.ItemStack;
|
||||
import nl.andrewl.aos_core.net.client.ClientHealthMessage;
|
||||
import nl.andrewl.aos_core.net.client.ItemStackMessage;
|
||||
import nl.andrewl.aos_core.net.client.PlayerJoinMessage;
|
||||
import nl.andrewl.aos_core.net.client.PlayerLeaveMessage;
|
||||
import nl.andrewl.aos_core.net.client.*;
|
||||
import nl.andrewl.aos_core.net.connect.DatagramInit;
|
||||
import nl.andrewl.record_net.Message;
|
||||
import org.joml.Vector3f;
|
||||
|
@ -154,6 +151,7 @@ public class PlayerManager {
|
|||
* @param player The player that died.
|
||||
*/
|
||||
public void playerKilled(ServerPlayer player) {
|
||||
Vector3f deathPosition = new Vector3f(player.getPosition());
|
||||
player.setPosition(getBestSpawnPoint(player));
|
||||
player.setVelocity(new Vector3f(0));
|
||||
player.setHealth(1);
|
||||
|
@ -171,6 +169,7 @@ public class PlayerManager {
|
|||
}
|
||||
handler.sendDatagramPacket(new ClientHealthMessage(player.getHealth()));
|
||||
broadcastUdpMessage(player.getUpdateMessage(System.currentTimeMillis()));
|
||||
broadcastUdpMessage(new SoundMessage("death", 1, deathPosition));
|
||||
// TODO: Team points or something.
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ import nl.andrewl.aos_core.model.Player;
|
|||
import nl.andrewl.aos_core.model.Projectile;
|
||||
import nl.andrewl.aos_core.model.world.Hit;
|
||||
import nl.andrewl.aos_core.net.client.ClientHealthMessage;
|
||||
import nl.andrewl.aos_core.net.client.SoundMessage;
|
||||
import nl.andrewl.aos_core.net.world.ChunkUpdateMessage;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Vector3f;
|
||||
|
@ -15,6 +16,7 @@ import java.util.HashMap;
|
|||
import java.util.LinkedList;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
/**
|
||||
* Component that manages the set of all active projectiles in the world, and
|
||||
|
@ -112,14 +114,16 @@ public class ProjectileManager {
|
|||
// Bullet struck the world first.
|
||||
server.getWorld().setBlockAt(hit.pos().x, hit.pos().y, hit.pos().z, (byte) 0);
|
||||
server.getPlayerManager().broadcastUdpMessage(ChunkUpdateMessage.fromWorld(hit.pos(), server.getWorld()));
|
||||
int soundVariant = ThreadLocalRandom.current().nextInt(1, 6);
|
||||
server.getPlayerManager().broadcastUdpMessage(new SoundMessage("bullet_impact_" + soundVariant, 1, hit.rawPos()));
|
||||
deleteProjectile(projectile);
|
||||
} else if (playerHit != null && (hit == null || playerHitDist < worldHitDist)) {
|
||||
// Bullet struck the player first.
|
||||
System.out.println("Player hit: " + playerHitType);
|
||||
float damage = 0.4f;
|
||||
if (playerHitType == 1) damage *= 2;
|
||||
hitPlayer.setHealth(hitPlayer.getHealth() - damage);
|
||||
System.out.println(hitPlayer.getHealth());
|
||||
int soundVariant = ThreadLocalRandom.current().nextInt(1, 4);
|
||||
server.getPlayerManager().broadcastUdpMessage(new SoundMessage("hurt_" + soundVariant, 1, hitPlayer.getPosition(), hitPlayer.getVelocity()));
|
||||
if (hitPlayer.getHealth() == 0) {
|
||||
System.out.println("Player killed!!!");
|
||||
server.getPlayerManager().playerKilled(hitPlayer);
|
||||
|
|
|
@ -45,7 +45,7 @@ public class Server implements Runnable {
|
|||
this.teamManager = new TeamManager(this);
|
||||
this.projectileManager = new ProjectileManager(this);
|
||||
this.worldUpdater = new WorldUpdater(this, config.ticksPerSecond);
|
||||
this.world = Worlds.testingWorld();
|
||||
this.world = Worlds.flatWorld();
|
||||
|
||||
// TODO: Add some way to configure teams with config files or commands.
|
||||
teamManager.addTeam("Red", new Vector3f(0.8f, 0, 0), "A");
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
package nl.andrewl.aos2_server.logic;
|
||||
|
||||
import nl.andrewl.aos2_server.Server;
|
||||
import nl.andrewl.aos2_server.model.ServerPlayer;
|
||||
import nl.andrewl.aos2_server.config.ServerConfig;
|
||||
import nl.andrewl.aos_core.model.item.*;
|
||||
import nl.andrewl.aos2_server.model.ServerPlayer;
|
||||
import nl.andrewl.aos_core.model.item.BlockItemStack;
|
||||
import nl.andrewl.aos_core.model.item.Gun;
|
||||
import nl.andrewl.aos_core.model.item.GunItemStack;
|
||||
import nl.andrewl.aos_core.model.item.ItemStack;
|
||||
import nl.andrewl.aos_core.model.item.gun.Ak47;
|
||||
import nl.andrewl.aos_core.model.item.gun.Rifle;
|
||||
import nl.andrewl.aos_core.model.world.World;
|
||||
import nl.andrewl.aos_core.net.client.ClientInputState;
|
||||
import nl.andrewl.aos_core.net.client.InventorySelectedStackMessage;
|
||||
|
@ -18,7 +23,7 @@ import org.joml.Vector3i;
|
|||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static nl.andrewl.aos2_server.model.ServerPlayer.*;
|
||||
import static nl.andrewl.aos2_server.model.ServerPlayer.RADIUS;
|
||||
|
||||
/**
|
||||
* Component that manages a server player's current actions and movement.
|
||||
|
@ -106,7 +111,13 @@ public class PlayerActionManager {
|
|||
gunNeedsReCock = true;
|
||||
}
|
||||
server.getPlayerManager().getHandler(player.getId()).sendDatagramPacket(new ItemStackMessage(player.getInventory()));
|
||||
server.getPlayerManager().broadcastUdpMessage(new SoundMessage("rifle", player.getPosition().x(), player.getPosition().y(), player.getPosition().z()));
|
||||
String shotSound = null;
|
||||
if (gun instanceof Rifle) {
|
||||
shotSound = "shot_m1-garand_1";
|
||||
} else if (gun instanceof Ak47) {
|
||||
shotSound = "shot_ak-47_1";
|
||||
}
|
||||
server.getPlayerManager().broadcastUdpMessage(new SoundMessage(shotSound, 1, player.getPosition(), player.getVelocity()));
|
||||
}
|
||||
|
||||
if (// Check to see if the player is reloading.
|
||||
|
@ -118,6 +129,7 @@ public class PlayerActionManager {
|
|||
gunReloadingStartedAt = now;
|
||||
gunReloading = true;
|
||||
server.getPlayerManager().getHandler(player.getId()).sendDatagramPacket(new ItemStackMessage(player.getInventory()));
|
||||
server.getPlayerManager().broadcastUdpMessage(new SoundMessage("reload", 1, player.getPosition(), player.getVelocity()));
|
||||
}
|
||||
|
||||
if (// Check to see if reloading is done.
|
||||
|
|
Loading…
Reference in New Issue