Merge remote-tracking branch 'origin/UI' into UI

# Conflicts:
#	app/src/main/java/org/toop/app/layer/layers/MultiplayerLayer.java
#	app/src/main/java/org/toop/app/layer/layers/game/TicTacToeLayer.java
This commit is contained in:
ramollia
2025-10-06 21:09:23 +02:00
5 changed files with 204 additions and 118 deletions

View File

@@ -93,7 +93,11 @@ public final class App extends Application {
final int childrenCount = root.getChildren().size(); final int childrenCount = root.getChildren().size();
for (int i = 0; i < childrenCount; i++) { for (int i = 0; i < childrenCount; i++) {
try {
root.getChildren().removeLast(); root.getChildren().removeLast();
} catch (Exception e) {
IO.println(e);
}
} }
stack.removeAllElements(); stack.removeAllElements();

View File

@@ -0,0 +1,68 @@
package org.toop.app.layer.layers;
import org.toop.app.layer.Layer;
import org.toop.framework.eventbus.EventFlow;
import org.toop.framework.networking.events.NetworkEvents;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.List;
public final class ConnectedLayer extends Layer {
long clientId;
String user;
List<String> onlinePlayers = new CopyOnWriteArrayList<>();
public ConnectedLayer(long clientId, String user) {
super("multiplayer.css");
this.clientId = clientId;
this.user = user;
reload();
new EventFlow().addPostEvent(new NetworkEvents.SendLogin(this.clientId, this.user)).postEvent();
new EventFlow().listen(this::handleReceivedChallenge);
new Thread(this::populatePlayerList).start();
}
private void populatePlayerList() {
EventFlow sendGetPlayerList = new EventFlow().addPostEvent(new NetworkEvents.SendGetPlayerlist(this.clientId));
new EventFlow().listen(NetworkEvents.PlayerlistResponse.class, e -> {
if (e.clientId() == this.clientId) {
List<String> playerList = new java.util.ArrayList<>(List.of(e.playerlist())); // TODO: Garbage, but works
playerList.removeIf(name -> name.equalsIgnoreCase(user));
if (this.onlinePlayers != playerList) {
this.onlinePlayers.clear();
this.onlinePlayers.addAll(playerList);
}
}
});
TimerTask task = new TimerTask() {
public void run() {
sendGetPlayerList.postEvent();
}
};
Timer pollTimer = new Timer();
pollTimer.schedule(task, 0L, 5000L);
}
private void sendChallenge(String oppUsername, String gameType) {
if (onlinePlayers.contains(oppUsername)) {
new EventFlow().addPostEvent(new NetworkEvents.SendChallenge(this.clientId, oppUsername, gameType))
.postEvent();
}
}
private void handleReceivedChallenge(NetworkEvents.ChallengeResponse response) {
// TODO: Popup? Idk what this actually sends back.
}
@Override
public void reload() {
popAll();
}
}

View File

@@ -1,13 +1,10 @@
package org.toop.app.layer.layers.game; package org.toop.app.layer.layers.game;
import javafx.geometry.Pos;
import javafx.scene.paint.Color;
import org.toop.app.App; import org.toop.app.App;
import org.toop.app.GameInformation; import org.toop.app.GameInformation;
import org.toop.app.canvas.TicTacToeCanvas; import org.toop.app.canvas.TicTacToeCanvas;
import org.toop.app.layer.Container; import org.toop.app.layer.Container;
import org.toop.app.layer.Layer; import org.toop.app.layer.Layer;
import org.toop.app.layer.NodeBuilder;
import org.toop.app.layer.containers.VerticalContainer; import org.toop.app.layer.containers.VerticalContainer;
import org.toop.app.layer.layers.MainLayer; import org.toop.app.layer.layers.MainLayer;
import org.toop.framework.eventbus.EventFlow; import org.toop.framework.eventbus.EventFlow;
@@ -17,9 +14,13 @@ import org.toop.game.tictactoe.TicTacToe;
import org.toop.game.tictactoe.TicTacToeAI; import org.toop.game.tictactoe.TicTacToeAI;
import org.toop.local.AppContext; import org.toop.local.AppContext;
import javafx.geometry.Pos;
import javafx.scene.paint.Color;
import java.util.concurrent.BlockingQueue; import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Pattern;
public final class TicTacToeLayer extends Layer { public final class TicTacToeLayer extends Layer {
private TicTacToeCanvas canvas; private TicTacToeCanvas canvas;
@@ -36,9 +37,9 @@ public final class TicTacToeLayer extends Layer {
private String player2Name = ""; private String player2Name = "";
public TicTacToeLayer(GameInformation information) { public TicTacToeLayer(GameInformation information) {
super("bg-primary"); super("game.css");
canvas = new TicTacToeCanvas(Color.LIME, (App.getHeight() / 100) * 75, (App.getHeight() / 100) * 75, (cell) -> { canvas = new TicTacToeCanvas(Color.WHITE, (App.getHeight() / 100) * 75, (App.getHeight() / 100) * 75, (cell) -> {
try { try {
if (information.isConnectionLocal()) { if (information.isConnectionLocal()) {
if (ticTacToe.getCurrentTurn() == 0) { if (ticTacToe.getCurrentTurn() == 0) {
@@ -47,12 +48,10 @@ public final class TicTacToeLayer extends Layer {
playerMoveQueue.put(new Game.Move(cell, 'O')); playerMoveQueue.put(new Game.Move(cell, 'O'));
} }
} else { } else {
if (ticTacToe.getCurrentTurn() == 0) {
if (information.isPlayerHuman()[0] && currentPlayerMove != Game.EMPTY) { if (information.isPlayerHuman()[0] && currentPlayerMove != Game.EMPTY) {
playerMoveQueue.put(new Game.Move(cell, currentPlayerMove)); playerMoveQueue.put(new Game.Move(cell, currentPlayerMove));
} }
} }
}
} catch (InterruptedException e) { } catch (InterruptedException e) {
return; return;
} }
@@ -94,13 +93,11 @@ public final class TicTacToeLayer extends Layer {
} }
} }
final var backButton = NodeBuilder.button(AppContext.getString("back"), () -> { final Container controlContainer = new VerticalContainer(5);
controlContainer.addButton(AppContext.getString("back"), () -> {
App.activate(new MainLayer()); App.activate(new MainLayer());
}); });
final Container controlContainer = new VerticalContainer(5);
controlContainer.addNodes(backButton);
addContainer(controlContainer, Pos.BOTTOM_LEFT, 2, -2, 0, 0); addContainer(controlContainer, Pos.BOTTOM_LEFT, 2, -2, 0, 0);
addGameCanvas(canvas, Pos.CENTER, 0, 0); addGameCanvas(canvas, Pos.CENTER, 0, 0);
} }
@@ -164,10 +161,8 @@ public final class TicTacToeLayer extends Layer {
public long receivedMove = -1; public long receivedMove = -1;
public boolean firstPlayerIsMe = true; public boolean firstPlayerIsMe = true;
} }
AtomicBoolean firstPlayerIsMe = new AtomicBoolean(true); AtomicBoolean firstPlayerIsMe = new AtomicBoolean(true);
AtomicBoolean gameHasStarted = new AtomicBoolean(false); AtomicBoolean gameHasStarted = new AtomicBoolean(false);
private void serverGameThread(NetworkEvents.StartClientResponse event) { private void serverGameThread(NetworkEvents.StartClientResponse event) {
boolean running = true; boolean running = true;
final long clientId = event.clientId(); final long clientId = event.clientId();
@@ -184,26 +179,20 @@ public final class TicTacToeLayer extends Layer {
.listen(this::handleReceivedMessage) .listen(this::handleReceivedMessage)
.listen(this::onMoveResponse); .listen(this::onMoveResponse);
new EventFlow().addPostEvent(new NetworkEvents.SendLogin(clientId, information.playerName()[0]))
.postEvent();
new EventFlow().addPostEvent(new NetworkEvents.SendSubscribe(clientId, "tic-tac-toe"))
.postEvent();
while (running) { while (running) {
try { try {
Thread.sleep(250); Thread.sleep(250);
} catch (InterruptedException exception) { } catch (InterruptedException exception) {}
}
boolean hasStarted = gameHasStarted.get(); boolean hasStarted = gameHasStarted.get();
if (hasStarted) { if (hasStarted) {
onlineGameState.firstPlayerIsMe = firstPlayerIsMe.get(); onlineGameState.firstPlayerIsMe = firstPlayerIsMe.get();
if (onlineGameState.firstPlayerIsMe) { if (onlineGameState.firstPlayerIsMe) {
currentPlayerMove = 'X'; currentPlayerMove = 'X';
} else { }
else {
currentPlayerMove = 'O'; currentPlayerMove = 'O';
} }
if (!information.isPlayerHuman()[0]) { if(!information.isPlayerHuman()[0]){
boolean myTurn = (onlineGameState.firstPlayerIsMe && ticTacToe.getCurrentTurn() % 2 == 0) boolean myTurn = (onlineGameState.firstPlayerIsMe && ticTacToe.getCurrentTurn() % 2 == 0)
|| (!onlineGameState.firstPlayerIsMe && ticTacToe.getCurrentTurn() % 2 == 1); || (!onlineGameState.firstPlayerIsMe && ticTacToe.getCurrentTurn() % 2 == 1);
if (myTurn) { if (myTurn) {
@@ -212,7 +201,8 @@ public final class TicTacToeLayer extends Layer {
new EventFlow().addPostEvent(new NetworkEvents.SendMove(clientId, (short) move.position())) new EventFlow().addPostEvent(new NetworkEvents.SendMove(clientId, (short) move.position()))
.postEvent(); .postEvent();
} }
} else { }
else {
try { try {
final Game.Move wants = playerMoveQueue.take(); final Game.Move wants = playerMoveQueue.take();
final Game.Move[] legalMoves = ticTacToe.getLegalMoves(); final Game.Move[] legalMoves = ticTacToe.getLegalMoves();
@@ -224,6 +214,7 @@ public final class TicTacToeLayer extends Layer {
} }
} }
} catch (InterruptedException exception) { } catch (InterruptedException exception) {
System.out.println(exception.getMessage());
return; return;
} }
} }
@@ -240,9 +231,10 @@ public final class TicTacToeLayer extends Layer {
} }
private void handleServerGameStart(NetworkEvents.GameMatchResponse resp) { private void handleServerGameStart(NetworkEvents.GameMatchResponse resp) {
if (resp.playerToMove().equals(resp.opponent())) { if(resp.playerToMove().equals(resp.opponent())){
firstPlayerIsMe.set(false); firstPlayerIsMe.set(false);
} else { }
else{
firstPlayerIsMe.set(true); firstPlayerIsMe.set(true);
} }
gameHasStarted.set(true); gameHasStarted.set(true);
@@ -253,10 +245,11 @@ public final class TicTacToeLayer extends Layer {
if (resp.player().equals(information.playerName()[0]) && firstPlayerIsMe.get() if (resp.player().equals(information.playerName()[0]) && firstPlayerIsMe.get()
|| !resp.player().equals(information.playerName()[0]) && !firstPlayerIsMe.get()) { || !resp.player().equals(information.playerName()[0]) && !firstPlayerIsMe.get()) {
playerChar = 'X'; playerChar = 'X';
} else { }
else {
playerChar = 'O'; playerChar = 'O';
} }
Game.Move move = new Game.Move(Integer.parseInt(resp.move()), playerChar); Game.Move move =new Game.Move(Integer.parseInt(resp.move()),playerChar);
Game.State state = ticTacToe.play(move); Game.State state = ticTacToe.play(move);
if (state != Game.State.NORMAL) { //todo differentiate between future draw guaranteed and is currently a draw if (state != Game.State.NORMAL) { //todo differentiate between future draw guaranteed and is currently a draw
gameHasStarted.set(false); gameHasStarted.set(false);
@@ -265,7 +258,7 @@ public final class TicTacToeLayer extends Layer {
} }
private void handleChallengeResponse(NetworkEvents.ChallengeResponse resp) { private void handleChallengeResponse(NetworkEvents.ChallengeResponse resp) {
new EventFlow().addPostEvent(new NetworkEvents.SendAcceptChallenge(resp.clientId(), Integer.parseInt(resp.challengeId()))) new EventFlow().addPostEvent(new NetworkEvents.SendAcceptChallenge(resp.clientId(),Integer.parseInt(resp.challengeId())))
.postEvent(); .postEvent();
} }
@@ -275,7 +268,6 @@ public final class TicTacToeLayer extends Layer {
//new EventFlow().addPostEvent(new NetworkEvents.SendMove(response.clientId(),(short)2)) //new EventFlow().addPostEvent(new NetworkEvents.SendMove(response.clientId(),(short)2))
// .postEvent(); // .postEvent();
} }
private void handleReceivedMessage(NetworkEvents.ReceivedMessage msg) { private void handleReceivedMessage(NetworkEvents.ReceivedMessage msg) {
System.out.println("Received Message: " + msg.message()); //todo add chat window System.out.println("Received Message: " + msg.message()); //todo add chat window
} }

View File

@@ -5,9 +5,11 @@ import org.toop.framework.asset.types.LoadableResource;
import javax.sound.sampled.*; import javax.sound.sampled.*;
import java.io.*; import java.io.*;
import java.nio.file.Files;
@FileExtension({"wav"}) @FileExtension({"wav"})
public class SoundEffectAsset extends BaseResource implements LoadableResource { public class SoundEffectAsset extends BaseResource implements LoadableResource {
private byte[] rawData;
public SoundEffectAsset(final File audioFile) { public SoundEffectAsset(final File audioFile) {
super(audioFile); super(audioFile);
@@ -15,10 +17,6 @@ public class SoundEffectAsset extends BaseResource implements LoadableResource {
// Gets a new clip to play // Gets a new clip to play
public Clip getNewClip() throws LineUnavailableException, UnsupportedAudioFileException, IOException { public Clip getNewClip() throws LineUnavailableException, UnsupportedAudioFileException, IOException {
if(!this.isLoaded()){
this.load();
}
// Get a new clip from audio system // Get a new clip from audio system
Clip clip = AudioSystem.getClip(); Clip clip = AudioSystem.getClip();
@@ -32,7 +30,13 @@ public class SoundEffectAsset extends BaseResource implements LoadableResource {
// Generates a new audio stream from byte array // Generates a new audio stream from byte array
private AudioInputStream getAudioStream() throws UnsupportedAudioFileException, IOException { private AudioInputStream getAudioStream() throws UnsupportedAudioFileException, IOException {
return AudioSystem.getAudioInputStream(this.file); // Check if raw data is loaded into memory
if(!this.isLoaded()){
this.load();
}
// Turn rawData into an input stream and turn that into an audio input stream;
return AudioSystem.getAudioInputStream(new ByteArrayInputStream(this.rawData));
} }
private AudioInputStream downSampleAudio(AudioInputStream audioInputStream, AudioFormat baseFormat) { private AudioInputStream downSampleAudio(AudioInputStream audioInputStream, AudioFormat baseFormat) {
@@ -52,16 +56,17 @@ public class SoundEffectAsset extends BaseResource implements LoadableResource {
@Override @Override
public void load() { public void load() {
try { try {
this.getAudioStream(); this.rawData = Files.readAllBytes(file.toPath());
this.isLoaded = true; this.isLoaded = true;
} catch (UnsupportedAudioFileException | IOException e) { } catch (IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} }
@Override @Override
public void unload() { public void unload() {
this.isLoaded = false; // TODO? this.rawData = null;
this.isLoaded = false;
} }
@Override @Override

View File

@@ -75,6 +75,20 @@ public class SoundManager {
for (MediaPlayer mediaPlayer : this.activeMusic) { for (MediaPlayer mediaPlayer : this.activeMusic) {
mediaPlayer.setVolume(this.volume); mediaPlayer.setVolume(this.volume);
} }
for (Clip clip : this.activeSoundEffects.values()){
updateClipVolume(clip);
}
}
private void updateClipVolume(Clip clip){
if (clip.isControlSupported(FloatControl.Type.MASTER_GAIN)){
FloatControl volumeControl = (FloatControl) clip.getControl(FloatControl.Type.MASTER_GAIN);
float min = volumeControl.getMinimum();
float max = volumeControl.getMaximum();
float dB = (float) (Math.log10(Math.max(volume, 0.0001)) * 20.0); // convert linear to dB
dB = Math.max(min, Math.min(max, dB));
volumeControl.setValue(dB);
}
} }
private void handleGetCurrentVolume(AudioEvents.GetCurrentVolume event) { private void handleGetCurrentVolume(AudioEvents.GetCurrentVolume event) {
@@ -144,6 +158,9 @@ public class SoundManager {
// Get a new clip from resource // Get a new clip from resource
Clip clip = asset.getNewClip(); Clip clip = asset.getNewClip();
// Set volume of clip
updateClipVolume(clip);
// If supposed to loop make it loop, else just start it once // If supposed to loop make it loop, else just start it once
if (loop) { if (loop) {
clip.loop(Clip.LOOP_CONTINUOUSLY); clip.loop(Clip.LOOP_CONTINUOUSLY);