Added better sound system.

This commit is contained in:
Andrew Lalis 2022-07-26 14:13:37 +02:00
parent 0526527fff
commit 5814d92e54
48 changed files with 209 additions and 27 deletions

View File

@ -9,7 +9,6 @@ import nl.andrewl.aos2_client.model.ClientPlayer;
import nl.andrewl.aos2_client.model.OtherPlayer; import nl.andrewl.aos2_client.model.OtherPlayer;
import nl.andrewl.aos2_client.render.GameRenderer; import nl.andrewl.aos2_client.render.GameRenderer;
import nl.andrewl.aos2_client.sound.SoundManager; 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.config.Config;
import nl.andrewl.aos_core.model.Player; import nl.andrewl.aos_core.model.Player;
import nl.andrewl.aos_core.model.Projectile; import nl.andrewl.aos_core.model.Projectile;
@ -38,14 +37,13 @@ public class Client implements Runnable {
private final InputHandler inputHandler; private final InputHandler inputHandler;
private GameRenderer gameRenderer; private GameRenderer gameRenderer;
private SoundManager soundManager; private SoundManager soundManager;
private SoundSource playerSource;
private long lastPlayerUpdate = 0; private long lastPlayerUpdate = 0;
private ClientWorld world; private ClientWorld world;
private ClientPlayer myPlayer; private ClientPlayer myPlayer;
private final Map<Integer, OtherPlayer> players; private final Map<Integer, OtherPlayer> players;
private final Map<Integer, Projectile> projectiles; private final Map<Integer, Projectile> projectiles;
private Map<Integer, Team> teams; private final Map<Integer, Team> teams;
public Client(ClientConfig config) { public Client(ClientConfig config) {
this.config = config; this.config = config;
@ -89,17 +87,18 @@ public class Client implements Runnable {
new PlayerInputMouseClickCallback(inputHandler) new PlayerInputMouseClickCallback(inputHandler)
); );
soundManager = new SoundManager(); soundManager = new SoundManager();
soundManager.load("rifle", "sound/m1garand-shot1.wav"); log.debug("Sound system initialized.");
playerSource = new SoundSource();
long lastFrameAt = System.currentTimeMillis(); long lastFrameAt = System.currentTimeMillis();
while (!gameRenderer.windowShouldClose() && !communicationHandler.isDone()) { while (!gameRenderer.windowShouldClose() && !communicationHandler.isDone()) {
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
float dt = (now - lastFrameAt) / 1000f; float dt = (now - lastFrameAt) / 1000f;
world.processQueuedChunkUpdates(); world.processQueuedChunkUpdates();
soundManager.updateListener(myPlayer.getPosition(), myPlayer.getVelocity());
gameRenderer.getCamera().interpolatePosition(dt); gameRenderer.getCamera().interpolatePosition(dt);
interpolatePlayers(dt); interpolatePlayers(now, dt);
interpolateProjectiles(dt); interpolateProjectiles(dt);
soundManager.playWalkingSounds(myPlayer, now);
gameRenderer.draw(); gameRenderer.draw();
lastFrameAt = now; lastFrameAt = now;
} }
@ -156,7 +155,7 @@ public class Client implements Runnable {
} else if (msg instanceof PlayerLeaveMessage leaveMessage) { } else if (msg instanceof PlayerLeaveMessage leaveMessage) {
players.remove(leaveMessage.id()); players.remove(leaveMessage.id());
} else if (msg instanceof SoundMessage soundMessage) { } else if (msg instanceof SoundMessage soundMessage) {
playerSource.play(soundManager.getSoundBuffer(soundMessage.name())); soundManager.play(soundMessage.name(), soundMessage.gain(), new Vector3f(soundMessage.px(), soundMessage.py(), soundMessage.pz()));
} else if (msg instanceof ProjectileMessage pm) { } else if (msg instanceof ProjectileMessage pm) {
Projectile p = projectiles.get(pm.id()); Projectile p = projectiles.get(pm.id());
if (p == null && !pm.destroyed()) { if (p == null && !pm.destroyed()) {
@ -186,10 +185,6 @@ public class Client implements Runnable {
return teams; return teams;
} }
public void setTeams(Map<Integer, Team> teams) {
this.teams = teams;
}
public Map<Integer, OtherPlayer> getPlayers() { public Map<Integer, OtherPlayer> getPlayers() {
return players; return players;
} }
@ -198,12 +193,13 @@ public class Client implements Runnable {
return projectiles; return projectiles;
} }
public void interpolatePlayers(float dt) { public void interpolatePlayers(long now, float dt) {
Vector3f movement = new Vector3f(); Vector3f movement = new Vector3f();
for (var player : players.values()) { for (var player : players.values()) {
movement.set(player.getVelocity()).mul(dt); movement.set(player.getVelocity()).mul(dt);
player.getPosition().add(movement); player.getPosition().add(movement);
player.updateModelTransform(); player.updateModelTransform();
soundManager.playWalkingSounds(player, now);
} }
} }

View File

@ -1,31 +1,81 @@
package nl.andrewl.aos2_client.sound; package nl.andrewl.aos2_client.sound;
import nl.andrewl.aos_core.model.Player;
import org.joml.Vector3f; import org.joml.Vector3f;
import org.lwjgl.openal.AL; import org.lwjgl.openal.AL;
import org.lwjgl.openal.ALC; import org.lwjgl.openal.ALC;
import org.lwjgl.openal.ALCCapabilities; import org.lwjgl.openal.ALCCapabilities;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.IntBuffer; import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; 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.*; import static org.lwjgl.openal.ALC10.*;
/** /**
* Main class for managing the OpenAL audio interface. * Main class for managing the OpenAL audio interface.
*/ */
public class SoundManager { 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 long alContext;
private final Map<String, Integer> audioBuffers = new HashMap<>(); 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() { public SoundManager() {
long device = alcOpenDevice((ByteBuffer) null); long device = alcOpenDevice((ByteBuffer) null);
ALCCapabilities capabilities = ALC.createCapabilities(device); ALCCapabilities capabilities = ALC.createCapabilities(device);
alContext = alcCreateContext(device, (IntBuffer) null); alContext = alcCreateContext(device, (IntBuffer) null);
alcMakeContextCurrent(alContext); alcMakeContextCurrent(alContext);
AL.createCapabilities(capabilities); 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) { public void load(String name, String resource) {
@ -41,14 +91,58 @@ public class SoundManager {
alListener3f(AL_VELOCITY, velocity.x(), velocity.y(), velocity.z()); alListener3f(AL_VELOCITY, velocity.x(), velocity.y(), velocity.z());
} }
public int getSoundBuffer(String name) { public Integer getSoundBuffer(String name) {
return audioBuffers.get(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() { public void free() {
for (var bufferId : audioBuffers.values()) { for (var bufferId : audioBuffers.values()) {
alDeleteBuffers(bufferId); alDeleteBuffers(bufferId);
} }
for (var source : availableSources) {
source.free();
}
alcDestroyContext(alContext); alcDestroyContext(alContext);
} }
} }

View File

@ -1,5 +1,7 @@
package nl.andrewl.aos2_client.sound; package nl.andrewl.aos2_client.sound;
import org.joml.Vector3f;
import static org.lwjgl.openal.AL10.*; import static org.lwjgl.openal.AL10.*;
public class SoundSource { public class SoundSource {
@ -17,6 +19,30 @@ public class SoundSource {
alSourcePlay(sourceId); 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() { public void free() {
alDeleteSources(sourceId); 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.

View File

@ -84,6 +84,16 @@ public class World {
setBlockAt(new Vector3f(x, y, z), block); 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) { public Chunk getChunkAt(Vector3i chunkPos) {
return chunkMap.get(chunkPos); return chunkMap.get(chunkPos);
} }

View File

@ -134,4 +134,21 @@ public final class Worlds {
return world; 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;
}
} }

View File

@ -1,6 +1,7 @@
package nl.andrewl.aos_core.net.client; package nl.andrewl.aos_core.net.client;
import nl.andrewl.record_net.Message; import nl.andrewl.record_net.Message;
import org.joml.Vector3f;
/** /**
* A message that indicates that a sound has been emitted somewhere in the * 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( public record SoundMessage(
String name, String name,
float px, float py, float pz float gain,
) implements Message {} 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);
}
}

View File

@ -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.Gun;
import nl.andrewl.aos_core.model.item.GunItemStack; import nl.andrewl.aos_core.model.item.GunItemStack;
import nl.andrewl.aos_core.model.item.ItemStack; import nl.andrewl.aos_core.model.item.ItemStack;
import nl.andrewl.aos_core.net.client.ClientHealthMessage; import nl.andrewl.aos_core.net.client.*;
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.connect.DatagramInit; import nl.andrewl.aos_core.net.connect.DatagramInit;
import nl.andrewl.record_net.Message; import nl.andrewl.record_net.Message;
import org.joml.Vector3f; import org.joml.Vector3f;
@ -154,6 +151,7 @@ public class PlayerManager {
* @param player The player that died. * @param player The player that died.
*/ */
public void playerKilled(ServerPlayer player) { public void playerKilled(ServerPlayer player) {
Vector3f deathPosition = new Vector3f(player.getPosition());
player.setPosition(getBestSpawnPoint(player)); player.setPosition(getBestSpawnPoint(player));
player.setVelocity(new Vector3f(0)); player.setVelocity(new Vector3f(0));
player.setHealth(1); player.setHealth(1);
@ -171,6 +169,7 @@ public class PlayerManager {
} }
handler.sendDatagramPacket(new ClientHealthMessage(player.getHealth())); handler.sendDatagramPacket(new ClientHealthMessage(player.getHealth()));
broadcastUdpMessage(player.getUpdateMessage(System.currentTimeMillis())); broadcastUdpMessage(player.getUpdateMessage(System.currentTimeMillis()));
broadcastUdpMessage(new SoundMessage("death", 1, deathPosition));
// TODO: Team points or something. // TODO: Team points or something.
} }

View File

@ -7,6 +7,7 @@ import nl.andrewl.aos_core.model.Player;
import nl.andrewl.aos_core.model.Projectile; import nl.andrewl.aos_core.model.Projectile;
import nl.andrewl.aos_core.model.world.Hit; import nl.andrewl.aos_core.model.world.Hit;
import nl.andrewl.aos_core.net.client.ClientHealthMessage; 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 nl.andrewl.aos_core.net.world.ChunkUpdateMessage;
import org.joml.Matrix4f; import org.joml.Matrix4f;
import org.joml.Vector3f; import org.joml.Vector3f;
@ -15,6 +16,7 @@ import java.util.HashMap;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.Map; import java.util.Map;
import java.util.Queue; import java.util.Queue;
import java.util.concurrent.ThreadLocalRandom;
/** /**
* Component that manages the set of all active projectiles in the world, and * 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. // Bullet struck the world first.
server.getWorld().setBlockAt(hit.pos().x, hit.pos().y, hit.pos().z, (byte) 0); server.getWorld().setBlockAt(hit.pos().x, hit.pos().y, hit.pos().z, (byte) 0);
server.getPlayerManager().broadcastUdpMessage(ChunkUpdateMessage.fromWorld(hit.pos(), server.getWorld())); 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); deleteProjectile(projectile);
} else if (playerHit != null && (hit == null || playerHitDist < worldHitDist)) { } else if (playerHit != null && (hit == null || playerHitDist < worldHitDist)) {
// Bullet struck the player first. // Bullet struck the player first.
System.out.println("Player hit: " + playerHitType);
float damage = 0.4f; float damage = 0.4f;
if (playerHitType == 1) damage *= 2; if (playerHitType == 1) damage *= 2;
hitPlayer.setHealth(hitPlayer.getHealth() - damage); 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) { if (hitPlayer.getHealth() == 0) {
System.out.println("Player killed!!!"); System.out.println("Player killed!!!");
server.getPlayerManager().playerKilled(hitPlayer); server.getPlayerManager().playerKilled(hitPlayer);

View File

@ -45,7 +45,7 @@ public class Server implements Runnable {
this.teamManager = new TeamManager(this); this.teamManager = new TeamManager(this);
this.projectileManager = new ProjectileManager(this); this.projectileManager = new ProjectileManager(this);
this.worldUpdater = new WorldUpdater(this, config.ticksPerSecond); this.worldUpdater = new WorldUpdater(this, config.ticksPerSecond);
this.world = Worlds.testingWorld(); this.world = Worlds.flatWorld();
// TODO: Add some way to configure teams with config files or commands. // TODO: Add some way to configure teams with config files or commands.
teamManager.addTeam("Red", new Vector3f(0.8f, 0, 0), "A"); teamManager.addTeam("Red", new Vector3f(0.8f, 0, 0), "A");

View File

@ -1,9 +1,14 @@
package nl.andrewl.aos2_server.logic; package nl.andrewl.aos2_server.logic;
import nl.andrewl.aos2_server.Server; import nl.andrewl.aos2_server.Server;
import nl.andrewl.aos2_server.model.ServerPlayer;
import nl.andrewl.aos2_server.config.ServerConfig; import nl.andrewl.aos2_server.config.ServerConfig;
import nl.andrewl.aos_core.model.item.*; import nl.andrewl.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.model.world.World;
import nl.andrewl.aos_core.net.client.ClientInputState; import nl.andrewl.aos_core.net.client.ClientInputState;
import nl.andrewl.aos_core.net.client.InventorySelectedStackMessage; import nl.andrewl.aos_core.net.client.InventorySelectedStackMessage;
@ -18,7 +23,7 @@ import org.joml.Vector3i;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import static nl.andrewl.aos2_server.model.ServerPlayer.*; import static nl.andrewl.aos2_server.model.ServerPlayer.RADIUS;
/** /**
* Component that manages a server player's current actions and movement. * Component that manages a server player's current actions and movement.
@ -106,7 +111,13 @@ public class PlayerActionManager {
gunNeedsReCock = true; gunNeedsReCock = true;
} }
server.getPlayerManager().getHandler(player.getId()).sendDatagramPacket(new ItemStackMessage(player.getInventory())); 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. if (// Check to see if the player is reloading.
@ -118,6 +129,7 @@ public class PlayerActionManager {
gunReloadingStartedAt = now; gunReloadingStartedAt = now;
gunReloading = true; gunReloading = true;
server.getPlayerManager().getHandler(player.getId()).sendDatagramPacket(new ItemStackMessage(player.getInventory())); 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. if (// Check to see if reloading is done.