diff --git a/client/src/main/java/nl/andrewl/aos2_client/Client.java b/client/src/main/java/nl/andrewl/aos2_client/Client.java index 4f3a4d4..7e3d748 100644 --- a/client/src/main/java/nl/andrewl/aos2_client/Client.java +++ b/client/src/main/java/nl/andrewl/aos2_client/Client.java @@ -8,6 +8,8 @@ import nl.andrewl.aos2_client.control.PlayerViewCursorCallback; 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.Team; @@ -34,6 +36,8 @@ public class Client implements Runnable { private final CommunicationHandler communicationHandler; private final InputHandler inputHandler; private GameRenderer gameRenderer; + private SoundManager soundManager; + private SoundSource playerSource; private long lastPlayerUpdate = 0; private ClientWorld world; @@ -81,6 +85,9 @@ public class Client implements Runnable { new PlayerInputKeyCallback(inputHandler), new PlayerInputMouseClickCallback(inputHandler) ); + soundManager = new SoundManager(); + soundManager.load("rifle", "sound/m1garand-shot1.wav"); + playerSource = new SoundSource(); long lastFrameAt = System.currentTimeMillis(); while (!gameRenderer.windowShouldClose()) { @@ -113,6 +120,7 @@ public class Client implements Runnable { if (gameRenderer != null) { gameRenderer.getCamera().setToPlayer(myPlayer); } + soundManager.updateListener(myPlayer.getPosition(), myPlayer.getVelocity()); lastPlayerUpdate = playerUpdate.timestamp(); } else { OtherPlayer p = players.get(playerUpdate.clientId()); @@ -141,6 +149,8 @@ public class Client implements Runnable { players.put(op.getId(), op); } else if (msg instanceof PlayerLeaveMessage leaveMessage) { players.remove(leaveMessage.id()); + } else if (msg instanceof SoundMessage soundMessage) { + playerSource.play(soundManager.getSoundBuffer(soundMessage.name())); } } diff --git a/client/src/main/java/nl/andrewl/aos2_client/control/InputHandler.java b/client/src/main/java/nl/andrewl/aos2_client/control/InputHandler.java index 8887fd0..b3d2fa8 100644 --- a/client/src/main/java/nl/andrewl/aos2_client/control/InputHandler.java +++ b/client/src/main/java/nl/andrewl/aos2_client/control/InputHandler.java @@ -39,6 +39,7 @@ public class InputHandler { glfwGetKey(window, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS, glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_1) == GLFW_PRESS, glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_2) == GLFW_PRESS, + glfwGetKey(window, GLFW_KEY_R) == GLFW_PRESS, selectedInventoryIndex ); if (!currentInputState.equals(lastInputState)) { diff --git a/client/src/main/java/nl/andrewl/aos2_client/render/GameRenderer.java b/client/src/main/java/nl/andrewl/aos2_client/render/GameRenderer.java index fb3ac2e..72e80b3 100644 --- a/client/src/main/java/nl/andrewl/aos2_client/render/GameRenderer.java +++ b/client/src/main/java/nl/andrewl/aos2_client/render/GameRenderer.java @@ -8,6 +8,8 @@ import nl.andrewl.aos2_client.render.gui.GUIRenderer; import nl.andrewl.aos2_client.render.gui.GUITexture; import nl.andrewl.aos2_client.render.model.Model; 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.ItemTypes; import org.joml.Matrix4f; import org.joml.Vector3f; @@ -44,6 +46,11 @@ public class GameRenderer { private Model rifleModel; private Model blockModel; + // Standard GUI textures. + private GUITexture crosshairTexture; + private GUITexture clipTexture; + private GUITexture bulletTexture; + private long windowHandle; private int screenWidth = 800; private int screenHeight = 600; @@ -110,17 +117,16 @@ public class GameRenderer { this.chunkRenderer = new ChunkRenderer(); log.debug("Initialized chunk renderer."); + this.guiRenderer = new GUIRenderer(); - // TODO: More organized way to load textures for GUI. - try { - var crosshairTexture = new GUITexture("gui/crosshair.png"); - float size = 32; - crosshairTexture.getScale().set(size / screenWidth, size / screenHeight); - guiRenderer.addTexture(crosshairTexture); - } catch (IOException e) { - throw new RuntimeException(e); - } + crosshairTexture = new GUITexture("gui/crosshair.png"); + clipTexture = new GUITexture("gui/clip.png"); + bulletTexture = new GUITexture("gui/bullet.png"); + guiRenderer.addTexture("crosshair", crosshairTexture); + guiRenderer.addTexture("clip", clipTexture); + guiRenderer.addTexture("bullet", bulletTexture); log.debug("Initialized GUI renderer."); + this.modelRenderer = new ModelRenderer(); try { playerModel = new Model("model/player_simple.obj", "model/simple_player.png"); @@ -212,7 +218,32 @@ public class GameRenderer { modelRenderer.end(); - guiRenderer.draw(); + // GUI rendering + guiRenderer.start(); + guiRenderer.draw(crosshairTexture, crosshairTexture.getIdealScaleX(32, screenWidth), crosshairTexture.getIdealScaleY(32, screenHeight), 0, 0); + // If we're holding a gun, draw clip and bullet graphics. + if (client.getMyPlayer().getInventory().getSelectedItemStack().getType() instanceof Gun) { + GunItemStack stack = (GunItemStack) client.getMyPlayer().getInventory().getSelectedItemStack(); + for (int i = 0; i < stack.getClipCount(); i++) { + guiRenderer.draw( + clipTexture, + clipTexture.getIdealScaleX(64, screenWidth), + clipTexture.getIdealScaleY(clipTexture.getIdealHeight(64), screenHeight), + 0.90f, + -0.90f + (i * 0.15f) + ); + } + for (int i = 0; i < stack.getBulletCount(); i++) { + guiRenderer.draw( + bulletTexture, + bulletTexture.getIdealScaleX(16, screenWidth), + bulletTexture.getIdealScaleY(bulletTexture.getIdealHeight(16), screenHeight), + 0.80f - (i * 0.05f), + -0.90f + ); + } + } + guiRenderer.end(); glfwSwapBuffers(windowHandle); glfwPollEvents(); diff --git a/client/src/main/java/nl/andrewl/aos2_client/render/gui/GUIRenderer.java b/client/src/main/java/nl/andrewl/aos2_client/render/gui/GUIRenderer.java index ba6dc99..0a85037 100644 --- a/client/src/main/java/nl/andrewl/aos2_client/render/gui/GUIRenderer.java +++ b/client/src/main/java/nl/andrewl/aos2_client/render/gui/GUIRenderer.java @@ -5,8 +5,8 @@ import org.joml.Matrix4f; import org.lwjgl.BufferUtils; import java.nio.FloatBuffer; -import java.util.ArrayList; -import java.util.List; +import java.util.HashMap; +import java.util.Map; import static org.lwjgl.opengl.GL46.*; @@ -19,9 +19,10 @@ public class GUIRenderer { private final int vertexCount; private final ShaderProgram shaderProgram; private final int transformUniformLocation; + private final Matrix4f transformMatrix; + private final float[] transformMatrixData; - - private final List guiTextures = new ArrayList<>(); + private final Map textures = new HashMap<>(); public GUIRenderer() { vaoId = glGenVertexArrays(); @@ -47,38 +48,52 @@ public class GUIRenderer { .build(); transformUniformLocation = shaderProgram.getUniform("transform"); shaderProgram.bindAttribute(0, "position"); + this.transformMatrix = new Matrix4f(); + this.transformMatrixData = new float[16]; } - public void addTexture(GUITexture texture) { - guiTextures.add(texture); + public void loadTexture(String name, String resource) { + textures.put(name, new GUITexture(resource)); } - public void draw() { + public void addTexture(String name, GUITexture texture) { + textures.put(name, texture); + } + + public void start() { shaderProgram.use(); glBindVertexArray(vaoId); glEnableVertexAttribArray(0); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glDisable(GL_DEPTH_TEST); - for (var texture : guiTextures) { - glActiveTexture(GL_TEXTURE0); - Matrix4f transform = new Matrix4f() - .translate(texture.getPosition().x, texture.getPosition().y, 0) - .scale(texture.getScale().x, texture.getScale().y, 1); - float[] transformData = new float[16]; - transform.get(transformData); - glUniformMatrix4fv(transformUniformLocation, false, transformData); - glBindTexture(GL_TEXTURE_2D, texture.getTextureId()); - glDrawArrays(GL_TRIANGLE_STRIP, 0, vertexCount); - } + } + + public void draw(String name, float scaleX, float scaleY, float x, float y) { + draw(textures.get(name), scaleX, scaleY, x, y); + } + + public void draw(GUITexture texture, float scaleX, float scaleY, float x, float y) { + glActiveTexture(0); + transformMatrix.identity() + .translate(x, y, 0) + .scale(scaleX, scaleY, 1) + .get(transformMatrixData); + glUniformMatrix4fv(transformUniformLocation, false, transformMatrixData); + glBindTexture(GL_TEXTURE_2D, texture.getTextureId()); + glDrawArrays(GL_TRIANGLE_STRIP, 0, vertexCount); + } + + public void end() { glDisable(GL_BLEND); glEnable(GL_DEPTH_TEST); glDisableVertexAttribArray(0); glBindVertexArray(0); + shaderProgram.stopUsing(); } public void free() { - for (var tex : guiTextures) tex.free(); + for (var tex : textures.values()) tex.free(); glDeleteBuffers(vboId); glDeleteVertexArrays(vaoId); shaderProgram.free(); diff --git a/client/src/main/java/nl/andrewl/aos2_client/render/gui/GUITexture.java b/client/src/main/java/nl/andrewl/aos2_client/render/gui/GUITexture.java index 59d15ce..56a1ba0 100644 --- a/client/src/main/java/nl/andrewl/aos2_client/render/gui/GUITexture.java +++ b/client/src/main/java/nl/andrewl/aos2_client/render/gui/GUITexture.java @@ -2,7 +2,6 @@ package nl.andrewl.aos2_client.render.gui; import nl.andrewl.aos_core.ImageUtils; import org.joml.Vector2f; -import org.joml.Vector3f; import javax.imageio.ImageIO; import java.awt.image.BufferedImage; @@ -10,14 +9,21 @@ import java.io.IOException; import static org.lwjgl.opengl.GL46.*; +/** + * Represents a texture loaded onto the GPU. + */ public class GUITexture { private final int textureId; + /** + * The original image's width. + */ private final int width; + /** + * The original image's height. + */ private final int height; - private final Vector2f position = new Vector2f(0, 0); - private final Vector2f scale = new Vector2f(1, 1); - public GUITexture(String location) throws IOException { + public GUITexture(String location) { try (var in = GUITexture.class.getClassLoader().getResourceAsStream(location)) { if (in == null) throw new IOException("Couldn't load texture image from " + location); BufferedImage img = ImageIO.read(in); @@ -31,21 +37,43 @@ public class GUITexture { glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); var buf = ImageUtils.decodePng(img); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, buf); + } catch (IOException e) { + throw new RuntimeException("Failed to load GUI texture.", e); } } + public int getWidth() { + return width; + } + + public int getHeight() { + return height; + } + + public float getAspectRatio() { + return (float) width / height; + } + + public float getIdealHeight(float width) { + return width / getAspectRatio(); + } + + public float getIdealWidth(float height) { + return height * getAspectRatio(); + } + + public float getIdealScaleX(float desiredWidth, float screenWidth) { + return desiredWidth / screenWidth; + } + + public float getIdealScaleY(float desiredHeight, float screenHeight) { + return desiredHeight / screenHeight; + } + public int getTextureId() { return textureId; } - public Vector2f getPosition() { - return position; - } - - public Vector2f getScale() { - return scale; - } - public void free() { glDeleteTextures(textureId); } diff --git a/client/src/main/java/nl/andrewl/aos2_client/sound/SoundManager.java b/client/src/main/java/nl/andrewl/aos2_client/sound/SoundManager.java new file mode 100644 index 0000000..d6420a7 --- /dev/null +++ b/client/src/main/java/nl/andrewl/aos2_client/sound/SoundManager.java @@ -0,0 +1,53 @@ +package nl.andrewl.aos2_client.sound; + +import nl.andrewl.aos2_client.model.ClientPlayer; +import org.joml.Vector3f; +import org.lwjgl.openal.AL; +import org.lwjgl.openal.ALC; +import org.lwjgl.openal.ALCCapabilities; + +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import java.util.HashMap; +import java.util.Map; + +import static org.lwjgl.openal.ALC10.*; +import static org.lwjgl.openal.AL10.*; + +public class SoundManager { + private final long alContext; + private final Map audioBuffers = new HashMap<>(); + + public SoundManager() { + long device = alcOpenDevice((ByteBuffer) null); + ALCCapabilities capabilities = ALC.createCapabilities(device); + alContext = alcCreateContext(device, (IntBuffer) null); + alcMakeContextCurrent(alContext); + AL.createCapabilities(capabilities); + + } + + public void load(String name, String resource) { + int bufferId = alGenBuffers(); + audioBuffers.put(name, bufferId); + WaveData waveData = WaveData.create(resource); + alBufferData(bufferId, waveData.format, waveData.data, waveData.samplerate); + waveData.dispose(); + } + + public void updateListener(Vector3f position, Vector3f velocity) { + alListener3f(AL_POSITION, position.x(), position.y(), position.z()); + alListener3f(AL_VELOCITY, velocity.x(), velocity.y(), velocity.z()); + } + + public int getSoundBuffer(String name) { + return audioBuffers.get(name); + } + + public void free() { + for (var bufferId : audioBuffers.values()) { + alDeleteBuffers(bufferId); + } + alcDestroyContext(alContext); + } +} diff --git a/client/src/main/java/nl/andrewl/aos2_client/sound/SoundSource.java b/client/src/main/java/nl/andrewl/aos2_client/sound/SoundSource.java new file mode 100644 index 0000000..c2b782f --- /dev/null +++ b/client/src/main/java/nl/andrewl/aos2_client/sound/SoundSource.java @@ -0,0 +1,23 @@ +package nl.andrewl.aos2_client.sound; + +import static org.lwjgl.openal.AL10.*; + +public class SoundSource { + private final int sourceId; + + public SoundSource() { + sourceId = alGenSources(); + alSourcef(sourceId, AL_GAIN, 1); + alSourcef(sourceId, AL_PITCH, 1); + alSource3f(sourceId, AL_POSITION, 0, 0, 0); + } + + public void play(int bufferId) { + alSourcei(sourceId, AL_BUFFER, bufferId); + alSourcePlay(sourceId); + } + + public void free() { + alDeleteSources(sourceId); + } +} diff --git a/client/src/main/java/nl/andrewl/aos2_client/sound/WaveData.java b/client/src/main/java/nl/andrewl/aos2_client/sound/WaveData.java new file mode 100644 index 0000000..079270d --- /dev/null +++ b/client/src/main/java/nl/andrewl/aos2_client/sound/WaveData.java @@ -0,0 +1,86 @@ +package nl.andrewl.aos2_client.sound; + +import org.lwjgl.BufferUtils; +import org.lwjgl.openal.AL10; + +import javax.sound.sampled.AudioFormat; +import javax.sound.sampled.AudioInputStream; +import javax.sound.sampled.AudioSystem; +import javax.sound.sampled.UnsupportedAudioFileException; +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; + +public class WaveData { + + final int format; + final int samplerate; + final int totalBytes; + final int bytesPerFrame; + final ByteBuffer data; + + private final AudioInputStream audioStream; + private final byte[] dataArray; + + private WaveData(AudioInputStream stream) { + this.audioStream = stream; + AudioFormat audioFormat = stream.getFormat(); + format = getOpenAlFormat(audioFormat.getChannels(), audioFormat.getSampleSizeInBits()); + this.samplerate = (int) audioFormat.getSampleRate(); + this.bytesPerFrame = audioFormat.getFrameSize(); + this.totalBytes = (int) (stream.getFrameLength() * bytesPerFrame); + this.data = BufferUtils.createByteBuffer(totalBytes); + this.dataArray = new byte[totalBytes]; + loadData(); + } + + protected void dispose() { + try { + audioStream.close(); + data.clear(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private ByteBuffer loadData() { + try { + int bytesRead = audioStream.read(dataArray, 0, totalBytes); + data.clear(); + data.put(dataArray, 0, bytesRead); + data.flip(); + } catch (IOException e) { + e.printStackTrace(); + System.err.println("Couldn't read bytes from audio stream!"); + } + return data; + } + + + public static WaveData create(String file){ + InputStream stream = WaveData.class.getClassLoader().getResourceAsStream(file); + if (stream == null) { + System.err.println("Couldn't find file: " + file); + return null; + } + InputStream bufferedInput = new BufferedInputStream(stream); + AudioInputStream audioStream; + try { + audioStream = AudioSystem.getAudioInputStream(bufferedInput); + } catch (UnsupportedAudioFileException | IOException e) { + throw new RuntimeException(e); + } + return new WaveData(audioStream); + } + + + private static int getOpenAlFormat(int channels, int bitsPerSample) { + if (channels == 1) { + return bitsPerSample == 8 ? AL10.AL_FORMAT_MONO8 : AL10.AL_FORMAT_MONO16; + } else { + return bitsPerSample == 8 ? AL10.AL_FORMAT_STEREO8 : AL10.AL_FORMAT_STEREO16; + } + } + +} diff --git a/client/src/main/resources/gui/bullet.png b/client/src/main/resources/gui/bullet.png new file mode 100644 index 0000000..6abbce4 Binary files /dev/null and b/client/src/main/resources/gui/bullet.png differ diff --git a/client/src/main/resources/gui/clip.png b/client/src/main/resources/gui/clip.png new file mode 100644 index 0000000..6b3a8a1 Binary files /dev/null and b/client/src/main/resources/gui/clip.png differ diff --git a/client/src/main/resources/sound/m1garand-shot1.wav b/client/src/main/resources/sound/m1garand-shot1.wav new file mode 100644 index 0000000..6b689bf Binary files /dev/null and b/client/src/main/resources/sound/m1garand-shot1.wav differ diff --git a/core/src/main/java/nl/andrewl/aos_core/Net.java b/core/src/main/java/nl/andrewl/aos_core/Net.java index 3791028..779f983 100644 --- a/core/src/main/java/nl/andrewl/aos_core/Net.java +++ b/core/src/main/java/nl/andrewl/aos_core/Net.java @@ -42,6 +42,7 @@ public final class Net { serializer.registerTypeSerializer(13, new InventorySerializer()); serializer.registerTypeSerializer(14, new ItemStackSerializer()); serializer.registerType(15, InventorySelectedStackMessage.class); + serializer.registerType(16, SoundMessage.class); } public static ExtendedDataInputStream getInputStream(InputStream in) { diff --git a/core/src/main/java/nl/andrewl/aos_core/model/item/Gun.java b/core/src/main/java/nl/andrewl/aos_core/model/item/Gun.java index 00cd1aa..6ae92d1 100644 --- a/core/src/main/java/nl/andrewl/aos_core/model/item/Gun.java +++ b/core/src/main/java/nl/andrewl/aos_core/model/item/Gun.java @@ -14,6 +14,7 @@ public class Gun extends Item { private final float reloadTime; private final float baseDamage; private final float recoil; + private boolean automatic; public Gun( int id, @@ -25,7 +26,8 @@ public class Gun extends Item { float shotCooldownTime, float reloadTime, float baseDamage, - float recoil + float recoil, + boolean automatic ) { super(id, name, 1); this.maxClipCount = maxClipCount; @@ -36,6 +38,7 @@ public class Gun extends Item { this.reloadTime = reloadTime; this.baseDamage = baseDamage; this.recoil = recoil; + this.automatic = automatic; } public int getMaxClipCount() { @@ -69,4 +72,8 @@ public class Gun extends Item { public float getRecoil() { return recoil; } + + public boolean isAutomatic() { + return automatic; + } } diff --git a/core/src/main/java/nl/andrewl/aos_core/model/item/gun/Rifle.java b/core/src/main/java/nl/andrewl/aos_core/model/item/gun/Rifle.java index 5cc10a6..1f07c6a 100644 --- a/core/src/main/java/nl/andrewl/aos_core/model/item/gun/Rifle.java +++ b/core/src/main/java/nl/andrewl/aos_core/model/item/gun/Rifle.java @@ -14,7 +14,8 @@ public class Rifle extends Gun { 0.8f, 2.5f, 80f, - 50f + 50f, + false ); } } diff --git a/core/src/main/java/nl/andrewl/aos_core/net/client/ClientInputState.java b/core/src/main/java/nl/andrewl/aos_core/net/client/ClientInputState.java index 1e0b2a8..ccc862f 100644 --- a/core/src/main/java/nl/andrewl/aos_core/net/client/ClientInputState.java +++ b/core/src/main/java/nl/andrewl/aos_core/net/client/ClientInputState.java @@ -20,5 +20,6 @@ public record ClientInputState( // Interaction boolean hitting, // Usually a "left-click" action. boolean interacting, // Usually a "right-click" action. + boolean reloading, // Usually the "R" key. int selectedInventoryIndex // The selected index in the player's inventory. ) implements Message {} diff --git a/core/src/main/java/nl/andrewl/aos_core/net/client/SoundMessage.java b/core/src/main/java/nl/andrewl/aos_core/net/client/SoundMessage.java new file mode 100644 index 0000000..c0ccb5f --- /dev/null +++ b/core/src/main/java/nl/andrewl/aos_core/net/client/SoundMessage.java @@ -0,0 +1,12 @@ +package nl.andrewl.aos_core.net.client; + +import nl.andrewl.record_net.Message; + +/** + * A message that indicates that a sound has been emitted somewhere in the + * world, and that clients may need to play the sound. + */ +public record SoundMessage( + String name, + float px, float py, float pz +) implements Message {} diff --git a/design/bullet.png b/design/bullet.png new file mode 100644 index 0000000..6abbce4 Binary files /dev/null and b/design/bullet.png differ diff --git a/design/bullet.svg b/design/bullet.svg new file mode 100644 index 0000000..6abf8fe --- /dev/null +++ b/design/bullet.svg @@ -0,0 +1,79 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/design/clip.png b/design/clip.png new file mode 100644 index 0000000..6b3a8a1 Binary files /dev/null and b/design/clip.png differ diff --git a/design/clip.svg b/design/clip.svg new file mode 100644 index 0000000..051cf3f --- /dev/null +++ b/design/clip.svg @@ -0,0 +1,135 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + diff --git a/server/src/main/java/nl/andrewl/aos2_server/logic/PlayerActionManager.java b/server/src/main/java/nl/andrewl/aos2_server/logic/PlayerActionManager.java index 65e9a2a..23ed622 100644 --- a/server/src/main/java/nl/andrewl/aos2_server/logic/PlayerActionManager.java +++ b/server/src/main/java/nl/andrewl/aos2_server/logic/PlayerActionManager.java @@ -3,12 +3,12 @@ package nl.andrewl.aos2_server.logic; import nl.andrewl.aos2_server.Server; import nl.andrewl.aos2_server.ServerPlayer; import nl.andrewl.aos2_server.config.ServerConfig; -import nl.andrewl.aos_core.model.item.BlockItemStack; -import nl.andrewl.aos_core.model.item.ItemTypes; +import nl.andrewl.aos_core.model.item.*; import nl.andrewl.aos_core.model.world.World; import nl.andrewl.aos_core.net.client.ClientInputState; import nl.andrewl.aos_core.net.client.InventorySelectedStackMessage; import nl.andrewl.aos_core.net.client.ItemStackMessage; +import nl.andrewl.aos_core.net.client.SoundMessage; import nl.andrewl.aos_core.net.world.ChunkUpdateMessage; import org.joml.Math; import org.joml.Vector2i; @@ -30,6 +30,11 @@ public class PlayerActionManager { private long lastBlockRemovedAt = 0; private long lastBlockPlacedAt = 0; + private long gunLastShotAt = 0; + private boolean gunNeedsReCock = false; + private boolean gunReloading = false; + private long gunReloadingStartedAt = 0; + private boolean updated = false; public PlayerActionManager(ServerPlayer player) { @@ -38,7 +43,7 @@ public class PlayerActionManager { player.getId(), false, false, false, false, false, false, false, - false, false, + false, false, false, player.getInventory().getSelectedIndex() ); } @@ -67,8 +72,15 @@ public class PlayerActionManager { updated = true; // Tell everyone else that this player's selected item has changed. } - if (player.getInventory().getSelectedItemStack().getType().equals(ItemTypes.BLOCK)) { - tickBlockAction(now, server, world); + ItemStack selectedStack = player.getInventory().getSelectedItemStack(); + if (selectedStack instanceof BlockItemStack b) { + tickBlockAction(now, server, world, b); + } else if (selectedStack instanceof GunItemStack g) { + try { + tickGunAction(now, server, world, g); + } catch (Exception e) { + System.out.println("bleh"); + } } if (player.isCrouching() != lastInputState.crouching()) { @@ -79,8 +91,52 @@ public class PlayerActionManager { tickMovement(dt, world, server.getConfig().physics); } - private void tickBlockAction(long now, Server server, World world) { - BlockItemStack stack = (BlockItemStack) player.getInventory().getSelectedItemStack(); + private void tickGunAction(long now, Server server, World world, GunItemStack g) { + Gun gun = (Gun) g.getType(); + if (// Check to see if the player is shooting. + lastInputState.hitting() && + g.getBulletCount() > 0 && + !gunReloading && + now - gunLastShotAt > gun.getShotCooldownTime() * 1000 && + (gun.isAutomatic() || !gunNeedsReCock) + ) { + // TODO: trace a ray from gun to see if players intersect with it. + g.setBulletCount(g.getBulletCount() - 1); + gunLastShotAt = now; + if (!gun.isAutomatic()) { + 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())); + } + + if (// Check to see if the player is reloading. + lastInputState.reloading() && + !gunReloading && + g.getClipCount() > 0 + ) { + g.setClipCount(g.getClipCount() - 1); + gunReloadingStartedAt = now; + gunReloading = true; + server.getPlayerManager().getHandler(player.getId()).sendDatagramPacket(new ItemStackMessage(player.getInventory())); + } + + if (// Check to see if reloading is done. + gunReloading && + now - gunReloadingStartedAt > gun.getReloadTime() * 1000 + ) { + g.setBulletCount(gun.getMaxBulletCount()); + gunReloading = false; + server.getPlayerManager().getHandler(player.getId()).sendDatagramPacket(new ItemStackMessage(player.getInventory())); + } + + // Check to see if the player released the trigger, for non-automatic weapons. + if (!gun.isAutomatic() && gunNeedsReCock && !lastInputState.hitting()) { + gunNeedsReCock = false; + } + } + + private void tickBlockAction(long now, Server server, World world, BlockItemStack stack) { // Check for breaking blocks. if ( lastInputState.hitting() &&