Compare commits
No commits in common. "main" and "v1.1.0" have entirely different histories.
|
@ -1,13 +1,7 @@
|
||||||
.idea/
|
.idea/
|
||||||
target/
|
target/
|
||||||
client-builds/
|
client-builds/
|
||||||
client.yaml
|
|
||||||
server.yaml
|
|
||||||
|
|
||||||
# Ignore the ./config directory so that developers can put their config files
|
# Ignore the ./config directory so that developers can put their config files
|
||||||
# there for server and client apps.
|
# there for server and client apps.
|
||||||
config
|
config
|
||||||
|
|
||||||
# Ignore compiled tool executables
|
|
||||||
build-clients
|
|
||||||
setversion
|
|
||||||
|
|
60
README.md
60
README.md
|
@ -8,22 +8,56 @@ _Read this guide to get started and join a server in just a minute or two!_
|
||||||
|
|
||||||
1. Make sure you've got at least Java 17 installed. You can get it [here](https://adoptium.net/temurin/releases).
|
1. Make sure you've got at least Java 17 installed. You can get it [here](https://adoptium.net/temurin/releases).
|
||||||
2. Download the `aos2-client` JAR file from the [releases page](https://github.com/andrewlalis/ace-of-shades-2/releases) that's compatible with your system.
|
2. Download the `aos2-client` JAR file from the [releases page](https://github.com/andrewlalis/ace-of-shades-2/releases) that's compatible with your system.
|
||||||
3. Run the game by double-clicking the JAR file, or entering `java -jar <jarfile>` in a terminal. This should generate a `client.yaml` file.
|
3. Create a file named `config.yaml` in the same directory as the JAR file, and place the following text in it:
|
||||||
|
```yaml
|
||||||
|
serverHost: localhost
|
||||||
|
serverPort: 25565
|
||||||
|
username: myUsername
|
||||||
|
input:
|
||||||
|
mouseSensitivity: 0.005
|
||||||
|
display:
|
||||||
|
fullscreen: true
|
||||||
|
captureCursor: true
|
||||||
|
fov: 80
|
||||||
|
```
|
||||||
4. Set the `serverHost`, `serverPort`, and `username` properties accordingly for the server you want to join.
|
4. Set the `serverHost`, `serverPort`, and `username` properties accordingly for the server you want to join.
|
||||||
5. Run the game again to join the server and start playing!
|
5. Run the game by double-clicking the `aos2-client` JAR file, or enter `java -jar aos2-client-{version}.jar` in a terminal.
|
||||||
|
|
||||||
### Controls
|
|
||||||
- `WASD` to move, `SPACE` to jump, `LEFT-CONTROL` to crouch.
|
|
||||||
- `LEFT-CLICK` to use your held item (shoot, destroy blocks, etc.).
|
|
||||||
- `RIGHT-CLICK` to interact with things using your held item (place blocks, etc.).
|
|
||||||
- `R` to reload.
|
|
||||||
- `T` to chat. Press `ENTER` to send the chat.
|
|
||||||
- `ESCAPE` to exit the game.
|
|
||||||
- Numbers are used for selecting different items.
|
|
||||||
- `F3` toggles showing debug info.
|
|
||||||
|
|
||||||
## Setting up a Server
|
## Setting up a Server
|
||||||
Setting up a server is quite easy. Just go to the [releases page](https://github.com/andrewlalis/ace-of-shades-2/releases) and download the latest `aos2-server` JAR file, and run it. It'll create a `server.yaml` configuration file if you don't provide one.
|
Setting up a server is quite easy. Just go to the [releases page](https://github.com/andrewlalis/ace-of-shades-2/releases) and download the latest `aos2-server` JAR file. Similar to the client, it's best if you provide a `config.yaml` file to the server, in the same directory. The following snippet shows the structure and default values of a server's configuration.
|
||||||
|
```yaml
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
Both the client and server use a similar style of YAML-based configuration, where upon booting up, the program will look for a configuration file in the current working directory with one of the following names: `configuration`, `config`, `cfg`, ending in either `.yaml` or `.yml`. Alternatively, you can provide the path to a configuration file at a different location via a single command-line argument. For example:
|
Both the client and server use a similar style of YAML-based configuration, where upon booting up, the program will look for a configuration file in the current working directory with one of the following names: `configuration`, `config`, `cfg`, ending in either `.yaml` or `.yml`. Alternatively, you can provide the path to a configuration file at a different location via a single command-line argument. For example:
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<artifactId>ace-of-shades-2</artifactId>
|
<artifactId>ace-of-shades-2</artifactId>
|
||||||
<groupId>nl.andrewl</groupId>
|
<groupId>nl.andrewl</groupId>
|
||||||
<version>1.5.0</version>
|
<version>1.1.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
|
|
@ -52,14 +52,6 @@ public class Camera {
|
||||||
velocity.set(p.getVelocity());
|
velocity.set(p.getVelocity());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setToPlayerScopeView(Player p) {
|
|
||||||
Vector3f pos = new Vector3f();
|
|
||||||
Matrix4f tx = p.getHeldItemTransform();
|
|
||||||
tx.transformPosition(pos);
|
|
||||||
position.set(pos);
|
|
||||||
velocity.set(p.getVelocity());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOrientationToPlayer(Player p) {
|
public void setOrientationToPlayer(Player p) {
|
||||||
orientation.set(p.getOrientation());
|
orientation.set(p.getOrientation());
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
package nl.andrewl.aos2_client;
|
package nl.andrewl.aos2_client;
|
||||||
|
|
||||||
import nl.andrewl.aos2_client.config.ClientConfig;
|
import nl.andrewl.aos2_client.config.ClientConfig;
|
||||||
import nl.andrewl.aos2_client.config.ConnectConfig;
|
import nl.andrewl.aos2_client.control.*;
|
||||||
import nl.andrewl.aos2_client.control.InputHandler;
|
|
||||||
import nl.andrewl.aos2_client.model.Chat;
|
import nl.andrewl.aos2_client.model.Chat;
|
||||||
import nl.andrewl.aos2_client.model.ClientPlayer;
|
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.aos_core.config.Config;
|
import nl.andrewl.aos_core.config.Config;
|
||||||
|
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.Team;
|
import nl.andrewl.aos_core.model.Team;
|
||||||
import nl.andrewl.aos_core.net.client.*;
|
import nl.andrewl.aos_core.net.client.*;
|
||||||
|
@ -17,22 +17,22 @@ import nl.andrewl.aos_core.net.world.ChunkHashMessage;
|
||||||
import nl.andrewl.aos_core.net.world.ChunkUpdateMessage;
|
import nl.andrewl.aos_core.net.world.ChunkUpdateMessage;
|
||||||
import nl.andrewl.record_net.Message;
|
import nl.andrewl.record_net.Message;
|
||||||
import org.joml.Vector3f;
|
import org.joml.Vector3f;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Queue;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
|
||||||
|
|
||||||
public class Client implements Runnable {
|
public class Client implements Runnable {
|
||||||
public final ConnectConfig connectConfig;
|
private static final Logger log = LoggerFactory.getLogger(Client.class);
|
||||||
public final ClientConfig config;
|
public static final double FPS = 60;
|
||||||
|
|
||||||
|
private final ClientConfig config;
|
||||||
private final CommunicationHandler communicationHandler;
|
private final CommunicationHandler communicationHandler;
|
||||||
private final InputHandler inputHandler;
|
private final InputHandler inputHandler;
|
||||||
private final Camera camera;
|
|
||||||
private GameRenderer gameRenderer;
|
private GameRenderer gameRenderer;
|
||||||
private SoundManager soundManager;
|
private SoundManager soundManager;
|
||||||
private long lastPlayerUpdate = 0;
|
private long lastPlayerUpdate = 0;
|
||||||
|
@ -43,19 +43,15 @@ public class Client implements Runnable {
|
||||||
private final Map<Integer, Projectile> projectiles;
|
private final Map<Integer, Projectile> projectiles;
|
||||||
private final Map<Integer, Team> teams;
|
private final Map<Integer, Team> teams;
|
||||||
private final Chat chat;
|
private final Chat chat;
|
||||||
private final Queue<Runnable> mainThreadActions;
|
|
||||||
|
|
||||||
public Client(ClientConfig config, ConnectConfig connectConfig) {
|
public Client(ClientConfig config) {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.connectConfig = connectConfig;
|
|
||||||
this.camera = new Camera();
|
|
||||||
this.players = new ConcurrentHashMap<>();
|
this.players = new ConcurrentHashMap<>();
|
||||||
this.teams = new ConcurrentHashMap<>();
|
this.teams = new ConcurrentHashMap<>();
|
||||||
this.projectiles = new ConcurrentHashMap<>();
|
this.projectiles = new ConcurrentHashMap<>();
|
||||||
this.communicationHandler = new CommunicationHandler(this);
|
this.communicationHandler = new CommunicationHandler(this);
|
||||||
this.inputHandler = new InputHandler(this, communicationHandler, camera);
|
this.inputHandler = new InputHandler(this, communicationHandler);
|
||||||
this.chat = new Chat();
|
this.chat = new Chat();
|
||||||
this.mainThreadActions = new ConcurrentLinkedQueue<>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ClientConfig getConfig() {
|
public ClientConfig getConfig() {
|
||||||
|
@ -80,28 +76,30 @@ public class Client implements Runnable {
|
||||||
try {
|
try {
|
||||||
communicationHandler.establishConnection();
|
communicationHandler.establishConnection();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
System.err.println("Couldn't connect to the server: " + e.getMessage());
|
log.error("Couldn't connect to the server: {}", e.getMessage());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
gameRenderer = new GameRenderer(this, inputHandler, camera);
|
gameRenderer = new GameRenderer(config.display, this);
|
||||||
|
gameRenderer.setupWindow(
|
||||||
|
new PlayerViewCursorCallback(config.input, this, gameRenderer.getCamera(), communicationHandler),
|
||||||
|
new PlayerInputKeyCallback(inputHandler),
|
||||||
|
new PlayerInputMouseClickCallback(inputHandler),
|
||||||
|
new PlayerInputMouseScrollCallback(this, communicationHandler)
|
||||||
|
);
|
||||||
soundManager = new SoundManager();
|
soundManager = new SoundManager();
|
||||||
|
log.debug("Sound system initialized.");
|
||||||
|
|
||||||
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();
|
||||||
while (!mainThreadActions.isEmpty()) {
|
|
||||||
mainThreadActions.remove().run();
|
|
||||||
}
|
|
||||||
soundManager.updateListener(myPlayer.getPosition(), myPlayer.getVelocity());
|
soundManager.updateListener(myPlayer.getPosition(), myPlayer.getVelocity());
|
||||||
gameRenderer.getCamera().interpolatePosition(dt);
|
gameRenderer.getCamera().interpolatePosition(dt);
|
||||||
interpolatePlayers(now, dt);
|
interpolatePlayers(now, dt);
|
||||||
interpolateProjectiles(dt);
|
interpolateProjectiles(dt);
|
||||||
soundManager.playWalkingSounds(myPlayer, world, now);
|
soundManager.playWalkingSounds(myPlayer, now);
|
||||||
|
|
||||||
gameRenderer.draw();
|
gameRenderer.draw();
|
||||||
lastFrameAt = now;
|
lastFrameAt = now;
|
||||||
}
|
}
|
||||||
|
@ -120,30 +118,27 @@ public class Client implements Runnable {
|
||||||
communicationHandler.sendMessage(new ChunkHashMessage(u.cx(), u.cy(), u.cz(), -1));
|
communicationHandler.sendMessage(new ChunkHashMessage(u.cx(), u.cy(), u.cz(), -1));
|
||||||
}
|
}
|
||||||
} else if (msg instanceof PlayerUpdateMessage playerUpdate) {
|
} else if (msg instanceof PlayerUpdateMessage playerUpdate) {
|
||||||
runLater(() -> {
|
if (playerUpdate.clientId() == myPlayer.getId() && playerUpdate.timestamp() > lastPlayerUpdate) {
|
||||||
if (playerUpdate.clientId() == myPlayer.getId() && playerUpdate.timestamp() > lastPlayerUpdate) {
|
myPlayer.getPosition().set(playerUpdate.px(), playerUpdate.py(), playerUpdate.pz());
|
||||||
myPlayer.getPosition().set(playerUpdate.px(), playerUpdate.py(), playerUpdate.pz());
|
myPlayer.getVelocity().set(playerUpdate.vx(), playerUpdate.vy(), playerUpdate.vz());
|
||||||
myPlayer.getVelocity().set(playerUpdate.vx(), playerUpdate.vy(), playerUpdate.vz());
|
myPlayer.setCrouching(playerUpdate.crouching());
|
||||||
myPlayer.setCrouching(playerUpdate.crouching());
|
if (gameRenderer != null) {
|
||||||
myPlayer.setMode(playerUpdate.mode());
|
gameRenderer.getCamera().setToPlayer(myPlayer);
|
||||||
if (gameRenderer != null) {
|
|
||||||
gameRenderer.getCamera().setToPlayer(myPlayer);
|
|
||||||
}
|
|
||||||
if (soundManager != null) {
|
|
||||||
soundManager.updateListener(myPlayer.getEyePosition(), myPlayer.getVelocity());
|
|
||||||
}
|
|
||||||
lastPlayerUpdate = playerUpdate.timestamp();
|
|
||||||
} else {
|
|
||||||
OtherPlayer p = players.get(playerUpdate.clientId());
|
|
||||||
if (p != null) {
|
|
||||||
playerUpdate.apply(p);
|
|
||||||
p.setHeldItemId(playerUpdate.selectedItemId());
|
|
||||||
p.updateModelTransform();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
if (soundManager != null) {
|
||||||
|
soundManager.updateListener(myPlayer.getPosition(), myPlayer.getVelocity());
|
||||||
|
}
|
||||||
|
lastPlayerUpdate = playerUpdate.timestamp();
|
||||||
|
} else {
|
||||||
|
OtherPlayer p = players.get(playerUpdate.clientId());
|
||||||
|
if (p != null) {
|
||||||
|
playerUpdate.apply(p);
|
||||||
|
p.setHeldItemId(playerUpdate.selectedItemId());
|
||||||
|
p.updateModelTransform();
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if (msg instanceof ClientInventoryMessage inventoryMessage) {
|
} else if (msg instanceof ClientInventoryMessage inventoryMessage) {
|
||||||
runLater(() -> myPlayer.setInventory(inventoryMessage.inv()));
|
myPlayer.setInventory(inventoryMessage.inv());
|
||||||
} else if (msg instanceof InventorySelectedStackMessage selectedStackMessage) {
|
} else if (msg instanceof InventorySelectedStackMessage selectedStackMessage) {
|
||||||
myPlayer.getInventory().setSelectedIndex(selectedStackMessage.index());
|
myPlayer.getInventory().setSelectedIndex(selectedStackMessage.index());
|
||||||
} else if (msg instanceof ItemStackMessage itemStackMessage) {
|
} else if (msg instanceof ItemStackMessage itemStackMessage) {
|
||||||
|
@ -154,20 +149,19 @@ public class Client implements Runnable {
|
||||||
player.setSelectedBlockValue(blockColorMessage.block());
|
player.setSelectedBlockValue(blockColorMessage.block());
|
||||||
}
|
}
|
||||||
} else if (msg instanceof PlayerJoinMessage joinMessage) {
|
} else if (msg instanceof PlayerJoinMessage joinMessage) {
|
||||||
runLater(() -> {
|
Player p = joinMessage.toPlayer();
|
||||||
OtherPlayer op = OtherPlayer.fromJoinMessage(joinMessage, this);
|
OtherPlayer op = new OtherPlayer(p.getId(), p.getUsername());
|
||||||
players.put(op.getId(), op);
|
if (joinMessage.teamId() != -1) {
|
||||||
});
|
op.setTeam(teams.get(joinMessage.teamId()));
|
||||||
|
}
|
||||||
|
op.getPosition().set(p.getPosition());
|
||||||
|
op.getVelocity().set(p.getVelocity());
|
||||||
|
op.getOrientation().set(p.getOrientation());
|
||||||
|
op.setHeldItemId(joinMessage.selectedItemId());
|
||||||
|
op.setSelectedBlockValue(joinMessage.selectedBlockValue());
|
||||||
|
players.put(op.getId(), op);
|
||||||
} else if (msg instanceof PlayerLeaveMessage leaveMessage) {
|
} else if (msg instanceof PlayerLeaveMessage leaveMessage) {
|
||||||
runLater(() -> players.remove(leaveMessage.id()));
|
players.remove(leaveMessage.id());
|
||||||
} else if (msg instanceof PlayerTeamUpdateMessage teamUpdateMessage) {
|
|
||||||
runLater(() -> {
|
|
||||||
OtherPlayer op = players.get(teamUpdateMessage.playerId());
|
|
||||||
Team team = teamUpdateMessage.teamId() == -1 ? null : teams.get(teamUpdateMessage.teamId());
|
|
||||||
if (op != null) {
|
|
||||||
op.setTeam(team);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if (msg instanceof SoundMessage soundMessage) {
|
} else if (msg instanceof SoundMessage soundMessage) {
|
||||||
if (soundManager != null) {
|
if (soundManager != null) {
|
||||||
soundManager.play(
|
soundManager.play(
|
||||||
|
@ -178,34 +172,24 @@ public class Client implements Runnable {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else if (msg instanceof ProjectileMessage pm) {
|
} else if (msg instanceof ProjectileMessage pm) {
|
||||||
runLater(() -> {
|
Projectile p = projectiles.get(pm.id());
|
||||||
Projectile p = projectiles.get(pm.id());
|
if (p == null && !pm.destroyed()) {
|
||||||
if (p == null && !pm.destroyed()) {
|
p = new Projectile(pm.id(), new Vector3f(pm.px(), pm.py(), pm.pz()), new Vector3f(pm.vx(), pm.vy(), pm.vz()), pm.type());
|
||||||
p = new Projectile(pm.id(), new Vector3f(pm.px(), pm.py(), pm.pz()), new Vector3f(pm.vx(), pm.vy(), pm.vz()), pm.type());
|
projectiles.put(p.getId(), p);
|
||||||
projectiles.put(p.getId(), p);
|
} else if (p != null) {
|
||||||
} else if (p != null) {
|
p.getPosition().set(pm.px(), pm.py(), pm.pz()); // Don't update position, it's too short of a timeframe to matter.
|
||||||
p.getPosition().set(pm.px(), pm.py(), pm.pz()); // Don't update position, it's too short of a timeframe to matter.
|
p.getVelocity().set(pm.vx(), pm.vy(), pm.vz());
|
||||||
p.getVelocity().set(pm.vx(), pm.vy(), pm.vz());
|
if (pm.destroyed()) {
|
||||||
if (pm.destroyed()) {
|
projectiles.remove(p.getId());
|
||||||
projectiles.remove(p.getId());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
} else if (msg instanceof ClientHealthMessage healthMessage) {
|
} else if (msg instanceof ClientHealthMessage healthMessage) {
|
||||||
myPlayer.setHealth(healthMessage.health());
|
myPlayer.setHealth(healthMessage.health());
|
||||||
} else if (msg instanceof ChatMessage chatMessage) {
|
} else if (msg instanceof ChatMessage chatMessage) {
|
||||||
chat.chatReceived(chatMessage);
|
chat.chatReceived(chatMessage);
|
||||||
if (soundManager != null) {
|
if (soundManager != null) {
|
||||||
soundManager.play("chat", 1, myPlayer.getEyePosition(), myPlayer.getVelocity());
|
soundManager.play("chat", 1, myPlayer.getPosition(), myPlayer.getVelocity());
|
||||||
}
|
}
|
||||||
} else if (msg instanceof ClientRecoilMessage recoil) {
|
|
||||||
runLater(() -> {
|
|
||||||
myPlayer.setOrientation(myPlayer.getOrientation().x + recoil.dx(), myPlayer.getOrientation().y + recoil.dy());
|
|
||||||
if (gameRenderer != null) {
|
|
||||||
gameRenderer.getCamera().setOrientationToPlayer(myPlayer);
|
|
||||||
}
|
|
||||||
communicationHandler.sendDatagramPacket(ClientOrientationState.fromPlayer(myPlayer));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -217,14 +201,6 @@ public class Client implements Runnable {
|
||||||
return world;
|
return world;
|
||||||
}
|
}
|
||||||
|
|
||||||
public InputHandler getInputHandler() {
|
|
||||||
return inputHandler;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CommunicationHandler getCommunicationHandler() {
|
|
||||||
return communicationHandler;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<Integer, Team> getTeams() {
|
public Map<Integer, Team> getTeams() {
|
||||||
return teams;
|
return teams;
|
||||||
}
|
}
|
||||||
|
@ -241,17 +217,13 @@ public class Client implements Runnable {
|
||||||
return chat;
|
return chat;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SoundManager getSoundManager() {
|
|
||||||
return soundManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void interpolatePlayers(long now, 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, world, now);
|
soundManager.playWalkingSounds(player, now);
|
||||||
}
|
}
|
||||||
gameRenderer.getGuiRenderer().updateNamePlates(players.values());
|
gameRenderer.getGuiRenderer().updateNamePlates(players.values());
|
||||||
}
|
}
|
||||||
|
@ -264,27 +236,13 @@ public class Client implements Runnable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void runLater(Runnable runnable) {
|
|
||||||
mainThreadActions.add(runnable);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main(String[] args) throws IOException {
|
public static void main(String[] args) throws IOException {
|
||||||
if (args.length < 3) {
|
|
||||||
System.err.println("Missing required host, port, username args.");
|
|
||||||
System.exit(1);
|
|
||||||
}
|
|
||||||
String host = args[0].trim();
|
|
||||||
int port = Integer.parseInt(args[1]);
|
|
||||||
String username = args[2].trim();
|
|
||||||
ConnectConfig connectCfg = new ConnectConfig(host, port, username, false);
|
|
||||||
|
|
||||||
List<Path> configPaths = Config.getCommonConfigPaths();
|
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) {
|
||||||
if (args.length > 3) {
|
configPaths.add(Path.of(args[0].trim()));
|
||||||
configPaths.add(Path.of(args[3].trim()));
|
|
||||||
}
|
}
|
||||||
ClientConfig clientConfig = Config.loadConfig(ClientConfig.class, configPaths, new ClientConfig(), "default-config.yaml");
|
ClientConfig clientConfig = Config.loadConfig(ClientConfig.class, configPaths, new ClientConfig());
|
||||||
Client client = new Client(clientConfig, connectCfg);
|
Client client = new Client(clientConfig);
|
||||||
client.run();
|
client.run();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,13 +3,11 @@ package nl.andrewl.aos2_client;
|
||||||
import nl.andrewl.aos2_client.model.ClientPlayer;
|
import nl.andrewl.aos2_client.model.ClientPlayer;
|
||||||
import nl.andrewl.aos2_client.model.OtherPlayer;
|
import nl.andrewl.aos2_client.model.OtherPlayer;
|
||||||
import nl.andrewl.aos_core.Net;
|
import nl.andrewl.aos_core.Net;
|
||||||
import nl.andrewl.aos_core.model.PlayerMode;
|
|
||||||
import nl.andrewl.aos_core.model.Team;
|
import nl.andrewl.aos_core.model.Team;
|
||||||
import nl.andrewl.aos_core.model.item.ItemStack;
|
import nl.andrewl.aos_core.model.item.ItemStack;
|
||||||
import nl.andrewl.aos_core.model.world.World;
|
import nl.andrewl.aos_core.model.world.World;
|
||||||
import nl.andrewl.aos_core.model.world.WorldIO;
|
import nl.andrewl.aos_core.model.world.WorldIO;
|
||||||
import nl.andrewl.aos_core.net.TcpReceiver;
|
import nl.andrewl.aos_core.net.*;
|
||||||
import nl.andrewl.aos_core.net.UdpReceiver;
|
|
||||||
import nl.andrewl.aos_core.net.connect.ConnectAcceptMessage;
|
import nl.andrewl.aos_core.net.connect.ConnectAcceptMessage;
|
||||||
import nl.andrewl.aos_core.net.connect.ConnectRejectMessage;
|
import nl.andrewl.aos_core.net.connect.ConnectRejectMessage;
|
||||||
import nl.andrewl.aos_core.net.connect.ConnectRequestMessage;
|
import nl.andrewl.aos_core.net.connect.ConnectRequestMessage;
|
||||||
|
@ -18,6 +16,8 @@ import nl.andrewl.record_net.Message;
|
||||||
import nl.andrewl.record_net.util.ExtendedDataInputStream;
|
import nl.andrewl.record_net.util.ExtendedDataInputStream;
|
||||||
import nl.andrewl.record_net.util.ExtendedDataOutputStream;
|
import nl.andrewl.record_net.util.ExtendedDataOutputStream;
|
||||||
import org.joml.Vector3f;
|
import org.joml.Vector3f;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.DatagramPacket;
|
import java.net.DatagramPacket;
|
||||||
|
@ -31,6 +31,8 @@ import java.net.Socket;
|
||||||
* methods for sending messages and processing those we receive.
|
* methods for sending messages and processing those we receive.
|
||||||
*/
|
*/
|
||||||
public class CommunicationHandler {
|
public class CommunicationHandler {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(CommunicationHandler.class);
|
||||||
|
|
||||||
private final Client client;
|
private final Client client;
|
||||||
private Socket socket;
|
private Socket socket;
|
||||||
private DatagramSocket datagramSocket;
|
private DatagramSocket datagramSocket;
|
||||||
|
@ -47,14 +49,16 @@ public class CommunicationHandler {
|
||||||
if (socket != null && !socket.isClosed()) {
|
if (socket != null && !socket.isClosed()) {
|
||||||
socket.close();
|
socket.close();
|
||||||
}
|
}
|
||||||
InetAddress address = InetAddress.getByName(client.connectConfig.host());
|
InetAddress address = InetAddress.getByName(client.getConfig().serverHost);
|
||||||
System.out.printf("Connecting to server at %s, port %d, with username \"%s\"...%n", address, client.connectConfig.port(), client.connectConfig.username());
|
int port = client.getConfig().serverPort;
|
||||||
|
String username = client.getConfig().username;
|
||||||
|
log.info("Connecting to server at {}, port {}, with username \"{}\"...", address, port, username);
|
||||||
|
|
||||||
socket = new Socket(address, client.connectConfig.port());
|
socket = new Socket(address, port);
|
||||||
socket.setSoTimeout(1000);
|
socket.setSoTimeout(1000);
|
||||||
in = Net.getInputStream(socket.getInputStream());
|
in = Net.getInputStream(socket.getInputStream());
|
||||||
out = Net.getOutputStream(socket.getOutputStream());
|
out = Net.getOutputStream(socket.getOutputStream());
|
||||||
Net.write(new ConnectRequestMessage(client.connectConfig.username(), client.connectConfig.spectator()), out);
|
Net.write(new ConnectRequestMessage(username), out);
|
||||||
Message response = Net.read(in);
|
Message response = Net.read(in);
|
||||||
socket.setSoTimeout(0);
|
socket.setSoTimeout(0);
|
||||||
if (response instanceof ConnectRejectMessage rejectMessage) {
|
if (response instanceof ConnectRejectMessage rejectMessage) {
|
||||||
|
@ -62,9 +66,12 @@ public class CommunicationHandler {
|
||||||
}
|
}
|
||||||
if (response instanceof ConnectAcceptMessage acceptMessage) {
|
if (response instanceof ConnectAcceptMessage acceptMessage) {
|
||||||
this.clientId = acceptMessage.clientId();
|
this.clientId = acceptMessage.clientId();
|
||||||
client.setMyPlayer(new ClientPlayer(clientId, client.connectConfig.username()));
|
log.debug("Connection accepted. My client id is {}.", clientId);
|
||||||
|
client.setMyPlayer(new ClientPlayer(clientId, username));
|
||||||
receiveInitialData();
|
receiveInitialData();
|
||||||
|
log.debug("Initial data received.");
|
||||||
establishDatagramConnection();
|
establishDatagramConnection();
|
||||||
|
log.info("Connection to server established. My client id is {}.", clientId);
|
||||||
new Thread(new TcpReceiver(in, client::onMessageReceived).withShutdownHook(this::shutdown)).start();
|
new Thread(new TcpReceiver(in, client::onMessageReceived).withShutdownHook(this::shutdown)).start();
|
||||||
new Thread(new UdpReceiver(datagramSocket, (msg, packet) -> client.onMessageReceived(msg))).start();
|
new Thread(new UdpReceiver(datagramSocket, (msg, packet) -> client.onMessageReceived(msg))).start();
|
||||||
} else {
|
} else {
|
||||||
|
@ -131,6 +138,7 @@ public class CommunicationHandler {
|
||||||
if (!connectionEstablished) {
|
if (!connectionEstablished) {
|
||||||
throw new IOException("Could not establish a datagram connection to the server after " + attempts + " attempts.");
|
throw new IOException("Could not establish a datagram connection to the server after " + attempts + " attempts.");
|
||||||
}
|
}
|
||||||
|
log.debug("Established datagram communication with the server.");
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getClientId() {
|
public int getClientId() {
|
||||||
|
@ -167,13 +175,13 @@ public class CommunicationHandler {
|
||||||
OtherPlayer player = new OtherPlayer(in.readInt(), in.readString());
|
OtherPlayer player = new OtherPlayer(in.readInt(), in.readString());
|
||||||
int teamId = in.readInt();
|
int teamId = in.readInt();
|
||||||
if (teamId != -1) player.setTeam(client.getTeams().get(teamId));
|
if (teamId != -1) player.setTeam(client.getTeams().get(teamId));
|
||||||
|
System.out.println(teamId);
|
||||||
player.getPosition().set(in.readFloat(), in.readFloat(), in.readFloat());
|
player.getPosition().set(in.readFloat(), in.readFloat(), in.readFloat());
|
||||||
player.getVelocity().set(in.readFloat(), in.readFloat(), in.readFloat());
|
player.getVelocity().set(in.readFloat(), in.readFloat(), in.readFloat());
|
||||||
player.getOrientation().set(in.readFloat(), in.readFloat());
|
player.getOrientation().set(in.readFloat(), in.readFloat());
|
||||||
player.setCrouching(in.readBoolean());
|
player.setCrouching(in.readBoolean());
|
||||||
player.setHeldItemId(in.readInt());
|
player.setHeldItemId(in.readInt());
|
||||||
player.setSelectedBlockValue(in.readByte());
|
player.setSelectedBlockValue(in.readByte());
|
||||||
player.setMode(PlayerMode.values()[in.readInt()]);
|
|
||||||
client.getPlayers().put(player.getId(), player);
|
client.getPlayers().put(player.getId(), player);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -189,6 +197,5 @@ public class CommunicationHandler {
|
||||||
int teamId = in.readInt();
|
int teamId = in.readInt();
|
||||||
if (teamId != -1) client.getMyPlayer().setTeam(client.getTeams().get(teamId));
|
if (teamId != -1) client.getMyPlayer().setTeam(client.getTeams().get(teamId));
|
||||||
client.getMyPlayer().getPosition().set(in.readFloat(), in.readFloat(), in.readFloat());
|
client.getMyPlayer().getPosition().set(in.readFloat(), in.readFloat(), in.readFloat());
|
||||||
client.getMyPlayer().setMode(PlayerMode.values()[in.readInt()]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
package nl.andrewl.aos2_client.config;
|
package nl.andrewl.aos2_client.config;
|
||||||
|
|
||||||
public class ClientConfig {
|
public class ClientConfig {
|
||||||
|
public String serverHost = "localhost";
|
||||||
|
public int serverPort = 25565;
|
||||||
|
public String username = "player";
|
||||||
public InputConfig input = new InputConfig();
|
public InputConfig input = new InputConfig();
|
||||||
public DisplayConfig display = new DisplayConfig();
|
public DisplayConfig display = new DisplayConfig();
|
||||||
|
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
package nl.andrewl.aos2_client.config;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The data that's needed by the client to initially establish a connection.
|
|
||||||
*/
|
|
||||||
public record ConnectConfig(
|
|
||||||
String host,
|
|
||||||
int port,
|
|
||||||
String username,
|
|
||||||
boolean spectator
|
|
||||||
) {}
|
|
|
@ -1,23 +0,0 @@
|
||||||
package nl.andrewl.aos2_client.control;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a particular context in which player input is obtained. Different
|
|
||||||
* contexts do different things with the input. The main game, for example, will
|
|
||||||
* move the player when WASD keys are pressed, and a chatting context might type
|
|
||||||
* out the keys the player presses into a chat buffer. Contexts may choose to
|
|
||||||
* implement only some specified methods here.
|
|
||||||
*/
|
|
||||||
public interface InputContext {
|
|
||||||
default void onEnable() {}
|
|
||||||
default void onDisable() {}
|
|
||||||
|
|
||||||
default void keyPress(long window, int key, int mods) {}
|
|
||||||
default void keyRelease(long window, int key, int mods) {}
|
|
||||||
default void keyRepeat(long window, int key, int mods) {}
|
|
||||||
default void charInput(long window, int codePoint) {}
|
|
||||||
|
|
||||||
default void mouseButtonPress(long window, int button, int mods) {}
|
|
||||||
default void mouseButtonRelease(long window, int button, int mods) {}
|
|
||||||
default void mouseScroll(long window, double xOffset, double yOffset) {}
|
|
||||||
default void mouseCursorPos(long window, double xPos, double yPos) {}
|
|
||||||
}
|
|
|
@ -1,11 +1,14 @@
|
||||||
package nl.andrewl.aos2_client.control;
|
package nl.andrewl.aos2_client.control;
|
||||||
|
|
||||||
import nl.andrewl.aos2_client.Camera;
|
|
||||||
import nl.andrewl.aos2_client.Client;
|
import nl.andrewl.aos2_client.Client;
|
||||||
import nl.andrewl.aos2_client.CommunicationHandler;
|
import nl.andrewl.aos2_client.CommunicationHandler;
|
||||||
import nl.andrewl.aos2_client.control.context.ChattingContext;
|
import nl.andrewl.aos2_client.model.ClientPlayer;
|
||||||
import nl.andrewl.aos2_client.control.context.ExitMenuContext;
|
import nl.andrewl.aos_core.model.item.BlockItemStack;
|
||||||
import nl.andrewl.aos2_client.control.context.NormalContext;
|
import nl.andrewl.aos_core.model.world.Hit;
|
||||||
|
import nl.andrewl.aos_core.net.client.BlockColorMessage;
|
||||||
|
import nl.andrewl.aos_core.net.client.ClientInputState;
|
||||||
|
|
||||||
|
import static org.lwjgl.glfw.GLFW.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class which manages the player's input, and sending it to the server.
|
* Class which manages the player's input, and sending it to the server.
|
||||||
|
@ -14,83 +17,65 @@ public class InputHandler {
|
||||||
private final Client client;
|
private final Client client;
|
||||||
private final CommunicationHandler comm;
|
private final CommunicationHandler comm;
|
||||||
|
|
||||||
private long windowId;
|
private ClientInputState lastInputState = null;
|
||||||
|
|
||||||
private final NormalContext normalContext;
|
private boolean forward;
|
||||||
private final ChattingContext chattingContext;
|
private boolean backward;
|
||||||
private final ExitMenuContext exitMenuContext;
|
private boolean left;
|
||||||
|
private boolean right;
|
||||||
|
private boolean jumping;
|
||||||
|
private boolean crouching;
|
||||||
|
private boolean sprinting;
|
||||||
|
private boolean hitting;
|
||||||
|
private boolean interacting;
|
||||||
|
private boolean reloading;
|
||||||
|
|
||||||
private InputContext activeContext;
|
|
||||||
|
|
||||||
public InputHandler(Client client, CommunicationHandler comm, Camera cam) {
|
public InputHandler(Client client, CommunicationHandler comm) {
|
||||||
this.client = client;
|
this.client = client;
|
||||||
this.comm = comm;
|
this.comm = comm;
|
||||||
this.normalContext = new NormalContext(this, cam);
|
|
||||||
this.chattingContext = new ChattingContext(this);
|
|
||||||
this.exitMenuContext = new ExitMenuContext(this);
|
|
||||||
this.activeContext = normalContext;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setWindowId(long windowId) {
|
public void updateInputState(long window) {
|
||||||
this.windowId = windowId;
|
// TODO: Allow customized keybindings.
|
||||||
}
|
int selectedInventoryIndex;
|
||||||
|
selectedInventoryIndex = client.getMyPlayer().getInventory().getSelectedIndex();
|
||||||
|
if (glfwGetKey(window, GLFW_KEY_1) == GLFW_PRESS) selectedInventoryIndex = 0;
|
||||||
|
if (glfwGetKey(window, GLFW_KEY_2) == GLFW_PRESS) selectedInventoryIndex = 1;
|
||||||
|
if (glfwGetKey(window, GLFW_KEY_3) == GLFW_PRESS) selectedInventoryIndex = 2;
|
||||||
|
if (glfwGetKey(window, GLFW_KEY_4) == GLFW_PRESS) selectedInventoryIndex = 3;
|
||||||
|
|
||||||
public InputContext getActiveContext() {
|
ClientInputState currentInputState = new ClientInputState(
|
||||||
return activeContext;
|
comm.getClientId(),
|
||||||
}
|
glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS,
|
||||||
|
glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS,
|
||||||
|
glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS,
|
||||||
|
glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS,
|
||||||
|
glfwGetKey(window, GLFW_KEY_SPACE) == GLFW_PRESS,
|
||||||
|
glfwGetKey(window, GLFW_KEY_LEFT_CONTROL) == GLFW_PRESS,
|
||||||
|
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)) {
|
||||||
|
comm.sendDatagramPacket(currentInputState);
|
||||||
|
lastInputState = currentInputState;
|
||||||
|
}
|
||||||
|
|
||||||
private void switchToContext(InputContext newContext) {
|
ClientPlayer player = client.getMyPlayer();
|
||||||
if (newContext.equals(activeContext)) return;
|
|
||||||
activeContext.onDisable();
|
|
||||||
newContext.onEnable();
|
|
||||||
activeContext = newContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void switchToNormalContext() {
|
// Check for "pick block" functionality.
|
||||||
switchToContext(normalContext);
|
if (glfwGetMouseButton(window, GLFW_MOUSE_BUTTON_3) == GLFW_PRESS && player.getInventory().getSelectedItemStack() instanceof BlockItemStack stack) {
|
||||||
}
|
Hit hit = client.getWorld().getLookingAtPos(player.getEyePosition(), player.getViewVector(), 50);
|
||||||
|
if (hit != null) {
|
||||||
public void switchToChattingContext() {
|
byte selectedBlock = client.getWorld().getBlockAt(hit.pos().x, hit.pos().y, hit.pos().z);
|
||||||
switchToContext(chattingContext);
|
if (selectedBlock > 0) {
|
||||||
}
|
stack.setSelectedValue(selectedBlock);
|
||||||
|
comm.sendDatagramPacket(new BlockColorMessage(player.getId(), selectedBlock));
|
||||||
public void switchToExitMenuContext() {
|
}
|
||||||
switchToContext(exitMenuContext);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public NormalContext getNormalContext() {
|
|
||||||
return normalContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ChattingContext getChattingContext() {
|
|
||||||
return chattingContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ExitMenuContext getExitMenuContext() {
|
|
||||||
return exitMenuContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isNormalContextActive() {
|
|
||||||
return normalContext.equals(activeContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isChattingContextActive() {
|
|
||||||
return chattingContext.equals(activeContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isExitMenuContextActive() {
|
|
||||||
return exitMenuContext.equals(activeContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Client getClient() {
|
|
||||||
return client;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CommunicationHandler getComm() {
|
|
||||||
return comm;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getWindowId() {
|
|
||||||
return windowId;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
package nl.andrewl.aos2_client.control;
|
|
||||||
|
|
||||||
import org.lwjgl.glfw.GLFWCharCallbackI;
|
|
||||||
|
|
||||||
public class PlayerCharacterInputCallback implements GLFWCharCallbackI {
|
|
||||||
private final InputHandler inputHandler;
|
|
||||||
|
|
||||||
public PlayerCharacterInputCallback(InputHandler inputHandler) {
|
|
||||||
this.inputHandler = inputHandler;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void invoke(long window, int codepoint) {
|
|
||||||
inputHandler.getActiveContext().charInput(window, codepoint);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -13,10 +13,9 @@ public class PlayerInputKeyCallback implements GLFWKeyCallbackI {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void invoke(long window, int key, int scancode, int action, int mods) {
|
public void invoke(long window, int key, int scancode, int action, int mods) {
|
||||||
switch (action) {
|
if (key == GLFW_KEY_ESCAPE && action == GLFW_RELEASE) {
|
||||||
case GLFW_PRESS -> inputHandler.getActiveContext().keyPress(window, key, mods);
|
glfwSetWindowShouldClose(window, true);
|
||||||
case GLFW_RELEASE -> inputHandler.getActiveContext().keyRelease(window, key, mods);
|
|
||||||
case GLFW_REPEAT -> inputHandler.getActiveContext().keyRepeat(window, key, mods);
|
|
||||||
}
|
}
|
||||||
|
inputHandler.updateInputState(window);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,6 @@ package nl.andrewl.aos2_client.control;
|
||||||
|
|
||||||
import org.lwjgl.glfw.GLFWMouseButtonCallbackI;
|
import org.lwjgl.glfw.GLFWMouseButtonCallbackI;
|
||||||
|
|
||||||
import static org.lwjgl.glfw.GLFW.GLFW_PRESS;
|
|
||||||
import static org.lwjgl.glfw.GLFW.GLFW_RELEASE;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback that's called when the player clicks with their mouse.
|
* Callback that's called when the player clicks with their mouse.
|
||||||
*/
|
*/
|
||||||
|
@ -17,9 +14,6 @@ public class PlayerInputMouseClickCallback implements GLFWMouseButtonCallbackI {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void invoke(long window, int button, int action, int mods) {
|
public void invoke(long window, int button, int action, int mods) {
|
||||||
switch (action) {
|
inputHandler.updateInputState(window);
|
||||||
case GLFW_PRESS -> inputHandler.getActiveContext().mouseButtonPress(window, button, mods);
|
|
||||||
case GLFW_RELEASE -> inputHandler.getActiveContext().mouseButtonRelease(window, button, mods);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,29 @@
|
||||||
package nl.andrewl.aos2_client.control;
|
package nl.andrewl.aos2_client.control;
|
||||||
|
|
||||||
|
import nl.andrewl.aos2_client.Client;
|
||||||
|
import nl.andrewl.aos2_client.CommunicationHandler;
|
||||||
|
import nl.andrewl.aos_core.model.item.BlockItemStack;
|
||||||
|
import nl.andrewl.aos_core.net.client.BlockColorMessage;
|
||||||
import org.lwjgl.glfw.GLFWScrollCallbackI;
|
import org.lwjgl.glfw.GLFWScrollCallbackI;
|
||||||
|
|
||||||
public class PlayerInputMouseScrollCallback implements GLFWScrollCallbackI {
|
public class PlayerInputMouseScrollCallback implements GLFWScrollCallbackI {
|
||||||
private final InputHandler inputHandler;
|
private final Client client;
|
||||||
|
private final CommunicationHandler comm;
|
||||||
|
|
||||||
public PlayerInputMouseScrollCallback(InputHandler inputHandler) {
|
public PlayerInputMouseScrollCallback(Client client, CommunicationHandler comm) {
|
||||||
this.inputHandler = inputHandler;
|
this.client = client;
|
||||||
|
this.comm = comm;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void invoke(long window, double xoffset, double yoffset) {
|
public void invoke(long window, double xoffset, double yoffset) {
|
||||||
inputHandler.getActiveContext().mouseScroll(window, xoffset, yoffset);
|
if (client.getMyPlayer().getInventory().getSelectedItemStack() instanceof BlockItemStack stack) {
|
||||||
|
if (yoffset < 0) {
|
||||||
|
stack.setSelectedValue((byte) (stack.getSelectedValue() - 1));
|
||||||
|
} else if (yoffset > 0) {
|
||||||
|
stack.setSelectedValue((byte) (stack.getSelectedValue() + 1));
|
||||||
|
}
|
||||||
|
comm.sendDatagramPacket(new BlockColorMessage(client.getMyPlayer().getId(), stack.getSelectedValue()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,66 @@
|
||||||
package nl.andrewl.aos2_client.control;
|
package nl.andrewl.aos2_client.control;
|
||||||
|
|
||||||
|
import nl.andrewl.aos2_client.Camera;
|
||||||
|
import nl.andrewl.aos2_client.Client;
|
||||||
|
import nl.andrewl.aos2_client.CommunicationHandler;
|
||||||
|
import nl.andrewl.aos2_client.config.ClientConfig;
|
||||||
|
import nl.andrewl.aos_core.net.client.ClientOrientationState;
|
||||||
import org.lwjgl.glfw.GLFWCursorPosCallbackI;
|
import org.lwjgl.glfw.GLFWCursorPosCallbackI;
|
||||||
|
|
||||||
|
import java.util.concurrent.ForkJoinPool;
|
||||||
|
|
||||||
|
import static org.lwjgl.glfw.GLFW.glfwGetCursorPos;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback that's called when the player's cursor position updates. This means
|
||||||
|
* the player is looking around.
|
||||||
|
*/
|
||||||
public class PlayerViewCursorCallback implements GLFWCursorPosCallbackI {
|
public class PlayerViewCursorCallback implements GLFWCursorPosCallbackI {
|
||||||
|
/**
|
||||||
|
* The number of milliseconds to wait before sending orientation updates,
|
||||||
|
* to prevent overloading the server.
|
||||||
|
*/
|
||||||
|
private static final int ORIENTATION_UPDATE_LIMIT = 20;
|
||||||
|
|
||||||
private final InputHandler inputHandler;
|
private final ClientConfig.InputConfig config;
|
||||||
|
private final Client client;
|
||||||
|
private final Camera camera;
|
||||||
|
private final CommunicationHandler comm;
|
||||||
|
private float lastMouseCursorX;
|
||||||
|
private float lastMouseCursorY;
|
||||||
|
private long lastOrientationUpdateSentAt = 0L;
|
||||||
|
|
||||||
public PlayerViewCursorCallback(InputHandler inputHandler) {
|
public PlayerViewCursorCallback(ClientConfig.InputConfig config, Client client, Camera cam, CommunicationHandler comm) {
|
||||||
this.inputHandler = inputHandler;
|
this.config = config;
|
||||||
|
this.client = client;
|
||||||
|
this.camera = cam;
|
||||||
|
this.comm = comm;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void invoke(long window, double xpos, double ypos) {
|
public void invoke(long window, double xpos, double ypos) {
|
||||||
inputHandler.getActiveContext().mouseCursorPos(window, xpos, ypos);
|
double[] xb = new double[1];
|
||||||
|
double[] yb = new double[1];
|
||||||
|
glfwGetCursorPos(window, xb, yb);
|
||||||
|
float x = (float) xb[0];
|
||||||
|
float y = (float) yb[0];
|
||||||
|
float dx = x - lastMouseCursorX;
|
||||||
|
float dy = y - lastMouseCursorY;
|
||||||
|
lastMouseCursorX = x;
|
||||||
|
lastMouseCursorY = y;
|
||||||
|
client.getMyPlayer().setOrientation(
|
||||||
|
client.getMyPlayer().getOrientation().x - dx * config.mouseSensitivity,
|
||||||
|
client.getMyPlayer().getOrientation().y - dy * config.mouseSensitivity
|
||||||
|
);
|
||||||
|
camera.setOrientationToPlayer(client.getMyPlayer());
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
if (lastOrientationUpdateSentAt + ORIENTATION_UPDATE_LIMIT < now) {
|
||||||
|
ForkJoinPool.commonPool().submit(() -> comm.sendDatagramPacket(new ClientOrientationState(
|
||||||
|
client.getMyPlayer().getId(),
|
||||||
|
client.getMyPlayer().getOrientation().x,
|
||||||
|
client.getMyPlayer().getOrientation().y
|
||||||
|
)));
|
||||||
|
lastOrientationUpdateSentAt = now;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,88 +0,0 @@
|
||||||
package nl.andrewl.aos2_client.control.context;
|
|
||||||
|
|
||||||
import nl.andrewl.aos2_client.control.InputContext;
|
|
||||||
import nl.andrewl.aos2_client.control.InputHandler;
|
|
||||||
import nl.andrewl.aos2_client.util.WindowUtils;
|
|
||||||
import nl.andrewl.aos_core.net.client.ChatWrittenMessage;
|
|
||||||
|
|
||||||
import static org.lwjgl.glfw.GLFW.*;
|
|
||||||
|
|
||||||
public class ChattingContext implements InputContext {
|
|
||||||
private static final int MAX_LENGTH = 120;
|
|
||||||
|
|
||||||
private final InputHandler inputHandler;
|
|
||||||
|
|
||||||
private final StringBuffer chatBuffer = new StringBuffer(MAX_LENGTH);
|
|
||||||
|
|
||||||
public ChattingContext(InputHandler inputHandler) {
|
|
||||||
this.inputHandler = inputHandler;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void appendToChat(int codePoint) {
|
|
||||||
appendToChat(Character.toString(codePoint));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void appendToChat(String content) {
|
|
||||||
if (chatBuffer.length() + content.length() > MAX_LENGTH) return;
|
|
||||||
chatBuffer.append(content);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void deleteFromChat() {
|
|
||||||
if (chatBuffer.length() > 0) {
|
|
||||||
chatBuffer.deleteCharAt(chatBuffer.length() - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void clearChatBuffer() {
|
|
||||||
chatBuffer.delete(0, chatBuffer.length());
|
|
||||||
}
|
|
||||||
|
|
||||||
private void sendChat() {
|
|
||||||
String text = chatBuffer.toString().trim();
|
|
||||||
if (!text.isBlank()) {
|
|
||||||
inputHandler.getComm().sendMessage(new ChatWrittenMessage(text));
|
|
||||||
}
|
|
||||||
inputHandler.switchToNormalContext();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getChatBufferText() {
|
|
||||||
return new String(chatBuffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onEnable() {
|
|
||||||
clearChatBuffer();
|
|
||||||
if (inputHandler.getClient().getConfig().display.captureCursor) {
|
|
||||||
WindowUtils.freeCursor(inputHandler.getWindowId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDisable() {
|
|
||||||
clearChatBuffer();
|
|
||||||
if (inputHandler.getClient().getConfig().display.captureCursor) {
|
|
||||||
WindowUtils.captureCursor(inputHandler.getWindowId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void keyPress(long window, int key, int mods) {
|
|
||||||
switch (key) {
|
|
||||||
case GLFW_KEY_BACKSPACE -> deleteFromChat();
|
|
||||||
case GLFW_KEY_ENTER -> sendChat();
|
|
||||||
case GLFW_KEY_ESCAPE -> inputHandler.switchToNormalContext();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void keyRepeat(long window, int key, int mods) {
|
|
||||||
switch (key) {
|
|
||||||
case GLFW_KEY_BACKSPACE -> deleteFromChat();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void charInput(long window, int codePoint) {
|
|
||||||
appendToChat(codePoint);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,38 +0,0 @@
|
||||||
package nl.andrewl.aos2_client.control.context;
|
|
||||||
|
|
||||||
import nl.andrewl.aos2_client.control.InputContext;
|
|
||||||
import nl.andrewl.aos2_client.control.InputHandler;
|
|
||||||
import nl.andrewl.aos2_client.util.WindowUtils;
|
|
||||||
|
|
||||||
import static org.lwjgl.glfw.GLFW.GLFW_KEY_ESCAPE;
|
|
||||||
import static org.lwjgl.glfw.GLFW.glfwSetWindowShouldClose;
|
|
||||||
|
|
||||||
public class ExitMenuContext implements InputContext {
|
|
||||||
private final InputHandler inputHandler;
|
|
||||||
|
|
||||||
public ExitMenuContext(InputHandler inputHandler) {
|
|
||||||
this.inputHandler = inputHandler;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onEnable() {
|
|
||||||
if (inputHandler.getClient().getConfig().display.captureCursor) {
|
|
||||||
WindowUtils.freeCursor(inputHandler.getWindowId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDisable() {
|
|
||||||
if (inputHandler.getClient().getConfig().display.captureCursor) {
|
|
||||||
WindowUtils.captureCursor(inputHandler.getWindowId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void keyPress(long window, int key, int mods) {
|
|
||||||
switch (key) {
|
|
||||||
case GLFW_KEY_ESCAPE -> glfwSetWindowShouldClose(window, true);
|
|
||||||
default -> inputHandler.switchToNormalContext();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,287 +0,0 @@
|
||||||
package nl.andrewl.aos2_client.control.context;
|
|
||||||
|
|
||||||
import nl.andrewl.aos2_client.Camera;
|
|
||||||
import nl.andrewl.aos2_client.control.InputContext;
|
|
||||||
import nl.andrewl.aos2_client.control.InputHandler;
|
|
||||||
import nl.andrewl.aos2_client.util.WindowUtils;
|
|
||||||
import nl.andrewl.aos_core.model.item.BlockItemStack;
|
|
||||||
import nl.andrewl.aos_core.model.item.GunItemStack;
|
|
||||||
import nl.andrewl.aos_core.model.item.ItemStack;
|
|
||||||
import nl.andrewl.aos_core.model.world.Hit;
|
|
||||||
import nl.andrewl.aos_core.net.client.BlockColorMessage;
|
|
||||||
import nl.andrewl.aos_core.net.client.ClientInputState;
|
|
||||||
import nl.andrewl.aos_core.net.client.ClientOrientationState;
|
|
||||||
|
|
||||||
import java.util.concurrent.ForkJoinPool;
|
|
||||||
|
|
||||||
import static org.lwjgl.glfw.GLFW.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The normal input context that occurs when the player is active in the game.
|
|
||||||
* This includes moving around, interacting with their inventory, moving their
|
|
||||||
* view, and so on.
|
|
||||||
*/
|
|
||||||
public class NormalContext implements InputContext {
|
|
||||||
/**
|
|
||||||
* The number of milliseconds to wait before sending orientation updates,
|
|
||||||
* to prevent overloading the server.
|
|
||||||
*/
|
|
||||||
private static final int ORIENTATION_UPDATE_LIMIT = 20;
|
|
||||||
|
|
||||||
private final InputHandler inputHandler;
|
|
||||||
private final Camera camera;
|
|
||||||
|
|
||||||
private ClientInputState lastInputState = null;
|
|
||||||
|
|
||||||
private boolean forward;
|
|
||||||
private boolean backward;
|
|
||||||
private boolean left;
|
|
||||||
private boolean right;
|
|
||||||
private boolean jumping;
|
|
||||||
private boolean crouching;
|
|
||||||
private boolean sprinting;
|
|
||||||
private boolean hitting;
|
|
||||||
private boolean interacting;
|
|
||||||
private boolean reloading;
|
|
||||||
private int selectedInventoryIndex;
|
|
||||||
|
|
||||||
private boolean debugEnabled;
|
|
||||||
|
|
||||||
private float lastMouseCursorX;
|
|
||||||
private float lastMouseCursorY;
|
|
||||||
private long lastOrientationUpdateSentAt = 0L;
|
|
||||||
|
|
||||||
public NormalContext(InputHandler inputHandler, Camera camera) {
|
|
||||||
this.inputHandler = inputHandler;
|
|
||||||
this.camera = camera;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void updateInputState() {
|
|
||||||
var comm = inputHandler.getComm();
|
|
||||||
ClientInputState currentInputState = new ClientInputState(
|
|
||||||
comm.getClientId(),
|
|
||||||
forward, backward, left, right,
|
|
||||||
jumping, crouching, sprinting,
|
|
||||||
hitting, interacting, reloading,
|
|
||||||
selectedInventoryIndex
|
|
||||||
);
|
|
||||||
if (!currentInputState.equals(lastInputState)) {
|
|
||||||
comm.sendDatagramPacket(currentInputState);
|
|
||||||
lastInputState = currentInputState;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void resetInputState() {
|
|
||||||
forward = false;
|
|
||||||
backward = false;
|
|
||||||
left = false;
|
|
||||||
right = false;
|
|
||||||
jumping = false;
|
|
||||||
crouching = false;
|
|
||||||
sprinting = false;
|
|
||||||
hitting = false;
|
|
||||||
interacting = false;
|
|
||||||
reloading = false;
|
|
||||||
var size = WindowUtils.getSize(inputHandler.getWindowId());
|
|
||||||
lastMouseCursorX = size.first() / 2f;
|
|
||||||
lastMouseCursorY = size.second() / 2f;
|
|
||||||
updateInputState();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setForward(boolean forward) {
|
|
||||||
this.forward = forward;
|
|
||||||
updateInputState();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBackward(boolean backward) {
|
|
||||||
this.backward = backward;
|
|
||||||
updateInputState();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLeft(boolean left) {
|
|
||||||
this.left = left;
|
|
||||||
updateInputState();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRight(boolean right) {
|
|
||||||
this.right = right;
|
|
||||||
updateInputState();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setJumping(boolean jumping) {
|
|
||||||
this.jumping = jumping;
|
|
||||||
updateInputState();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCrouching(boolean crouching) {
|
|
||||||
this.crouching = crouching;
|
|
||||||
updateInputState();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSprinting(boolean sprinting) {
|
|
||||||
this.sprinting = sprinting;
|
|
||||||
updateInputState();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setHitting(boolean hitting) {
|
|
||||||
this.hitting = hitting;
|
|
||||||
updateInputState();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setInteracting(boolean interacting) {
|
|
||||||
this.interacting = interacting;
|
|
||||||
updateInputState();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setReloading(boolean reloading) {
|
|
||||||
this.reloading = reloading;
|
|
||||||
updateInputState();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSelectedInventoryIndex(int selectedInventoryIndex) {
|
|
||||||
this.selectedInventoryIndex = selectedInventoryIndex;
|
|
||||||
updateInputState();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void toggleDebugEnabled() {
|
|
||||||
this.debugEnabled = !debugEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void enableChatting() {
|
|
||||||
inputHandler.switchToChattingContext();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onEnable() {
|
|
||||||
resetInputState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDisable() {
|
|
||||||
resetInputState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void keyPress(long window, int key, int mods) {
|
|
||||||
switch (key) {
|
|
||||||
case GLFW_KEY_W -> setForward(true);
|
|
||||||
case GLFW_KEY_A -> setLeft(true);
|
|
||||||
case GLFW_KEY_S -> setBackward(true);
|
|
||||||
case GLFW_KEY_D -> setRight(true);
|
|
||||||
case GLFW_KEY_SPACE -> setJumping(true);
|
|
||||||
case GLFW_KEY_LEFT_CONTROL -> setCrouching(true);
|
|
||||||
case GLFW_KEY_LEFT_SHIFT -> setSprinting(true);
|
|
||||||
case GLFW_KEY_R -> setReloading(true);
|
|
||||||
|
|
||||||
case GLFW_KEY_1 -> setSelectedInventoryIndex(0);
|
|
||||||
case GLFW_KEY_2 -> setSelectedInventoryIndex(1);
|
|
||||||
case GLFW_KEY_3 -> setSelectedInventoryIndex(2);
|
|
||||||
case GLFW_KEY_4 -> setSelectedInventoryIndex(3);
|
|
||||||
|
|
||||||
case GLFW_KEY_F3 -> toggleDebugEnabled();
|
|
||||||
case GLFW_KEY_ESCAPE -> inputHandler.switchToExitMenuContext();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void keyRelease(long window, int key, int mods) {
|
|
||||||
switch (key) {
|
|
||||||
case GLFW_KEY_W -> setForward(false);
|
|
||||||
case GLFW_KEY_A -> setLeft(false);
|
|
||||||
case GLFW_KEY_S -> setBackward(false);
|
|
||||||
case GLFW_KEY_D -> setRight(false);
|
|
||||||
case GLFW_KEY_SPACE -> setJumping(false);
|
|
||||||
case GLFW_KEY_LEFT_CONTROL -> setCrouching(false);
|
|
||||||
case GLFW_KEY_LEFT_SHIFT -> setSprinting(false);
|
|
||||||
case GLFW_KEY_R -> setReloading(false);
|
|
||||||
|
|
||||||
case GLFW_KEY_T -> enableChatting();
|
|
||||||
case GLFW_KEY_SLASH -> {
|
|
||||||
enableChatting();
|
|
||||||
inputHandler.getChattingContext().appendToChat("/");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void mouseButtonPress(long window, int button, int mods) {
|
|
||||||
switch (button) {
|
|
||||||
case GLFW_MOUSE_BUTTON_1 -> setHitting(true);
|
|
||||||
case GLFW_MOUSE_BUTTON_2 -> setInteracting(true);
|
|
||||||
case GLFW_MOUSE_BUTTON_3 -> pickBlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void mouseButtonRelease(long window, int button, int mods) {
|
|
||||||
switch (button) {
|
|
||||||
case GLFW_MOUSE_BUTTON_1 -> setHitting(false);
|
|
||||||
case GLFW_MOUSE_BUTTON_2 -> setInteracting(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void mouseScroll(long window, double xOffset, double yOffset) {
|
|
||||||
var player = inputHandler.getClient().getMyPlayer();
|
|
||||||
ItemStack stack = player.getInventory().getSelectedItemStack();
|
|
||||||
if (stack instanceof BlockItemStack blockStack) {
|
|
||||||
if (yOffset < 0) {
|
|
||||||
blockStack.setSelectedValue((byte) (blockStack.getSelectedValue() - 1));
|
|
||||||
} else if (yOffset > 0) {
|
|
||||||
blockStack.setSelectedValue((byte) (blockStack.getSelectedValue() + 1));
|
|
||||||
}
|
|
||||||
inputHandler.getComm().sendDatagramPacket(new BlockColorMessage(player.getId(), blockStack.getSelectedValue()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void mouseCursorPos(long window, double xPos, double yPos) {
|
|
||||||
double[] xb = new double[1];
|
|
||||||
double[] yb = new double[1];
|
|
||||||
glfwGetCursorPos(window, xb, yb);
|
|
||||||
float x = (float) xb[0];
|
|
||||||
float y = (float) yb[0];
|
|
||||||
float dx = x - lastMouseCursorX;
|
|
||||||
float dy = y - lastMouseCursorY;
|
|
||||||
lastMouseCursorX = x;
|
|
||||||
lastMouseCursorY = y;
|
|
||||||
var client = inputHandler.getClient();
|
|
||||||
float trueSensitivity = inputHandler.getClient().getConfig().input.mouseSensitivity;
|
|
||||||
if (isScopeEnabled()) trueSensitivity *= 0.1f;
|
|
||||||
client.getMyPlayer().setOrientation(
|
|
||||||
client.getMyPlayer().getOrientation().x - dx * trueSensitivity,
|
|
||||||
client.getMyPlayer().getOrientation().y - dy * trueSensitivity
|
|
||||||
);
|
|
||||||
camera.setOrientationToPlayer(client.getMyPlayer());
|
|
||||||
long now = System.currentTimeMillis();
|
|
||||||
if (lastOrientationUpdateSentAt + ORIENTATION_UPDATE_LIMIT < now) {
|
|
||||||
ForkJoinPool.commonPool().submit(() -> inputHandler.getComm().sendDatagramPacket(ClientOrientationState.fromPlayer(client.getMyPlayer())));
|
|
||||||
lastOrientationUpdateSentAt = now;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void pickBlock() {
|
|
||||||
var client = inputHandler.getClient();
|
|
||||||
var comm = inputHandler.getComm();
|
|
||||||
var player = client.getMyPlayer();
|
|
||||||
if (player.getInventory().getSelectedItemStack() instanceof BlockItemStack stack) {
|
|
||||||
Hit hit = client.getWorld().getLookingAtPos(player.getEyePosition(), player.getViewVector(), 50);
|
|
||||||
if (hit != null) {
|
|
||||||
byte selectedBlock = client.getWorld().getBlockAt(hit.pos().x, hit.pos().y, hit.pos().z);
|
|
||||||
if (selectedBlock > 0) {
|
|
||||||
stack.setSelectedValue(selectedBlock);
|
|
||||||
comm.sendDatagramPacket(new BlockColorMessage(player.getId(), selectedBlock));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isScopeEnabled() {
|
|
||||||
return interacting &&
|
|
||||||
inputHandler.getClient().getMyPlayer().getInventory().getSelectedItemStack() instanceof GunItemStack;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isDebugEnabled() {
|
|
||||||
return debugEnabled;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +1,6 @@
|
||||||
package nl.andrewl.aos2_client.model;
|
package nl.andrewl.aos2_client.model;
|
||||||
|
|
||||||
import nl.andrewl.aos2_client.Camera;
|
import nl.andrewl.aos2_client.Camera;
|
||||||
import nl.andrewl.aos2_client.control.InputHandler;
|
|
||||||
import nl.andrewl.aos_core.model.Player;
|
import nl.andrewl.aos_core.model.Player;
|
||||||
import nl.andrewl.aos_core.model.item.Inventory;
|
import nl.andrewl.aos_core.model.item.Inventory;
|
||||||
import org.joml.Matrix3f;
|
import org.joml.Matrix3f;
|
||||||
|
@ -43,16 +42,12 @@ public class ClientPlayer extends Player {
|
||||||
this.health = health;
|
this.health = health;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateHeldItemTransform(Camera cam, InputHandler inputHandler) {
|
public void updateHeldItemTransform(Camera cam) {
|
||||||
heldItemTransform.identity()
|
heldItemTransform.identity()
|
||||||
.translate(cam.getPosition())
|
.translate(cam.getPosition())
|
||||||
.rotate((float) (cam.getOrientation().x + Math.PI), Camera.UP)
|
.rotate((float) (cam.getOrientation().x + Math.PI), Camera.UP)
|
||||||
.rotate(-cam.getOrientation().y + (float) Math.PI / 2, Camera.RIGHT);
|
.rotate(-cam.getOrientation().y + (float) Math.PI / 2, Camera.RIGHT)
|
||||||
if (inputHandler.isNormalContextActive() && inputHandler.getNormalContext().isScopeEnabled()) {
|
.translate(-0.35f, -0.4f, 0.5f);
|
||||||
heldItemTransform.translate(0, -0.12f, 0);
|
|
||||||
} else {
|
|
||||||
heldItemTransform.translate(-0.35f, -0.4f, 0.5f);
|
|
||||||
}
|
|
||||||
heldItemTransform.get(heldItemTransformData);
|
heldItemTransform.get(heldItemTransformData);
|
||||||
|
|
||||||
heldItemTransform.normal(heldItemNormalTransform);
|
heldItemTransform.normal(heldItemNormalTransform);
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
package nl.andrewl.aos2_client.model;
|
package nl.andrewl.aos2_client.model;
|
||||||
|
|
||||||
import nl.andrewl.aos2_client.Camera;
|
import nl.andrewl.aos2_client.Camera;
|
||||||
import nl.andrewl.aos2_client.Client;
|
|
||||||
import nl.andrewl.aos_core.model.Player;
|
import nl.andrewl.aos_core.model.Player;
|
||||||
import nl.andrewl.aos_core.model.item.ItemTypes;
|
import nl.andrewl.aos_core.model.item.ItemTypes;
|
||||||
import nl.andrewl.aos_core.net.client.PlayerJoinMessage;
|
|
||||||
import org.joml.Matrix3f;
|
import org.joml.Matrix3f;
|
||||||
import org.joml.Matrix4f;
|
import org.joml.Matrix4f;
|
||||||
import org.joml.Vector3f;
|
import org.joml.Vector3f;
|
||||||
|
@ -101,18 +99,4 @@ public class OtherPlayer extends Player {
|
||||||
public float[] getHeldItemNormalTransformData() {
|
public float[] getHeldItemNormalTransformData() {
|
||||||
return heldItemNormalTransformData;
|
return heldItemNormalTransformData;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static OtherPlayer fromJoinMessage(PlayerJoinMessage msg, Client client) {
|
|
||||||
OtherPlayer op = new OtherPlayer(msg.id(), msg.username());
|
|
||||||
if (msg.teamId() != -1 && client.getTeams().containsKey(msg.teamId())) {
|
|
||||||
op.setTeam(client.getTeams().get(msg.teamId()));
|
|
||||||
}
|
|
||||||
op.getPosition().set(msg.px(), msg.py(), msg.pz());
|
|
||||||
op.getVelocity().set(msg.vx(), msg.vy(), msg.vz());
|
|
||||||
op.getOrientation().set(msg.ox(), msg.oy());
|
|
||||||
op.setHeldItemId(msg.selectedItemId());
|
|
||||||
op.setSelectedBlockValue(msg.selectedBlockValue());
|
|
||||||
op.setMode(msg.mode());
|
|
||||||
return op;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,23 +3,20 @@ package nl.andrewl.aos2_client.render;
|
||||||
import nl.andrewl.aos2_client.Camera;
|
import nl.andrewl.aos2_client.Camera;
|
||||||
import nl.andrewl.aos2_client.Client;
|
import nl.andrewl.aos2_client.Client;
|
||||||
import nl.andrewl.aos2_client.config.ClientConfig;
|
import nl.andrewl.aos2_client.config.ClientConfig;
|
||||||
import nl.andrewl.aos2_client.control.*;
|
|
||||||
import nl.andrewl.aos2_client.model.ClientPlayer;
|
import nl.andrewl.aos2_client.model.ClientPlayer;
|
||||||
import nl.andrewl.aos2_client.render.chunk.ChunkRenderer;
|
import nl.andrewl.aos2_client.render.chunk.ChunkRenderer;
|
||||||
import nl.andrewl.aos2_client.render.gui.GuiRenderer;
|
import nl.andrewl.aos2_client.render.gui.GuiRenderer;
|
||||||
import nl.andrewl.aos2_client.render.model.Model;
|
import nl.andrewl.aos2_client.render.model.Model;
|
||||||
import nl.andrewl.aos_core.model.PlayerMode;
|
|
||||||
import nl.andrewl.aos_core.model.Team;
|
import nl.andrewl.aos_core.model.Team;
|
||||||
import nl.andrewl.aos_core.model.item.BlockItemStack;
|
import nl.andrewl.aos_core.model.item.BlockItemStack;
|
||||||
import nl.andrewl.aos_core.model.item.Inventory;
|
|
||||||
import nl.andrewl.aos_core.model.item.ItemTypes;
|
import nl.andrewl.aos_core.model.item.ItemTypes;
|
||||||
import org.joml.Matrix3f;
|
import org.joml.Matrix3f;
|
||||||
import org.joml.Matrix4f;
|
import org.joml.Matrix4f;
|
||||||
import org.joml.Vector3f;
|
import org.joml.Vector3f;
|
||||||
import org.lwjgl.glfw.Callbacks;
|
import org.lwjgl.glfw.*;
|
||||||
import org.lwjgl.glfw.GLFWErrorCallback;
|
|
||||||
import org.lwjgl.glfw.GLFWVidMode;
|
|
||||||
import org.lwjgl.opengl.GL;
|
import org.lwjgl.opengl.GL;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
@ -33,43 +30,46 @@ import static org.lwjgl.opengl.GL46.*;
|
||||||
* OpenGL context exists.
|
* OpenGL context exists.
|
||||||
*/
|
*/
|
||||||
public class GameRenderer {
|
public class GameRenderer {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(GameRenderer.class);
|
||||||
private static final float Z_NEAR = 0.01f;
|
private static final float Z_NEAR = 0.01f;
|
||||||
private static final float Z_FAR = 500f;
|
private static final float Z_FAR = 500f;
|
||||||
|
|
||||||
private final ClientConfig.DisplayConfig config;
|
private final ClientConfig.DisplayConfig config;
|
||||||
private final ChunkRenderer chunkRenderer;
|
private ChunkRenderer chunkRenderer;
|
||||||
private final GuiRenderer guiRenderer;
|
private GuiRenderer guiRenderer;
|
||||||
private final ModelRenderer modelRenderer;
|
private ModelRenderer modelRenderer;
|
||||||
private final Camera camera;
|
private final Camera camera;
|
||||||
private final Client client;
|
private final Client client;
|
||||||
private final InputHandler inputHandler;
|
|
||||||
|
|
||||||
// Standard models for various game components.
|
// Standard models for various game components.
|
||||||
private final Model playerModel;
|
private Model playerModel;
|
||||||
private final Model rifleModel;
|
private Model rifleModel;
|
||||||
private final Model blockModel;
|
private Model blockModel;
|
||||||
private final Model bulletModel;
|
private Model bulletModel;
|
||||||
private final Model smgModel;
|
private Model smgModel;
|
||||||
private final Model shotgunModel;
|
private Model shotgunModel;
|
||||||
private final Model flagModel;
|
private Model flagModel;
|
||||||
|
|
||||||
private final long windowHandle;
|
private long windowHandle;
|
||||||
private final int screenWidth;
|
private int screenWidth = 800;
|
||||||
private final int screenHeight;
|
private int screenHeight = 600;
|
||||||
|
|
||||||
private final Matrix4f perspectiveTransform;
|
private final Matrix4f perspectiveTransform;
|
||||||
private final float[] perspectiveTransformData = new float[16];
|
|
||||||
|
|
||||||
public GameRenderer(Client client, InputHandler inputHandler, Camera camera) {
|
public GameRenderer(ClientConfig.DisplayConfig config, Client client) {
|
||||||
this.config = client.getConfig().display;
|
this.config = config;
|
||||||
this.client = client;
|
this.client = client;
|
||||||
this.inputHandler = inputHandler;
|
this.camera = new Camera();
|
||||||
this.camera = camera;
|
|
||||||
camera.setToPlayer(client.getMyPlayer());
|
camera.setToPlayer(client.getMyPlayer());
|
||||||
this.perspectiveTransform = new Matrix4f();
|
this.perspectiveTransform = new Matrix4f();
|
||||||
|
}
|
||||||
|
|
||||||
// Initialize window!
|
public void setupWindow(
|
||||||
|
GLFWCursorPosCallbackI viewCursorCallback,
|
||||||
|
GLFWKeyCallbackI inputKeyCallback,
|
||||||
|
GLFWMouseButtonCallbackI mouseButtonCallback,
|
||||||
|
GLFWScrollCallbackI scrollCallback
|
||||||
|
) {
|
||||||
GLFWErrorCallback.createPrint(System.err).set();
|
GLFWErrorCallback.createPrint(System.err).set();
|
||||||
if (!glfwInit()) throw new IllegalStateException("Could not initialize GLFW.");
|
if (!glfwInit()) throw new IllegalStateException("Could not initialize GLFW.");
|
||||||
glfwDefaultWindowHints();
|
glfwDefaultWindowHints();
|
||||||
|
@ -79,6 +79,7 @@ public class GameRenderer {
|
||||||
long monitorId = glfwGetPrimaryMonitor();
|
long monitorId = glfwGetPrimaryMonitor();
|
||||||
GLFWVidMode primaryMonitorSettings = glfwGetVideoMode(monitorId);
|
GLFWVidMode primaryMonitorSettings = glfwGetVideoMode(monitorId);
|
||||||
if (primaryMonitorSettings == null) throw new IllegalStateException("Could not get information about the primary monitory.");
|
if (primaryMonitorSettings == null) throw new IllegalStateException("Could not get information about the primary monitory.");
|
||||||
|
log.debug("Primary monitor settings: Width: {}, Height: {}, FOV: {}", primaryMonitorSettings.width(), primaryMonitorSettings.height(), config.fov);
|
||||||
if (config.fullscreen) {
|
if (config.fullscreen) {
|
||||||
screenWidth = primaryMonitorSettings.width();
|
screenWidth = primaryMonitorSettings.width();
|
||||||
screenHeight = primaryMonitorSettings.height();
|
screenHeight = primaryMonitorSettings.height();
|
||||||
|
@ -89,22 +90,19 @@ public class GameRenderer {
|
||||||
windowHandle = glfwCreateWindow(screenWidth, screenHeight, "Ace of Shades 2", 0, 0);
|
windowHandle = glfwCreateWindow(screenWidth, screenHeight, "Ace of Shades 2", 0, 0);
|
||||||
}
|
}
|
||||||
if (windowHandle == 0) throw new RuntimeException("Failed to create GLFW window.");
|
if (windowHandle == 0) throw new RuntimeException("Failed to create GLFW window.");
|
||||||
inputHandler.setWindowId(windowHandle);
|
log.debug("Initialized GLFW window.");
|
||||||
|
|
||||||
// Setup callbacks.
|
// Setup callbacks.
|
||||||
glfwSetKeyCallback(windowHandle, new PlayerInputKeyCallback(inputHandler));
|
glfwSetKeyCallback(windowHandle, inputKeyCallback);
|
||||||
glfwSetCursorPosCallback(windowHandle, new PlayerViewCursorCallback(inputHandler));
|
glfwSetCursorPosCallback(windowHandle, viewCursorCallback);
|
||||||
glfwSetMouseButtonCallback(windowHandle, new PlayerInputMouseClickCallback(inputHandler));
|
glfwSetMouseButtonCallback(windowHandle, mouseButtonCallback);
|
||||||
glfwSetScrollCallback(windowHandle, new PlayerInputMouseScrollCallback(inputHandler));
|
glfwSetScrollCallback(windowHandle, scrollCallback);
|
||||||
glfwSetCharCallback(windowHandle, new PlayerCharacterInputCallback(inputHandler));
|
|
||||||
glfwSetWindowFocusCallback(windowHandle, (window, focused) -> {
|
|
||||||
if (!focused) inputHandler.switchToExitMenuContext();
|
|
||||||
});
|
|
||||||
if (config.captureCursor) {
|
if (config.captureCursor) {
|
||||||
glfwSetInputMode(windowHandle, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
|
glfwSetInputMode(windowHandle, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
|
||||||
}
|
}
|
||||||
glfwSetInputMode(windowHandle, GLFW_RAW_MOUSE_MOTION, GLFW_TRUE);
|
glfwSetInputMode(windowHandle, GLFW_RAW_MOUSE_MOTION, GLFW_TRUE);
|
||||||
glfwSetCursorPos(windowHandle, 0, 0);
|
glfwSetCursorPos(windowHandle, 0, 0);
|
||||||
|
log.debug("Set up window callbacks.");
|
||||||
|
|
||||||
glfwMakeContextCurrent(windowHandle);
|
glfwMakeContextCurrent(windowHandle);
|
||||||
glfwSwapInterval(1);
|
glfwSwapInterval(1);
|
||||||
|
@ -112,18 +110,21 @@ public class GameRenderer {
|
||||||
|
|
||||||
GL.createCapabilities();
|
GL.createCapabilities();
|
||||||
// GLUtil.setupDebugMessageCallback(System.out);
|
// GLUtil.setupDebugMessageCallback(System.out);
|
||||||
glClearColor(0.1f, 0.1f, 0.1f, 0.0f);
|
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
|
||||||
glEnable(GL_CULL_FACE);
|
glEnable(GL_CULL_FACE);
|
||||||
glEnable(GL_DEPTH_TEST);
|
glEnable(GL_DEPTH_TEST);
|
||||||
glCullFace(GL_BACK);
|
glCullFace(GL_BACK);
|
||||||
|
log.debug("Initialized OpenGL context.");
|
||||||
|
|
||||||
this.chunkRenderer = new ChunkRenderer();
|
this.chunkRenderer = new ChunkRenderer();
|
||||||
|
log.debug("Initialized chunk renderer.");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.guiRenderer = new GuiRenderer();
|
this.guiRenderer = new GuiRenderer();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
log.debug("Initialized GUI renderer.");
|
||||||
|
|
||||||
this.modelRenderer = new ModelRenderer();
|
this.modelRenderer = new ModelRenderer();
|
||||||
try {
|
try {
|
||||||
|
@ -137,7 +138,8 @@ public class GameRenderer {
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
updatePerspective(config.fov);
|
log.debug("Initialized model renderer.");
|
||||||
|
updatePerspective();
|
||||||
}
|
}
|
||||||
|
|
||||||
public float getAspectRatio() {
|
public float getAspectRatio() {
|
||||||
|
@ -147,17 +149,18 @@ public class GameRenderer {
|
||||||
/**
|
/**
|
||||||
* Updates the rendering perspective used to render the game.
|
* Updates the rendering perspective used to render the game.
|
||||||
*/
|
*/
|
||||||
public void updatePerspective(float fov) {
|
private void updatePerspective() {
|
||||||
float fovRad = (float) Math.toRadians(fov);
|
float fovRad = (float) Math.toRadians(config.fov);
|
||||||
if (fovRad >= Math.PI) {
|
if (fovRad >= Math.PI) {
|
||||||
fovRad = (float) (Math.PI - 0.01f);
|
fovRad = (float) (Math.PI - 0.01f);
|
||||||
} else if (fovRad <= 0) {
|
} else if (fovRad <= 0) {
|
||||||
fovRad = 0.01f;
|
fovRad = 0.01f;
|
||||||
}
|
}
|
||||||
perspectiveTransform.setPerspective(fovRad, getAspectRatio(), Z_NEAR, Z_FAR);
|
perspectiveTransform.setPerspective(fovRad, getAspectRatio(), Z_NEAR, Z_FAR);
|
||||||
perspectiveTransform.get(perspectiveTransformData);
|
float[] data = new float[16];
|
||||||
if (chunkRenderer != null) chunkRenderer.setPerspective(perspectiveTransformData);
|
perspectiveTransform.get(data);
|
||||||
if (modelRenderer != null) modelRenderer.setPerspective(perspectiveTransformData);
|
if (chunkRenderer != null) chunkRenderer.setPerspective(data);
|
||||||
|
if (modelRenderer != null) modelRenderer.setPerspective(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean windowShouldClose() {
|
public boolean windowShouldClose() {
|
||||||
|
@ -174,24 +177,16 @@ public class GameRenderer {
|
||||||
|
|
||||||
public void draw() {
|
public void draw() {
|
||||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
|
||||||
|
chunkRenderer.draw(camera, client.getWorld().getChunkMeshesToDraw());
|
||||||
|
|
||||||
ClientPlayer myPlayer = client.getMyPlayer();
|
ClientPlayer myPlayer = client.getMyPlayer();
|
||||||
Inventory inv = myPlayer.getInventory();
|
|
||||||
if (inputHandler.isNormalContextActive() && inputHandler.getNormalContext().isScopeEnabled()) {
|
|
||||||
updatePerspective(15);
|
|
||||||
} else {
|
|
||||||
updatePerspective(config.fov);
|
|
||||||
}
|
|
||||||
myPlayer.updateHeldItemTransform(camera, client.getInputHandler());
|
|
||||||
|
|
||||||
chunkRenderer.draw(camera, client.getWorld().getChunkMeshesToDraw());
|
|
||||||
|
|
||||||
// Draw models. Use one texture at a time for efficiency.
|
// Draw models. Use one texture at a time for efficiency.
|
||||||
modelRenderer.start(camera.getViewTransformData());
|
modelRenderer.start(camera.getViewTransformData());
|
||||||
|
myPlayer.updateHeldItemTransform(camera);
|
||||||
|
|
||||||
playerModel.bind();
|
playerModel.bind();
|
||||||
for (var player : client.getPlayers().values()) {
|
for (var player : client.getPlayers().values()) {
|
||||||
if (player.getMode() == PlayerMode.SPECTATOR) continue;
|
|
||||||
if (player.getTeam() != null) {
|
if (player.getTeam() != null) {
|
||||||
modelRenderer.setAspectColor(player.getTeam().getColor());
|
modelRenderer.setAspectColor(player.getTeam().getColor());
|
||||||
} else {
|
} else {
|
||||||
|
@ -203,33 +198,30 @@ public class GameRenderer {
|
||||||
|
|
||||||
// Render guns!
|
// Render guns!
|
||||||
rifleModel.bind();
|
rifleModel.bind();
|
||||||
if (inv.getSelectedItemStack() != null && inv.getSelectedItemStack().getType().getId() == ItemTypes.RIFLE.getId()) {
|
if (myPlayer.getInventory().getSelectedItemStack().getType().getId() == ItemTypes.RIFLE.getId()) {
|
||||||
modelRenderer.render(rifleModel, myPlayer.getHeldItemTransformData(), myPlayer.getHeldItemNormalTransformData());
|
modelRenderer.render(rifleModel, myPlayer.getHeldItemTransformData(), myPlayer.getHeldItemNormalTransformData());
|
||||||
}
|
}
|
||||||
for (var player : client.getPlayers().values()) {
|
for (var player : client.getPlayers().values()) {
|
||||||
if (player.getMode() == PlayerMode.SPECTATOR) continue;
|
|
||||||
if (player.getHeldItemId() == ItemTypes.RIFLE.getId()) {
|
if (player.getHeldItemId() == ItemTypes.RIFLE.getId()) {
|
||||||
modelRenderer.render(rifleModel, player.getHeldItemTransformData(), player.getHeldItemNormalTransformData());
|
modelRenderer.render(rifleModel, player.getHeldItemTransformData(), player.getHeldItemNormalTransformData());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rifleModel.unbind();
|
rifleModel.unbind();
|
||||||
smgModel.bind();
|
smgModel.bind();
|
||||||
if (inv.getSelectedItemStack() != null && inv.getSelectedItemStack().getType().getId() == ItemTypes.AK_47.getId()) {
|
if (myPlayer.getInventory().getSelectedItemStack().getType().getId() == ItemTypes.AK_47.getId()) {
|
||||||
modelRenderer.render(smgModel, myPlayer.getHeldItemTransformData(), myPlayer.getHeldItemNormalTransformData());
|
modelRenderer.render(smgModel, myPlayer.getHeldItemTransformData(), myPlayer.getHeldItemNormalTransformData());
|
||||||
}
|
}
|
||||||
for (var player : client.getPlayers().values()) {
|
for (var player : client.getPlayers().values()) {
|
||||||
if (player.getMode() == PlayerMode.SPECTATOR) continue;
|
|
||||||
if (player.getHeldItemId() == ItemTypes.AK_47.getId()) {
|
if (player.getHeldItemId() == ItemTypes.AK_47.getId()) {
|
||||||
modelRenderer.render(smgModel, player.getHeldItemTransformData(), player.getHeldItemNormalTransformData());
|
modelRenderer.render(smgModel, player.getHeldItemTransformData(), player.getHeldItemNormalTransformData());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
smgModel.unbind();
|
smgModel.unbind();
|
||||||
shotgunModel.bind();
|
shotgunModel.bind();
|
||||||
if (inv.getSelectedItemStack() != null && inv.getSelectedItemStack().getType().getId() == ItemTypes.WINCHESTER.getId()) {
|
if (myPlayer.getInventory().getSelectedItemStack().getType().getId() == ItemTypes.WINCHESTER.getId()) {
|
||||||
modelRenderer.render(shotgunModel, myPlayer.getHeldItemTransformData(), myPlayer.getHeldItemNormalTransformData());
|
modelRenderer.render(shotgunModel, myPlayer.getHeldItemTransformData(), myPlayer.getHeldItemNormalTransformData());
|
||||||
}
|
}
|
||||||
for (var player : client.getPlayers().values()) {
|
for (var player : client.getPlayers().values()) {
|
||||||
if (player.getMode() == PlayerMode.SPECTATOR) continue;
|
|
||||||
if (player.getHeldItemId() == ItemTypes.WINCHESTER.getId()) {
|
if (player.getHeldItemId() == ItemTypes.WINCHESTER.getId()) {
|
||||||
modelRenderer.render(shotgunModel, player.getHeldItemTransformData(), player.getHeldItemNormalTransformData());
|
modelRenderer.render(shotgunModel, player.getHeldItemTransformData(), player.getHeldItemNormalTransformData());
|
||||||
}
|
}
|
||||||
|
@ -237,14 +229,13 @@ public class GameRenderer {
|
||||||
shotgunModel.unbind();
|
shotgunModel.unbind();
|
||||||
|
|
||||||
blockModel.bind();
|
blockModel.bind();
|
||||||
if (inv.getSelectedItemStack() != null && inv.getSelectedItemStack().getType().getId() == ItemTypes.BLOCK.getId()) {
|
if (myPlayer.getInventory().getSelectedItemStack().getType().getId() == ItemTypes.BLOCK.getId()) {
|
||||||
BlockItemStack stack = (BlockItemStack) myPlayer.getInventory().getSelectedItemStack();
|
BlockItemStack stack = (BlockItemStack) myPlayer.getInventory().getSelectedItemStack();
|
||||||
modelRenderer.setAspectColor(client.getWorld().getPalette().getColor(stack.getSelectedValue()));
|
modelRenderer.setAspectColor(client.getWorld().getPalette().getColor(stack.getSelectedValue()));
|
||||||
modelRenderer.render(blockModel, myPlayer.getHeldItemTransformData(), myPlayer.getHeldItemNormalTransformData());
|
modelRenderer.render(blockModel, myPlayer.getHeldItemTransformData(), myPlayer.getHeldItemNormalTransformData());
|
||||||
}
|
}
|
||||||
modelRenderer.setAspectColor(new Vector3f(0.5f, 0.5f, 0.5f));
|
modelRenderer.setAspectColor(new Vector3f(0.5f, 0.5f, 0.5f));
|
||||||
for (var player : client.getPlayers().values()) {
|
for (var player : client.getPlayers().values()) {
|
||||||
if (player.getMode() == PlayerMode.SPECTATOR) continue;
|
|
||||||
if (player.getHeldItemId() == ItemTypes.BLOCK.getId()) {
|
if (player.getHeldItemId() == ItemTypes.BLOCK.getId()) {
|
||||||
modelRenderer.setAspectColor(client.getWorld().getPalette().getColor(player.getSelectedBlockValue()));
|
modelRenderer.setAspectColor(client.getWorld().getPalette().getColor(player.getSelectedBlockValue()));
|
||||||
modelRenderer.render(blockModel, player.getHeldItemTransformData(), player.getHeldItemNormalTransformData());
|
modelRenderer.render(blockModel, player.getHeldItemTransformData(), player.getHeldItemNormalTransformData());
|
||||||
|
@ -268,7 +259,7 @@ public class GameRenderer {
|
||||||
flagModel.bind();
|
flagModel.bind();
|
||||||
for (Team team : client.getTeams().values()) {
|
for (Team team : client.getTeams().values()) {
|
||||||
modelTransform.identity()
|
modelTransform.identity()
|
||||||
.translate(team.getSpawnPoint().x() - 0.25f, team.getSpawnPoint().y(), team.getSpawnPoint().z() - 0.25f);
|
.translate(team.getSpawnPoint());
|
||||||
modelTransform.normal(normalTransform);
|
modelTransform.normal(normalTransform);
|
||||||
modelRenderer.setAspectColor(team.getColor());
|
modelRenderer.setAspectColor(team.getColor());
|
||||||
modelRenderer.render(flagModel, modelTransform, normalTransform);
|
modelRenderer.render(flagModel, modelTransform, normalTransform);
|
||||||
|
@ -280,7 +271,7 @@ public class GameRenderer {
|
||||||
// GUI rendering
|
// GUI rendering
|
||||||
guiRenderer.start();
|
guiRenderer.start();
|
||||||
guiRenderer.drawNameplates(myPlayer, camera.getViewTransformData(), perspectiveTransform.get(new float[16]));
|
guiRenderer.drawNameplates(myPlayer, camera.getViewTransformData(), perspectiveTransform.get(new float[16]));
|
||||||
guiRenderer.drawNvg(screenWidth, screenHeight, client);
|
guiRenderer.drawNvg(screenWidth, screenHeight, myPlayer, client.getChat());
|
||||||
guiRenderer.end();
|
guiRenderer.end();
|
||||||
|
|
||||||
glfwSwapBuffers(windowHandle);
|
glfwSwapBuffers(windowHandle);
|
||||||
|
@ -288,15 +279,15 @@ public class GameRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void freeWindow() {
|
public void freeWindow() {
|
||||||
rifleModel.free();
|
if (rifleModel != null) rifleModel.free();
|
||||||
smgModel.free();
|
if (smgModel != null) smgModel.free();
|
||||||
flagModel.free();
|
if (flagModel != null) flagModel.free();
|
||||||
bulletModel.free();
|
if (bulletModel != null) bulletModel.free();
|
||||||
playerModel.free();
|
if (playerModel != null) playerModel.free();
|
||||||
blockModel.free();
|
if (blockModel != null) blockModel.free();
|
||||||
modelRenderer.free();
|
if (modelRenderer != null) modelRenderer.free();
|
||||||
guiRenderer.free();
|
if (guiRenderer != null) guiRenderer.free();
|
||||||
chunkRenderer.free();
|
if (chunkRenderer != null) chunkRenderer.free();
|
||||||
GL.destroy();
|
GL.destroy();
|
||||||
Callbacks.glfwFreeCallbacks(windowHandle);
|
Callbacks.glfwFreeCallbacks(windowHandle);
|
||||||
glfwSetErrorCallback(null);
|
glfwSetErrorCallback(null);
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
package nl.andrewl.aos2_client.render;
|
|
||||||
|
|
||||||
public record TransformData(
|
|
||||||
float[] tx,
|
|
||||||
float[] norm
|
|
||||||
) {}
|
|
|
@ -2,6 +2,8 @@ package nl.andrewl.aos2_client.render.chunk;
|
||||||
|
|
||||||
import nl.andrewl.aos_core.model.world.Chunk;
|
import nl.andrewl.aos_core.model.world.Chunk;
|
||||||
import nl.andrewl.aos_core.model.world.World;
|
import nl.andrewl.aos_core.model.world.World;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import static org.lwjgl.opengl.GL46.*;
|
import static org.lwjgl.opengl.GL46.*;
|
||||||
|
|
||||||
|
@ -9,6 +11,8 @@ import static org.lwjgl.opengl.GL46.*;
|
||||||
* Represents a 3d mesh for a chunk.
|
* Represents a 3d mesh for a chunk.
|
||||||
*/
|
*/
|
||||||
public class ChunkMesh {
|
public class ChunkMesh {
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(ChunkMesh.class);
|
||||||
|
|
||||||
private final int vboId;
|
private final int vboId;
|
||||||
private final int vaoId;
|
private final int vaoId;
|
||||||
private final int eboId;
|
private final int eboId;
|
||||||
|
@ -45,11 +49,20 @@ public class ChunkMesh {
|
||||||
* Generates and loads this chunk's mesh into the allocated OpenGL buffers.
|
* Generates and loads this chunk's mesh into the allocated OpenGL buffers.
|
||||||
*/
|
*/
|
||||||
private void loadMesh(ChunkMeshGenerator meshGenerator) {
|
private void loadMesh(ChunkMeshGenerator meshGenerator) {
|
||||||
|
// long start = System.nanoTime();
|
||||||
var meshData = meshGenerator.generateMesh(chunk, world);
|
var meshData = meshGenerator.generateMesh(chunk, world);
|
||||||
|
// double dur = (System.nanoTime() - start) / 1_000_000.0;
|
||||||
this.indexCount = meshData.indexBuffer().limit();
|
this.indexCount = meshData.indexBuffer().limit();
|
||||||
|
// log.debug(
|
||||||
|
// "Generated mesh for chunk ({}, {}, {}) in {} ms. {} vertices and {} indices.",
|
||||||
|
// chunk.getPosition().x, chunk.getPosition().y, chunk.getPosition().z,
|
||||||
|
// dur,
|
||||||
|
// meshData.vertexBuffer().limit() / 9, indexCount
|
||||||
|
// );
|
||||||
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, vboId);
|
glBindBuffer(GL_ARRAY_BUFFER, vboId);
|
||||||
glBufferData(GL_ARRAY_BUFFER, meshData.vertexBuffer(), GL_STATIC_DRAW);
|
glBufferData(GL_ARRAY_BUFFER, meshData.vertexBuffer(), GL_STATIC_DRAW);
|
||||||
|
|
||||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, eboId);
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, eboId);
|
||||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, meshData.indexBuffer(), GL_STATIC_DRAW);
|
glBufferData(GL_ELEMENT_ARRAY_BUFFER, meshData.indexBuffer(), GL_STATIC_DRAW);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,16 @@
|
||||||
package nl.andrewl.aos2_client.render.gui;
|
package nl.andrewl.aos2_client.render.gui;
|
||||||
|
|
||||||
import nl.andrewl.aos2_client.Camera;
|
import nl.andrewl.aos2_client.Camera;
|
||||||
import nl.andrewl.aos2_client.Client;
|
import nl.andrewl.aos2_client.model.Chat;
|
||||||
import nl.andrewl.aos2_client.model.ClientPlayer;
|
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.ShaderProgram;
|
import nl.andrewl.aos2_client.render.ShaderProgram;
|
||||||
import nl.andrewl.aos2_client.sound.SoundSource;
|
|
||||||
import nl.andrewl.aos_core.FileUtils;
|
import nl.andrewl.aos_core.FileUtils;
|
||||||
import nl.andrewl.aos_core.model.Player;
|
import nl.andrewl.aos_core.model.Player;
|
||||||
import nl.andrewl.aos_core.model.PlayerMode;
|
|
||||||
import nl.andrewl.aos_core.model.item.BlockItem;
|
import nl.andrewl.aos_core.model.item.BlockItem;
|
||||||
import nl.andrewl.aos_core.model.item.BlockItemStack;
|
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.world.Hit;
|
|
||||||
import org.joml.Matrix4f;
|
import org.joml.Matrix4f;
|
||||||
import org.lwjgl.BufferUtils;
|
import org.lwjgl.BufferUtils;
|
||||||
import org.lwjgl.nanovg.NVGColor;
|
import org.lwjgl.nanovg.NVGColor;
|
||||||
|
@ -197,12 +194,10 @@ public class GuiRenderer {
|
||||||
glUniform1i(namePlateTextureSamplerUniform, 0);
|
glUniform1i(namePlateTextureSamplerUniform, 0);
|
||||||
glUniformMatrix4fv(namePlateViewTransformUniform, false, viewTransformData);
|
glUniformMatrix4fv(namePlateViewTransformUniform, false, viewTransformData);
|
||||||
glUniformMatrix4fv(namePlatePerspectiveTransformUniform, false, perspectiveTransformData);
|
glUniformMatrix4fv(namePlatePerspectiveTransformUniform, false, perspectiveTransformData);
|
||||||
// Show nameplates from farther away if we're in creative/spectator.
|
|
||||||
float nameplateRadius = myPlayer.getMode() == PlayerMode.NORMAL ? 50 : 200;
|
|
||||||
for (var entry : playerNamePlates.entrySet()) {
|
for (var entry : playerNamePlates.entrySet()) {
|
||||||
OtherPlayer player = entry.getKey();
|
OtherPlayer player = entry.getKey();
|
||||||
// There are some scenarios where we skip rendering the name.
|
// Skip rendering far-away nameplates.
|
||||||
if (player.getPosition().distance(myPlayer.getPosition()) > nameplateRadius || player.getMode() == PlayerMode.SPECTATOR) continue;
|
if (player.getPosition().distance(myPlayer.getPosition()) > 50) continue;
|
||||||
GuiTexture texture = entry.getValue();
|
GuiTexture texture = entry.getValue();
|
||||||
float aspectRatio = (float) texture.getHeight() / (float) texture.getWidth();
|
float aspectRatio = (float) texture.getHeight() / (float) texture.getWidth();
|
||||||
transformMatrix.identity()
|
transformMatrix.identity()
|
||||||
|
@ -221,24 +216,14 @@ public class GuiRenderer {
|
||||||
shaderProgram.use();
|
shaderProgram.use();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void drawNvg(float width, float height, Client client) {
|
public void drawNvg(float width, float height, ClientPlayer player, Chat chat) {
|
||||||
nvgBeginFrame(vgId, width, height, width / height);
|
nvgBeginFrame(vgId, width, height, width / height);
|
||||||
nvgSave(vgId);
|
nvgSave(vgId);
|
||||||
|
|
||||||
boolean scopeEnabled = client.getInputHandler().getNormalContext().isScopeEnabled();
|
drawCrosshair(width, height);
|
||||||
PlayerMode mode = client.getMyPlayer().getMode();
|
drawChat(width, height, chat);
|
||||||
drawCrosshair(width, height, scopeEnabled);
|
drawHealthBar(width, height, player);
|
||||||
drawHeldItemStackInfo(width, height, client.getMyPlayer());
|
drawHeldItemStackInfo(width, height, player);
|
||||||
if (!scopeEnabled) {
|
|
||||||
drawChat(width, height, client);
|
|
||||||
if (mode == PlayerMode.NORMAL) drawHealthBar(width, height, client.getMyPlayer());
|
|
||||||
}
|
|
||||||
if (client.getInputHandler().getNormalContext().isDebugEnabled()) {
|
|
||||||
drawDebugInfo(width, height, client);
|
|
||||||
}
|
|
||||||
if (client.getInputHandler().isExitMenuContextActive()) {
|
|
||||||
drawExitMenu(width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
nvgRestore(vgId);
|
nvgRestore(vgId);
|
||||||
nvgEndFrame(vgId);
|
nvgEndFrame(vgId);
|
||||||
|
@ -263,40 +248,34 @@ public class GuiRenderer {
|
||||||
shaderProgram.free();
|
shaderProgram.free();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void drawCrosshair(float w, float h, boolean scopeEnabled) {
|
private void drawCrosshair(float w, float h) {
|
||||||
float cx = w / 2f;
|
float cx = w / 2f;
|
||||||
float cy = h / 2f;
|
float cy = h / 2f;
|
||||||
float size = 20f;
|
|
||||||
if (scopeEnabled) {
|
|
||||||
size = 3f;
|
|
||||||
nvgStrokeColor(vgId, GuiUtils.rgba(1, 0, 0, 0.5f, colorA));
|
|
||||||
} else {
|
|
||||||
nvgStrokeColor(vgId, GuiUtils.rgba(1, 1, 1, 0.25f, colorA));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
nvgStrokeColor(vgId, GuiUtils.rgba(1, 1, 1, 0.25f, colorA));
|
||||||
nvgBeginPath(vgId);
|
nvgBeginPath(vgId);
|
||||||
nvgMoveTo(vgId, cx - size / 2, cy);
|
nvgMoveTo(vgId, cx - 10, cy);
|
||||||
nvgLineTo(vgId, cx + size / 2, cy);
|
nvgLineTo(vgId, cx + 10, cy);
|
||||||
nvgMoveTo(vgId, cx, cy - size / 2);
|
nvgMoveTo(vgId, cx, cy - 10);
|
||||||
nvgLineTo(vgId, cx, cy + size / 2);
|
nvgLineTo(vgId, cx, cy + 10);
|
||||||
nvgStroke(vgId);
|
nvgStroke(vgId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void drawHealthBar(float w, float h, ClientPlayer player) {
|
private void drawHealthBar(float w, float h, ClientPlayer player) {
|
||||||
nvgFillColor(vgId, GuiUtils.rgba(0.6f, 0, 0, 1, colorA));
|
nvgFillColor(vgId, GuiUtils.rgba(1, 0, 0, 1, colorA));
|
||||||
nvgBeginPath(vgId);
|
nvgBeginPath(vgId);
|
||||||
nvgRect(vgId, w - 170, h - 100, 100, 20);
|
nvgRect(vgId, 20, h - 60, 100, 20);
|
||||||
nvgFill(vgId);
|
nvgFill(vgId);
|
||||||
nvgFillColor(vgId, GuiUtils.rgba(0, 0.6f, 0, 1, colorA));
|
nvgFillColor(vgId, GuiUtils.rgba(0, 1, 0, 1, colorA));
|
||||||
nvgBeginPath(vgId);
|
nvgBeginPath(vgId);
|
||||||
nvgRect(vgId, w - 170, h - 100, 100 * player.getHealth(), 20);
|
nvgRect(vgId, 20, h - 60, 100 * player.getHealth(), 20);
|
||||||
nvgFill(vgId);
|
nvgFill(vgId);
|
||||||
|
|
||||||
nvgFillColor(vgId, GuiUtils.rgba(1, 1, 1, 1, colorA));
|
nvgFillColor(vgId, GuiUtils.rgba(1, 1, 1, 1, colorA));
|
||||||
nvgFontSize(vgId, 12f);
|
nvgFontSize(vgId, 12f);
|
||||||
nvgFontFaceId(vgId, jetbrainsMonoFont);
|
nvgFontFaceId(vgId, jetbrainsMonoFont);
|
||||||
nvgTextAlign(vgId, NVG_ALIGN_LEFT | NVG_ALIGN_TOP);
|
nvgTextAlign(vgId, NVG_ALIGN_LEFT | NVG_ALIGN_TOP);
|
||||||
nvgText(vgId, w - 165, h - 95, String.format("%.2f / 1.00", player.getHealth()));
|
nvgText(vgId, 20, h - 30, String.format("%.2f / 1.00 HP", player.getHealth()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void drawHeldItemStackInfo(float w, float h, ClientPlayer player) {
|
private void drawHeldItemStackInfo(float w, float h, ClientPlayer player) {
|
||||||
|
@ -337,81 +316,34 @@ public class GuiRenderer {
|
||||||
nvgFontFaceId(vgId, jetbrainsMonoFont);
|
nvgFontFaceId(vgId, jetbrainsMonoFont);
|
||||||
nvgTextAlign(vgId, NVG_ALIGN_LEFT | NVG_ALIGN_TOP);
|
nvgTextAlign(vgId, NVG_ALIGN_LEFT | NVG_ALIGN_TOP);
|
||||||
nvgText(vgId, w - 140, h - 30, String.format("%d / %d Blocks", stack.getAmount(), block.getMaxAmount()));
|
nvgText(vgId, w - 140, h - 30, String.format("%d / %d Blocks", stack.getAmount(), block.getMaxAmount()));
|
||||||
nvgText(vgId, w - 140, h - 14, String.format("Selected value: %d", stack.getSelectedValue()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void drawChat(float w, float h, Client client) {
|
private void drawChat(float w, float h, Chat chat) {
|
||||||
var chat = client.getChat();
|
float chatWidth = w / 3;
|
||||||
nvgFontSize(vgId, 12f);
|
float chatHeight = h / 4;
|
||||||
nvgFontFaceId(vgId, jetbrainsMonoFont);
|
|
||||||
nvgTextAlign(vgId, NVG_ALIGN_LEFT | NVG_ALIGN_TOP);
|
|
||||||
float y = h - 16 - 12;
|
|
||||||
for (var msg : chat.getMessages()) {
|
|
||||||
if (msg.author().equals("_ANNOUNCE")) {
|
|
||||||
nvgFillColor(vgId, GuiUtils.rgba(0.7f, 0, 0, 1, colorA));
|
|
||||||
nvgText(vgId, 5, y, msg.message());
|
|
||||||
} else if (msg.author().equals("_PRIVATE")) {
|
|
||||||
nvgFillColor(vgId, GuiUtils.rgba(0.6f, 0.6f, 0.6f, 1, colorA));
|
|
||||||
nvgText(vgId, 5, y, msg.message());
|
|
||||||
} else {
|
|
||||||
nvgFillColor(vgId, GuiUtils.rgba(1, 1, 1, 1, colorA));
|
|
||||||
nvgText(vgId, 5, y, msg.author() + ": " + msg.message());
|
|
||||||
}
|
|
||||||
y -= 16;
|
|
||||||
}
|
|
||||||
var input = client.getInputHandler();
|
|
||||||
if (input.isChattingContextActive()) {
|
|
||||||
nvgFillColor(vgId, GuiUtils.rgba(0, 0, 0, 0.5f, colorA));
|
|
||||||
nvgBeginPath(vgId);
|
|
||||||
nvgRect(vgId, 0, h - 16, w, 16);
|
|
||||||
nvgFill(vgId);
|
|
||||||
nvgFillColor(vgId, GuiUtils.rgba(1, 1, 1, 1, colorA));
|
|
||||||
nvgText(vgId, 5, h - 14, "> " + input.getChattingContext().getChatBufferText() + "_");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void drawDebugInfo(float w, float h, Client client) {
|
nvgFillColor(vgId, GuiUtils.rgba(0, 0, 0, 0.25f, colorA));
|
||||||
float y = h / 4 + 10;
|
|
||||||
nvgFontSize(vgId, 12f);
|
|
||||||
nvgFontFaceId(vgId, jetbrainsMonoFont);
|
|
||||||
nvgTextAlign(vgId, NVG_ALIGN_LEFT | NVG_ALIGN_TOP);
|
|
||||||
nvgFillColor(vgId, GuiUtils.rgba(1, 1, 1, 1, colorA));
|
|
||||||
var pos = client.getMyPlayer().getPosition();
|
|
||||||
nvgText(vgId, 5, y, String.format("Pos: x=%.3f, y=%.3f, z=%.3f", pos.x, pos.y, pos.z));
|
|
||||||
y += 12;
|
|
||||||
var vel = client.getMyPlayer().getVelocity();
|
|
||||||
nvgText(vgId, 5, y, String.format("Vel: x=%.3f, y=%.3f, z=%.3f, speed=%.3f", vel.x, vel.y, vel.z, vel.length()));
|
|
||||||
y += 12;
|
|
||||||
var view = client.getMyPlayer().getOrientation();
|
|
||||||
nvgText(vgId, 5, y, String.format("View: horizontal=%.3f, vertical=%.3f", Math.toDegrees(view.x), Math.toDegrees(view.y)));
|
|
||||||
y += 12;
|
|
||||||
var soundSources = client.getSoundManager().getSources();
|
|
||||||
int activeCount = (int) soundSources.stream().filter(SoundSource::isPlaying).count();
|
|
||||||
nvgText(vgId, 5, y, String.format("Sounds: %d / %d playing", activeCount, soundSources.size()));
|
|
||||||
y += 12;
|
|
||||||
nvgText(vgId, 5, y, String.format("Projectiles: %d", client.getProjectiles().size()));
|
|
||||||
y += 12;
|
|
||||||
nvgText(vgId, 5, y, String.format("Players: %d", client.getPlayers().size()));
|
|
||||||
y += 12;
|
|
||||||
nvgText(vgId, 5, y, String.format("Chunks: %d", client.getWorld().getChunkMap().size()));
|
|
||||||
y += 12;
|
|
||||||
|
|
||||||
Hit hit = client.getWorld().getLookingAtPos(client.getMyPlayer().getEyePosition(), client.getMyPlayer().getViewVector(), 50);
|
|
||||||
if (hit != null) {
|
|
||||||
nvgText(vgId, 5, y, String.format("Looking at: x=%d, y=%d, z=%d", hit.pos().x, hit.pos().y, hit.pos().z));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void drawExitMenu(float width, float height) {
|
|
||||||
nvgFillColor(vgId, GuiUtils.rgba(0, 0, 0, 0.5f, colorA));
|
|
||||||
nvgBeginPath(vgId);
|
nvgBeginPath(vgId);
|
||||||
nvgRect(vgId, 0, 0, width, height);
|
nvgRect(vgId, 0, 0, chatWidth, chatHeight);
|
||||||
nvgFill(vgId);
|
nvgFill(vgId);
|
||||||
|
|
||||||
nvgFontSize(vgId, 12f);
|
nvgFontSize(vgId, 12f);
|
||||||
nvgFontFaceId(vgId, jetbrainsMonoFont);
|
nvgFontFaceId(vgId, jetbrainsMonoFont);
|
||||||
nvgTextAlign(vgId, NVG_ALIGN_LEFT | NVG_ALIGN_TOP);
|
nvgTextAlign(vgId, NVG_ALIGN_LEFT | NVG_ALIGN_TOP);
|
||||||
nvgFillColor(vgId, GuiUtils.rgba(1, 1, 1, 1, colorA));
|
float y = chatHeight - 12;
|
||||||
nvgText(vgId, width / 2f, height / 2f, "Press ESC to quit. Press any other key to return to the game.");
|
for (var msg : chat.getMessages()) {
|
||||||
|
if (msg.author().equals("_ANNOUNCE")) {
|
||||||
|
nvgFillColor(vgId, GuiUtils.rgba(0.7f, 0, 0, 1, colorA));
|
||||||
|
nvgText(vgId, 5, y, msg.message());
|
||||||
|
} else if (msg.author().equals("_PRIVATE")) {
|
||||||
|
nvgFillColor(vgId, GuiUtils.rgba(0.3f, 0.3f, 0.3f, 1, colorA));
|
||||||
|
nvgText(vgId, 5, y, msg.message());
|
||||||
|
} else {
|
||||||
|
nvgFillColor(vgId, GuiUtils.rgba(1, 1, 1, 1, colorA));
|
||||||
|
nvgText(vgId, 5, y, msg.author() + ": " + msg.message());
|
||||||
|
}
|
||||||
|
|
||||||
|
y -= 16;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,19 @@
|
||||||
package nl.andrewl.aos2_client.sound;
|
package nl.andrewl.aos2_client.sound;
|
||||||
|
|
||||||
import nl.andrewl.aos_core.model.Player;
|
import nl.andrewl.aos_core.model.Player;
|
||||||
import nl.andrewl.aos_core.model.PlayerMode;
|
|
||||||
import nl.andrewl.aos_core.model.world.World;
|
|
||||||
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.*;
|
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.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ThreadLocalRandom;
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
|
||||||
|
@ -21,6 +24,8 @@ 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 = 32;
|
private static final int SOURCE_COUNT = 32;
|
||||||
|
|
||||||
private final long alContext;
|
private final long alContext;
|
||||||
|
@ -75,8 +80,6 @@ public class SoundManager {
|
||||||
load("block_break_1", "sound/m_block_break_1.wav");
|
load("block_break_1", "sound/m_block_break_1.wav");
|
||||||
load("block_place_1", "sound/m_block_place_1.wav");
|
load("block_place_1", "sound/m_block_place_1.wav");
|
||||||
load("chat", "sound/chat.wav");
|
load("chat", "sound/chat.wav");
|
||||||
load("hit_1", "sound/m_hit_1.wav");
|
|
||||||
load("hit_2", "sound/m_hit_2.wav");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void load(String name, String resource) {
|
public void load(String name, String resource) {
|
||||||
|
@ -99,7 +102,7 @@ public class SoundManager {
|
||||||
public void play(String soundName, float gain, Vector3f position, Vector3f velocity) {
|
public void play(String soundName, float gain, Vector3f position, Vector3f velocity) {
|
||||||
Integer bufferId = getSoundBuffer(soundName);
|
Integer bufferId = getSoundBuffer(soundName);
|
||||||
if (bufferId == null) {
|
if (bufferId == null) {
|
||||||
System.err.printf("Attempted to play unknown sound \"%s\".%n", soundName);
|
log.warn("Attempted to play unknown sound \"{}\"", soundName);
|
||||||
} else {
|
} else {
|
||||||
SoundSource source = getNextAvailableSoundSource();
|
SoundSource source = getNextAvailableSoundSource();
|
||||||
if (source != null) {
|
if (source != null) {
|
||||||
|
@ -108,7 +111,7 @@ public class SoundManager {
|
||||||
source.setGain(gain);
|
source.setGain(gain);
|
||||||
source.play(bufferId);
|
source.play(bufferId);
|
||||||
} else {
|
} else {
|
||||||
System.err.printf("No sound sources available to play sound \"%s\".%n", soundName);
|
log.warn("Couldn't get an available sound source to play sound \"{}\"", soundName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -117,9 +120,8 @@ public class SoundManager {
|
||||||
play(soundName, gain, position, new Vector3f(0, 0, 0));
|
play(soundName, gain, position, new Vector3f(0, 0, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void playWalkingSounds(Player player, World world, long now) {
|
public void playWalkingSounds(Player player, long now) {
|
||||||
// Don't play sounds for players who are still, non-normal mode, or not on the ground.
|
if (player.getVelocity().length() <= 0) return;
|
||||||
if (player.getVelocity().length() <= 0 || player.getMode() != PlayerMode.NORMAL || !player.isGrounded(world)) return;
|
|
||||||
long lastSoundAt = lastPlayerWalkingSounds.computeIfAbsent(player, p -> 0L);
|
long lastSoundAt = lastPlayerWalkingSounds.computeIfAbsent(player, p -> 0L);
|
||||||
long delay = 500; // Delay in ms between footfalls.
|
long delay = 500; // Delay in ms between footfalls.
|
||||||
if (player.getVelocity().length() > 5) delay -= 150;
|
if (player.getVelocity().length() > 5) delay -= 150;
|
||||||
|
@ -131,10 +133,6 @@ public class SoundManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Collection<SoundSource> getSources() {
|
|
||||||
return Collections.unmodifiableCollection(availableSources);
|
|
||||||
}
|
|
||||||
|
|
||||||
private SoundSource getNextAvailableSoundSource() {
|
private SoundSource getNextAvailableSoundSource() {
|
||||||
for (var source : availableSources) {
|
for (var source : availableSources) {
|
||||||
if (!source.isPlaying()) return source;
|
if (!source.isPlaying()) return source;
|
||||||
|
|
|
@ -1,29 +0,0 @@
|
||||||
package nl.andrewl.aos2_client.util;
|
|
||||||
|
|
||||||
import nl.andrewl.aos_core.Pair;
|
|
||||||
import org.lwjgl.BufferUtils;
|
|
||||||
|
|
||||||
import java.nio.IntBuffer;
|
|
||||||
|
|
||||||
import static org.lwjgl.glfw.GLFW.*;
|
|
||||||
|
|
||||||
public class WindowUtils {
|
|
||||||
public static Pair<Integer, Integer> getSize(long id) {
|
|
||||||
IntBuffer wBuf = BufferUtils.createIntBuffer(1);
|
|
||||||
IntBuffer hBuf = BufferUtils.createIntBuffer(1);
|
|
||||||
glfwGetWindowSize(id, wBuf, hBuf);
|
|
||||||
return new Pair<>(wBuf.get(0), hBuf.get(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void captureCursor(long id) {
|
|
||||||
glfwSetInputMode(id, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
|
|
||||||
var size = WindowUtils.getSize(id);
|
|
||||||
glfwSetCursorPos(id, size.first() / 2.0, size.second() / 2.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void freeCursor(long id) {
|
|
||||||
glfwSetInputMode(id, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
|
|
||||||
var size = WindowUtils.getSize(id);
|
|
||||||
glfwSetCursorPos(id, size.first() / 2.0, size.second() / 2.0);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
# Ace of Shades 2 Client Configuration
|
|
||||||
|
|
||||||
# Settings for input.
|
|
||||||
input:
|
|
||||||
mouseSensitivity: 0.005
|
|
||||||
# Settings for display.
|
|
||||||
display:
|
|
||||||
fullscreen: true
|
|
||||||
captureCursor: true
|
|
||||||
fov: 80
|
|
Binary file not shown.
Binary file not shown.
14
core/pom.xml
14
core/pom.xml
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<artifactId>ace-of-shades-2</artifactId>
|
<artifactId>ace-of-shades-2</artifactId>
|
||||||
<groupId>nl.andrewl</groupId>
|
<groupId>nl.andrewl</groupId>
|
||||||
<version>1.5.0</version>
|
<version>1.1.0</version>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
@ -47,6 +47,18 @@
|
||||||
<artifactId>snakeyaml</artifactId>
|
<artifactId>snakeyaml</artifactId>
|
||||||
<version>1.30</version>
|
<version>1.30</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-api -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-api</artifactId>
|
||||||
|
<version>1.7.36</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-slf4j-impl -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.logging.log4j</groupId>
|
||||||
|
<artifactId>log4j-slf4j-impl</artifactId>
|
||||||
|
<version>2.18.0</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api -->
|
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-api -->
|
||||||
|
|
|
@ -26,37 +26,28 @@ public final class Net {
|
||||||
|
|
||||||
private static final Serializer serializer = new Serializer();
|
private static final Serializer serializer = new Serializer();
|
||||||
static {
|
static {
|
||||||
int i = 1;
|
serializer.registerType(1, ConnectRequestMessage.class);
|
||||||
// Basic protocol messages.
|
serializer.registerType(2, ConnectAcceptMessage.class);
|
||||||
serializer.registerType(i++, ConnectRequestMessage.class);
|
serializer.registerType(3, ConnectRejectMessage.class);
|
||||||
serializer.registerType(i++, ConnectAcceptMessage.class);
|
serializer.registerType(4, DatagramInit.class);
|
||||||
serializer.registerType(i++, ConnectRejectMessage.class);
|
serializer.registerType(5, ChunkHashMessage.class);
|
||||||
serializer.registerType(i++, DatagramInit.class);
|
serializer.registerType(6, ChunkDataMessage.class);
|
||||||
|
serializer.registerType(7, ChunkUpdateMessage.class);
|
||||||
// World messages.
|
serializer.registerType(8, ClientInputState.class);
|
||||||
serializer.registerType(i++, ChunkHashMessage.class);
|
serializer.registerType(9, ClientOrientationState.class);
|
||||||
serializer.registerType(i++, ChunkDataMessage.class);
|
serializer.registerType(10, PlayerUpdateMessage.class);
|
||||||
serializer.registerType(i++, ChunkUpdateMessage.class);
|
serializer.registerType(11, PlayerJoinMessage.class);
|
||||||
serializer.registerType(i++, ProjectileMessage.class);
|
serializer.registerType(12, PlayerLeaveMessage.class);
|
||||||
|
|
||||||
// Player/client messages.
|
|
||||||
serializer.registerType(i++, ClientInputState.class);
|
|
||||||
serializer.registerType(i++, ClientOrientationState.class);
|
|
||||||
serializer.registerType(i++, ClientHealthMessage.class);
|
|
||||||
serializer.registerType(i++, PlayerUpdateMessage.class);
|
|
||||||
serializer.registerType(i++, PlayerJoinMessage.class);
|
|
||||||
serializer.registerType(i++, PlayerLeaveMessage.class);
|
|
||||||
serializer.registerType(i++, PlayerTeamUpdateMessage.class);
|
|
||||||
serializer.registerType(i++, BlockColorMessage.class);
|
|
||||||
serializer.registerType(i++, InventorySelectedStackMessage.class);
|
|
||||||
serializer.registerType(i++, ChatMessage.class);
|
|
||||||
serializer.registerType(i++, ChatWrittenMessage.class);
|
|
||||||
serializer.registerType(i++, ClientRecoilMessage.class);
|
|
||||||
// Separate serializers for client inventory messages.
|
// Separate serializers for client inventory messages.
|
||||||
serializer.registerTypeSerializer(i++, new InventorySerializer());
|
serializer.registerTypeSerializer(13, new InventorySerializer());
|
||||||
serializer.registerTypeSerializer(i++, new ItemStackSerializer());
|
serializer.registerTypeSerializer(14, new ItemStackSerializer());
|
||||||
|
serializer.registerType(15, InventorySelectedStackMessage.class);
|
||||||
serializer.registerType(i++, SoundMessage.class);
|
serializer.registerType(16, SoundMessage.class);
|
||||||
|
serializer.registerType(17, ProjectileMessage.class);
|
||||||
|
serializer.registerType(18, ClientHealthMessage.class);
|
||||||
|
serializer.registerType(19, BlockColorMessage.class);
|
||||||
|
serializer.registerType(20, ChatMessage.class);
|
||||||
|
serializer.registerType(21, ChatWrittenMessage.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ExtendedDataInputStream getInputStream(InputStream in) {
|
public static ExtendedDataInputStream getInputStream(InputStream in) {
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package nl.andrewl.aos_core.config;
|
package nl.andrewl.aos_core.config;
|
||||||
|
|
||||||
import nl.andrewl.aos_core.FileUtils;
|
|
||||||
import org.yaml.snakeyaml.Yaml;
|
import org.yaml.snakeyaml.Yaml;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -20,11 +19,10 @@ public final class Config {
|
||||||
* @param paths The paths to load from.
|
* @param paths The paths to load from.
|
||||||
* @param fallback A default configuration object to use if no config could
|
* @param fallback A default configuration object to use if no config could
|
||||||
* be loaded from any of the paths.
|
* be loaded from any of the paths.
|
||||||
* @param defaultConfigFile The default config file resource to save.
|
|
||||||
* @return The configuration object.
|
* @return The configuration object.
|
||||||
* @param <T> The type of the configuration object.
|
* @param <T> The type of the configuration object.
|
||||||
*/
|
*/
|
||||||
public static <T> T loadConfig(Class<T> configType, List<Path> paths, T fallback, String defaultConfigFile) {
|
public static <T> T loadConfig(Class<T> configType, List<Path> paths, T fallback) {
|
||||||
for (var path : paths) {
|
for (var path : paths) {
|
||||||
if (Files.exists(path) && Files.isRegularFile(path) && Files.isReadable(path)) {
|
if (Files.exists(path) && Files.isRegularFile(path) && Files.isReadable(path)) {
|
||||||
try (var reader = Files.newBufferedReader(path)) {
|
try (var reader = Files.newBufferedReader(path)) {
|
||||||
|
@ -34,21 +32,27 @@ public final class Config {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Path outputPath = paths.size() > 0 ? paths.get(0) : Path.of("config.yaml");
|
|
||||||
try (var writer = Files.newBufferedWriter(outputPath)) {
|
|
||||||
writer.write(FileUtils.readClasspathFile(defaultConfigFile));
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
return fallback;
|
return fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static <T> T loadConfig(Class<T> configType, List<Path> paths) {
|
||||||
|
var cfg = loadConfig(configType, paths, null);
|
||||||
|
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, Path... paths) {
|
||||||
|
return loadConfig(configType, List.of(paths), fallback);
|
||||||
|
}
|
||||||
|
|
||||||
public static List<Path> getCommonConfigPaths() {
|
public static List<Path> getCommonConfigPaths() {
|
||||||
List<Path> paths = new ArrayList<>();
|
List<Path> paths = new ArrayList<>();
|
||||||
paths.add(Path.of("config.yaml"));
|
|
||||||
paths.add(Path.of("config.yml"));
|
|
||||||
paths.add(Path.of("configuration.yaml"));
|
paths.add(Path.of("configuration.yaml"));
|
||||||
paths.add(Path.of("configuration.yml"));
|
paths.add(Path.of("configuration.yml"));
|
||||||
|
paths.add(Path.of("config.yaml"));
|
||||||
|
paths.add(Path.of("config.yml"));
|
||||||
paths.add(Path.of("cfg.yaml"));
|
paths.add(Path.of("cfg.yaml"));
|
||||||
paths.add(Path.of("cfg.yml"));
|
paths.add(Path.of("cfg.yml"));
|
||||||
return paths;
|
return paths;
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
package nl.andrewl.aos_core.model;
|
package nl.andrewl.aos_core.model;
|
||||||
|
|
||||||
import nl.andrewl.aos_core.Directions;
|
|
||||||
import nl.andrewl.aos_core.MathUtils;
|
import nl.andrewl.aos_core.MathUtils;
|
||||||
import nl.andrewl.aos_core.model.world.World;
|
|
||||||
import org.joml.*;
|
|
||||||
import org.joml.Math;
|
import org.joml.Math;
|
||||||
|
import org.joml.Vector2f;
|
||||||
|
import org.joml.Vector3f;
|
||||||
|
import org.joml.Vector3i;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -75,13 +75,7 @@ public class Player {
|
||||||
*/
|
*/
|
||||||
protected Team team;
|
protected Team team;
|
||||||
|
|
||||||
/**
|
public Player(int id, String username, Team team) {
|
||||||
* The mode that the player is in, which dictates how they can move and/or
|
|
||||||
* interact with the world.
|
|
||||||
*/
|
|
||||||
protected PlayerMode mode;
|
|
||||||
|
|
||||||
public Player(int id, String username, Team team, PlayerMode mode) {
|
|
||||||
this.position = new Vector3f();
|
this.position = new Vector3f();
|
||||||
this.velocity = new Vector3f();
|
this.velocity = new Vector3f();
|
||||||
this.orientation = new Vector2f();
|
this.orientation = new Vector2f();
|
||||||
|
@ -89,11 +83,10 @@ public class Player {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.username = username;
|
this.username = username;
|
||||||
this.team = team;
|
this.team = team;
|
||||||
this.mode = mode;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Player(int id, String username) {
|
public Player(int id, String username) {
|
||||||
this(id, username, null, PlayerMode.NORMAL);
|
this(id, username, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Vector3f getPosition() {
|
public Vector3f getPosition() {
|
||||||
|
@ -146,14 +139,6 @@ public class Player {
|
||||||
this.team = team;
|
this.team = team;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PlayerMode getMode() {
|
|
||||||
return mode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMode(PlayerMode mode) {
|
|
||||||
this.mode = mode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Vector3f getViewVector() {
|
public Vector3f getViewVector() {
|
||||||
return viewVector;
|
return viewVector;
|
||||||
}
|
}
|
||||||
|
@ -183,48 +168,6 @@ public class Player {
|
||||||
return crouching ? HEIGHT_CROUCH : HEIGHT;
|
return crouching ? HEIGHT_CROUCH : HEIGHT;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a transformation that transforms a position to the position of the
|
|
||||||
* player's held gun.
|
|
||||||
* @return The gun transform.
|
|
||||||
*/
|
|
||||||
public Matrix4f getHeldItemTransform() {
|
|
||||||
return new Matrix4f()
|
|
||||||
.translate(position)
|
|
||||||
.rotate(orientation.x + (float) Math.PI, Directions.UPf)
|
|
||||||
.translate(-0.35f, getEyeHeight() - 0.4f, 0.35f);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the list of all spaces occupied by a player's position, in the
|
|
||||||
* horizontal XZ plane. This can be between 1 and 4 spaces, depending on
|
|
||||||
* if the player's position is overlapping with a few blocks.
|
|
||||||
* @param pos The position.
|
|
||||||
* @return The list of 2d positions occupied.
|
|
||||||
*/
|
|
||||||
private List<Vector2i> getHorizontalSpaceOccupied(Vector3f pos) {
|
|
||||||
// Get the list of 2d x,z coordinates that we overlap with.
|
|
||||||
List<Vector2i> points = new ArrayList<>(4); // Due to the size of radius, there can only be a max of 4 blocks.
|
|
||||||
int minX = (int) Math.floor(pos.x - RADIUS);
|
|
||||||
int minZ = (int) Math.floor(pos.z - RADIUS);
|
|
||||||
int maxX = (int) Math.floor(pos.x + RADIUS);
|
|
||||||
int maxZ = (int) Math.floor(pos.z + RADIUS);
|
|
||||||
for (int x = minX; x <= maxX; x++) {
|
|
||||||
for (int z = minZ; z <= maxZ; z++) {
|
|
||||||
points.add(new Vector2i(x, z));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return points;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isGrounded(World world) {
|
|
||||||
// Player must be flat on the top of a block.
|
|
||||||
if (Math.floor(position.y) != position.y) return false;
|
|
||||||
// Check to see if there's a block under any of the spaces the player is over.
|
|
||||||
return getHorizontalSpaceOccupied(position).stream()
|
|
||||||
.anyMatch(point -> world.getBlockAt(point.x, position.y - 0.1f, point.y) != 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<Vector3i> getBlockSpaceOccupied() {
|
public List<Vector3i> getBlockSpaceOccupied() {
|
||||||
float playerBodyMinZ = position.z - RADIUS;
|
float playerBodyMinZ = position.z - RADIUS;
|
||||||
float playerBodyMaxZ = position.z + RADIUS;
|
float playerBodyMaxZ = position.z + RADIUS;
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
package nl.andrewl.aos_core.model;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents the different modes that a player can be in.
|
|
||||||
* <ul>
|
|
||||||
* <li>
|
|
||||||
* In normal mode, the player acts as a usual competitive player that
|
|
||||||
* has an inventory, can shoot weapons, and must traverse the world by
|
|
||||||
* walking around.
|
|
||||||
* </li>
|
|
||||||
* <li>
|
|
||||||
* In creative mode, the player can fly, but still collides with the
|
|
||||||
* world's objects. The player also has unlimited ammunition and
|
|
||||||
* blocks.
|
|
||||||
* </li>
|
|
||||||
* <li>
|
|
||||||
* In spectator mode, the player can fly freely throughout the world,
|
|
||||||
* limited by nothing. The player can't interact with the world in any
|
|
||||||
* way other than simply observing it through sight and sound.
|
|
||||||
* </li>
|
|
||||||
* </ul>
|
|
||||||
*/
|
|
||||||
public enum PlayerMode {
|
|
||||||
NORMAL,
|
|
||||||
CREATIVE,
|
|
||||||
SPECTATOR
|
|
||||||
}
|
|
|
@ -31,7 +31,6 @@ public class Inventory {
|
||||||
}
|
}
|
||||||
|
|
||||||
public ItemStack getSelectedItemStack() {
|
public ItemStack getSelectedItemStack() {
|
||||||
if (itemStacks.isEmpty()) return null;
|
|
||||||
return itemStacks.get(selectedIndex);
|
return itemStacks.get(selectedIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,13 +47,6 @@ public class Inventory {
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getIndex(ItemStack stack) {
|
|
||||||
for (int i = 0; i < itemStacks.size(); i++) {
|
|
||||||
if (itemStacks.get(i).equals(stack)) return i;
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte getSelectedBlockValue() {
|
public byte getSelectedBlockValue() {
|
||||||
for (var stack : itemStacks) {
|
for (var stack : itemStacks) {
|
||||||
if (stack instanceof BlockItemStack b) {
|
if (stack instanceof BlockItemStack b) {
|
||||||
|
@ -63,9 +55,4 @@ public class Inventory {
|
||||||
}
|
}
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clear() {
|
|
||||||
itemStacks.clear();
|
|
||||||
selectedIndex = -1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ public class Ak47 extends Gun {
|
||||||
0.1f,
|
0.1f,
|
||||||
1.2f,
|
1.2f,
|
||||||
0.4f,
|
0.4f,
|
||||||
0.1f,
|
30f,
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,14 +7,14 @@ public class Rifle extends Gun {
|
||||||
super(
|
super(
|
||||||
id,
|
id,
|
||||||
"Rifle",
|
"Rifle",
|
||||||
6,
|
5,
|
||||||
8,
|
8,
|
||||||
1,
|
1,
|
||||||
0.98f,
|
0.97f,
|
||||||
0.8f,
|
0.8f,
|
||||||
2.5f,
|
2.5f,
|
||||||
0.8f,
|
0.8f,
|
||||||
0.2f,
|
50f,
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,14 +7,14 @@ public class Winchester extends Gun {
|
||||||
super(
|
super(
|
||||||
id,
|
id,
|
||||||
"Winchester",
|
"Winchester",
|
||||||
|
10,
|
||||||
6,
|
6,
|
||||||
4,
|
4,
|
||||||
4,
|
|
||||||
0.85f,
|
0.85f,
|
||||||
0.75f,
|
0.75f,
|
||||||
2.5f,
|
2.5f,
|
||||||
0.3f,
|
0.3f,
|
||||||
0.33f,
|
60f,
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ import java.util.Map;
|
||||||
* that players can interact in.
|
* that players can interact in.
|
||||||
*/
|
*/
|
||||||
public class World {
|
public class World {
|
||||||
private static final float DELTA = 0.001f;
|
private static final float DELTA = 0.01f;
|
||||||
|
|
||||||
protected final Map<Vector3ic, Chunk> chunkMap = new HashMap<>();
|
protected final Map<Vector3ic, Chunk> chunkMap = new HashMap<>();
|
||||||
protected ColorPalette palette;
|
protected ColorPalette palette;
|
||||||
|
@ -142,12 +142,6 @@ public class World {
|
||||||
return chunkMap.values().stream().mapToInt(c -> c.getPosition().z * Chunk.SIZE + Chunk.SIZE - 1).max().orElse(0);
|
return chunkMap.values().stream().mapToInt(c -> c.getPosition().z * Chunk.SIZE + Chunk.SIZE - 1).max().orElse(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean containsPoint(Vector3i pos) {
|
|
||||||
return pos.x >= getMinX() && pos.x < getMaxX() &&
|
|
||||||
pos.y >= getMinY() && pos.y < getMaxY() &&
|
|
||||||
pos.z >= getMinZ() && pos.z < getMaxZ();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clears all data from the world.
|
* Clears all data from the world.
|
||||||
*/
|
*/
|
||||||
|
@ -170,12 +164,8 @@ public class World {
|
||||||
public Hit getLookingAtPos(Vector3f eyePos, Vector3f eyeDir, float limit) {
|
public Hit getLookingAtPos(Vector3f eyePos, Vector3f eyeDir, float limit) {
|
||||||
if (eyeDir.lengthSquared() == 0 || limit <= 0) return null;
|
if (eyeDir.lengthSquared() == 0 || limit <= 0) return null;
|
||||||
Vector3f pos = new Vector3f(eyePos);
|
Vector3f pos = new Vector3f(eyePos);
|
||||||
Vector3f previousPos = new Vector3f();
|
|
||||||
while (pos.distance(eyePos) < limit) {
|
while (pos.distance(eyePos) < limit) {
|
||||||
previousPos.set(pos);
|
|
||||||
stepToNextBlock(pos, eyeDir);
|
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) {
|
if (getBlockAt(pos) > 0) {
|
||||||
Vector3i hitPos = new Vector3i(
|
Vector3i hitPos = new Vector3i(
|
||||||
(int) Math.floor(pos.x),
|
(int) Math.floor(pos.x),
|
||||||
|
@ -244,7 +234,7 @@ public class World {
|
||||||
// Testing code!
|
// Testing code!
|
||||||
if (diff == 0) {
|
if (diff == 0) {
|
||||||
System.out.printf("n = %.8f, nextValue = %.8f, floor(n) - DELTA = %.8f%n", n, nextValue, Math.floor(n) - DELTA);
|
System.out.printf("n = %.8f, nextValue = %.8f, floor(n) - DELTA = %.8f%n", n, nextValue, Math.floor(n) - DELTA);
|
||||||
return Float.MAX_VALUE;
|
throw new RuntimeException("EEK");
|
||||||
}
|
}
|
||||||
return Math.abs(diff / dir);
|
return Math.abs(diff / dir);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package nl.andrewl.aos_core.net.client;
|
package nl.andrewl.aos_core.net.client;
|
||||||
|
|
||||||
import nl.andrewl.aos_core.model.Player;
|
|
||||||
import nl.andrewl.record_net.Message;
|
import nl.andrewl.record_net.Message;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -12,8 +11,4 @@ import nl.andrewl.record_net.Message;
|
||||||
public record ClientOrientationState(
|
public record ClientOrientationState(
|
||||||
int clientId,
|
int clientId,
|
||||||
float x, float y
|
float x, float y
|
||||||
) implements Message {
|
) implements Message {}
|
||||||
public static ClientOrientationState fromPlayer(Player player) {
|
|
||||||
return new ClientOrientationState(player.getId(), player.getOrientation().x, player.getOrientation().y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
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 according to a recoil event.
|
|
||||||
*/
|
|
||||||
public record ClientRecoilMessage(
|
|
||||||
float dx, float dy
|
|
||||||
) implements Message {}
|
|
|
@ -1,7 +1,6 @@
|
||||||
package nl.andrewl.aos_core.net.client;
|
package nl.andrewl.aos_core.net.client;
|
||||||
|
|
||||||
import nl.andrewl.aos_core.model.Player;
|
import nl.andrewl.aos_core.model.Player;
|
||||||
import nl.andrewl.aos_core.model.PlayerMode;
|
|
||||||
import nl.andrewl.aos_core.model.item.ItemTypes;
|
import nl.andrewl.aos_core.model.item.ItemTypes;
|
||||||
import nl.andrewl.record_net.Message;
|
import nl.andrewl.record_net.Message;
|
||||||
|
|
||||||
|
@ -16,15 +15,13 @@ public record PlayerJoinMessage(
|
||||||
float ox, float oy,
|
float ox, float oy,
|
||||||
boolean crouching,
|
boolean crouching,
|
||||||
int selectedItemId,
|
int selectedItemId,
|
||||||
byte selectedBlockValue,
|
byte selectedBlockValue
|
||||||
PlayerMode mode
|
|
||||||
) implements Message {
|
) implements Message {
|
||||||
public Player toPlayer() {
|
public Player toPlayer() {
|
||||||
Player p = new Player(id, username);
|
Player p = new Player(id, username);
|
||||||
p.getPosition().set(px, py, pz);
|
p.getPosition().set(px, py, pz);
|
||||||
p.getVelocity().set(vx, vy, vz);
|
p.getVelocity().set(vx, vy, vz);
|
||||||
p.getOrientation().set(ox, oy);
|
p.getOrientation().set(ox, oy);
|
||||||
p.setMode(mode);
|
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
package nl.andrewl.aos_core.net.client;
|
|
||||||
|
|
||||||
import nl.andrewl.record_net.Message;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A message that's sent by the server to announce that a player has changed to
|
|
||||||
* a specified team. Both the player and team should already be recognized by
|
|
||||||
* all clients; otherwise they can ignore this.
|
|
||||||
*/
|
|
||||||
public record PlayerTeamUpdateMessage(
|
|
||||||
int playerId,
|
|
||||||
int teamId
|
|
||||||
) implements Message {}
|
|
|
@ -1,7 +1,6 @@
|
||||||
package nl.andrewl.aos_core.net.client;
|
package nl.andrewl.aos_core.net.client;
|
||||||
|
|
||||||
import nl.andrewl.aos_core.model.Player;
|
import nl.andrewl.aos_core.model.Player;
|
||||||
import nl.andrewl.aos_core.model.PlayerMode;
|
|
||||||
import nl.andrewl.record_net.Message;
|
import nl.andrewl.record_net.Message;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -17,8 +16,7 @@ public record PlayerUpdateMessage(
|
||||||
float vx, float vy, float vz,
|
float vx, float vy, float vz,
|
||||||
float ox, float oy,
|
float ox, float oy,
|
||||||
boolean crouching,
|
boolean crouching,
|
||||||
int selectedItemId,
|
int selectedItemId
|
||||||
PlayerMode mode
|
|
||||||
) implements Message {
|
) implements Message {
|
||||||
|
|
||||||
public void apply(Player p) {
|
public void apply(Player p) {
|
||||||
|
@ -26,6 +24,5 @@ public record PlayerUpdateMessage(
|
||||||
p.getVelocity().set(vx, vy, vz);
|
p.getVelocity().set(vx, vy, vz);
|
||||||
p.getOrientation().set(ox, oy);
|
p.getOrientation().set(ox, oy);
|
||||||
p.setCrouching(crouching);
|
p.setCrouching(crouching);
|
||||||
p.setMode(mode);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,4 @@ package nl.andrewl.aos_core.net.connect;
|
||||||
|
|
||||||
import nl.andrewl.record_net.Message;
|
import nl.andrewl.record_net.Message;
|
||||||
|
|
||||||
/**
|
public record ConnectRequestMessage(String username) implements Message {}
|
||||||
* The first message that a client sends via TCP to the server, to indicate
|
|
||||||
* that they'd like to join.
|
|
||||||
* @param username The player's chosen username.
|
|
||||||
* @param spectator Whether the player wants to be a spectator.
|
|
||||||
*/
|
|
||||||
public record ConnectRequestMessage(String username, boolean spectator) implements Message {}
|
|
||||||
|
|
|
@ -4,6 +4,6 @@ appender.console.name = STDOUT
|
||||||
appender.console.layout.type = PatternLayout
|
appender.console.layout.type = PatternLayout
|
||||||
appender.console.layout.pattern = [%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n
|
appender.console.layout.pattern = [%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n
|
||||||
|
|
||||||
rootLogger.level = info
|
rootLogger.level = debug
|
||||||
rootLogger.appenderRefs = stdout
|
rootLogger.appenderRefs = stdout
|
||||||
rootLogger.appenderRef.stdout.ref = STDOUT
|
rootLogger.appenderRef.stdout.ref = STDOUT
|
Binary file not shown.
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 29 KiB |
Binary file not shown.
Before Width: | Height: | Size: 31 KiB |
Binary file not shown.
Before Width: | Height: | Size: 1.7 KiB |
|
@ -1,30 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
function join_by {
|
|
||||||
local d=${1-} f=${2-}
|
|
||||||
if shift 2; then
|
|
||||||
printf %s "$f" "${@/#/$d}"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
mvn clean package
|
|
||||||
cd target
|
|
||||||
module_jars=(lib/*)
|
|
||||||
eligible_main_jars=("*.jar")
|
|
||||||
main_jar=(${eligible_main_jars[0]})
|
|
||||||
module_path=$(join_by ":" ${module_jars[@]})
|
|
||||||
module_path="$main_jar:$module_path"
|
|
||||||
echo $module_path
|
|
||||||
jpackage \
|
|
||||||
--name "Ace of Shades Launcher" \
|
|
||||||
--app-version "1.0.0" \
|
|
||||||
--description "Launcher app for Ace of Shades, a voxel-based first-person shooter." \
|
|
||||||
--icon ../icon.ico \
|
|
||||||
--linux-shortcut \
|
|
||||||
--linux-deb-maintainer "andrewlalisofficial@gmail.com" \
|
|
||||||
--linux-menu-group "Game" \
|
|
||||||
--linux-app-category "Game" \
|
|
||||||
--module-path "$module_path" \
|
|
||||||
--module aos2_launcher/nl.andrewl.aos2_launcher.Launcher \
|
|
||||||
--add-modules jdk.crypto.cryptoki
|
|
||||||
|
|
|
@ -1,42 +0,0 @@
|
||||||
# This script prepares and runs the jpackage command to generate a Windows AOS Client installer.
|
|
||||||
|
|
||||||
$projectDir = $PSScriptRoot
|
|
||||||
Push-Location $projectDir\target
|
|
||||||
|
|
||||||
# Remove existing file if it exists.
|
|
||||||
Write-Output "Removing existing exe file."
|
|
||||||
Get-ChildItem *.exe | ForEach-Object { Remove-Item -Path $_.FullName -Force }
|
|
||||||
Write-Output "Done."
|
|
||||||
|
|
||||||
# Run the build
|
|
||||||
Write-Output "Building the project."
|
|
||||||
Push-Location $projectDir
|
|
||||||
mvn clean package
|
|
||||||
|
|
||||||
# Get list of dependency modules that maven copied into the lib directory.
|
|
||||||
Push-Location $projectDir\target
|
|
||||||
$modules = Get-ChildItem -Path lib -Name | ForEach-Object { "lib\$_" }
|
|
||||||
# Add our own main module.
|
|
||||||
$mainModuleJar = Get-ChildItem -Name -Include "aos2-launcher-*.jar" -Exclude "*-jar-with-dependencies.jar"
|
|
||||||
$modules += $mainModuleJar
|
|
||||||
Write-Output "Found modules: $modules"
|
|
||||||
$modulePath = $modules -join ';'
|
|
||||||
|
|
||||||
Write-Output "Running jpackage..."
|
|
||||||
jpackage `
|
|
||||||
--type msi `
|
|
||||||
--name "Ace-of-Shades" `
|
|
||||||
--app-version "1.0.0" `
|
|
||||||
--description "Top-down 2D shooter game inspired by Ace of Spades." `
|
|
||||||
--icon ..\icon.ico `
|
|
||||||
--win-shortcut `
|
|
||||||
--win-dir-chooser `
|
|
||||||
--win-per-user-install `
|
|
||||||
--win-menu `
|
|
||||||
--win-shortcut `
|
|
||||||
--win-menu-group "Game" `
|
|
||||||
--module-path "$modulePath" `
|
|
||||||
--module aos2_launcher/nl.andrewl.aos2_launcher.Launcher `
|
|
||||||
--add-modules jdk.crypto.cryptoki
|
|
||||||
|
|
||||||
Write-Output "Done!"
|
|
|
@ -11,9 +11,8 @@
|
||||||
<properties>
|
<properties>
|
||||||
<maven.compiler.source>18</maven.compiler.source>
|
<maven.compiler.source>18</maven.compiler.source>
|
||||||
<maven.compiler.target>18</maven.compiler.target>
|
<maven.compiler.target>18</maven.compiler.target>
|
||||||
<javafx.version>18.0.2</javafx.version>
|
<javafx.version>18.0.1</javafx.version>
|
||||||
<javafx.maven.plugin.version>0.0.8</javafx.maven.plugin.version>
|
<javafx.maven.plugin.version>0.0.8</javafx.maven.plugin.version>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
@ -27,12 +26,6 @@
|
||||||
<artifactId>javafx-fxml</artifactId>
|
<artifactId>javafx-fxml</artifactId>
|
||||||
<version>${javafx.version}</version>
|
<version>${javafx.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.google.code.gson</groupId>
|
|
||||||
<artifactId>gson</artifactId>
|
|
||||||
<version>2.9.1</version>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
@ -54,46 +47,6 @@
|
||||||
<target>17</target>
|
<target>17</target>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
|
||||||
<artifactId>maven-assembly-plugin</artifactId>
|
|
||||||
<version>3.3.0</version>
|
|
||||||
<configuration>
|
|
||||||
<archive>
|
|
||||||
<manifest>
|
|
||||||
<mainClass>nl.andrewl.aos2_launcher.Launcher</mainClass>
|
|
||||||
</manifest>
|
|
||||||
</archive>
|
|
||||||
<descriptorRefs>
|
|
||||||
<descriptorRef>jar-with-dependencies</descriptorRef>
|
|
||||||
</descriptorRefs>
|
|
||||||
</configuration>
|
|
||||||
<executions>
|
|
||||||
<execution>
|
|
||||||
<id>make-assembly</id>
|
|
||||||
<phase>package</phase>
|
|
||||||
<goals>
|
|
||||||
<goal>single</goal>
|
|
||||||
</goals>
|
|
||||||
</execution>
|
|
||||||
</executions>
|
|
||||||
</plugin>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
|
||||||
<artifactId>maven-dependency-plugin</artifactId>
|
|
||||||
<version>2.8</version>
|
|
||||||
<executions>
|
|
||||||
<execution>
|
|
||||||
<phase>package</phase>
|
|
||||||
<goals>
|
|
||||||
<goal>copy-dependencies</goal>
|
|
||||||
</goals>
|
|
||||||
<configuration>
|
|
||||||
<outputDirectory>${project.build.directory}/lib</outputDirectory>
|
|
||||||
</configuration>
|
|
||||||
</execution>
|
|
||||||
</executions>
|
|
||||||
</plugin>
|
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
|
|
||||||
|
|
|
@ -4,10 +4,6 @@ module aos2_launcher {
|
||||||
requires javafx.graphics;
|
requires javafx.graphics;
|
||||||
requires javafx.fxml;
|
requires javafx.fxml;
|
||||||
|
|
||||||
requires java.net.http;
|
|
||||||
requires com.google.gson;
|
|
||||||
|
|
||||||
exports nl.andrewl.aos2_launcher to javafx.graphics;
|
exports nl.andrewl.aos2_launcher to javafx.graphics;
|
||||||
opens nl.andrewl.aos2_launcher to javafx.fxml;
|
opens nl.andrewl.aos2_launcher to javafx.fxml;
|
||||||
opens nl.andrewl.aos2_launcher.view to javafx.fxml;
|
|
||||||
}
|
}
|
|
@ -1,4 +0,0 @@
|
||||||
package nl.andrewl.aos2_launcher;
|
|
||||||
|
|
||||||
public class EditProfileController {
|
|
||||||
}
|
|
|
@ -1,70 +0,0 @@
|
||||||
package nl.andrewl.aos2_launcher;
|
|
||||||
|
|
||||||
import javafx.application.Platform;
|
|
||||||
import javafx.scene.control.Alert;
|
|
||||||
import javafx.stage.Window;
|
|
||||||
import nl.andrewl.aos2_launcher.model.Profile;
|
|
||||||
import nl.andrewl.aos2_launcher.model.ProgressReporter;
|
|
||||||
import nl.andrewl.aos2_launcher.model.Server;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
|
|
||||||
public class GameRunner {
|
|
||||||
public void run(Profile profile, Server server, ProgressReporter progressReporter, Window owner) {
|
|
||||||
SystemVersionValidator.getJreExecutablePath(progressReporter)
|
|
||||||
.whenCompleteAsync((jrePath, throwable) -> {
|
|
||||||
if (throwable != null) {
|
|
||||||
showPopup(
|
|
||||||
owner,
|
|
||||||
Alert.AlertType.ERROR,
|
|
||||||
"An error occurred while ensuring that you've got the latest Java runtime: " + throwable.getMessage()
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
VersionFetcher.INSTANCE.ensureVersionIsDownloaded(profile.getClientVersion(), progressReporter)
|
|
||||||
.whenCompleteAsync((clientJarPath, throwable2) -> {
|
|
||||||
progressReporter.disableProgress();
|
|
||||||
if (throwable2 != null) {
|
|
||||||
showPopup(
|
|
||||||
owner,
|
|
||||||
Alert.AlertType.ERROR,
|
|
||||||
"An error occurred while ensuring you've got the correct client version: " + throwable2.getMessage()
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
startGame(owner, profile, server, jrePath, clientJarPath);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void startGame(Window owner, Profile profile, Server server, Path jrePath, Path clientJarPath) {
|
|
||||||
try {
|
|
||||||
Process p = new ProcessBuilder()
|
|
||||||
.command(
|
|
||||||
jrePath.toAbsolutePath().toString(),
|
|
||||||
"-jar", clientJarPath.toAbsolutePath().toString(),
|
|
||||||
server.getHost(),
|
|
||||||
Integer.toString(server.getPort()),
|
|
||||||
profile.getUsername()
|
|
||||||
)
|
|
||||||
.directory(profile.getDir().toFile())
|
|
||||||
.inheritIO()
|
|
||||||
.start();
|
|
||||||
p.wait();
|
|
||||||
} catch (IOException e) {
|
|
||||||
showPopup(owner, Alert.AlertType.ERROR, "An error occurred while starting the game: " + e.getMessage());
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
showPopup(owner, Alert.AlertType.ERROR, "The game was interrupted: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showPopup(Window owner, Alert.AlertType type, String text) {
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
Alert alert = new Alert(Alert.AlertType.ERROR);
|
|
||||||
alert.initOwner(owner);
|
|
||||||
alert.setContentText(text);
|
|
||||||
alert.show();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -7,40 +7,22 @@ import javafx.scene.Scene;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The main starting point for the launcher app.
|
* The main starting point for the launcher app.
|
||||||
*/
|
*/
|
||||||
public class Launcher extends Application {
|
public class Launcher extends Application {
|
||||||
public static final Path BASE_DIR = Path.of(System.getProperty("user.home"), ".ace-of-shades");
|
|
||||||
public static final Path VERSIONS_DIR = BASE_DIR.resolve("versions");
|
|
||||||
public static final Path PROFILES_FILE = BASE_DIR.resolve("profiles.json");
|
|
||||||
public static final Path PROFILES_DIR = BASE_DIR.resolve("profiles");
|
|
||||||
public static final Path JRE_PATH = BASE_DIR.resolve("jre");
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void start(Stage stage) throws IOException {
|
public void start(Stage stage) throws IOException {
|
||||||
if (!Files.exists(BASE_DIR)) Files.createDirectory(BASE_DIR);
|
|
||||||
if (!Files.exists(VERSIONS_DIR)) Files.createDirectory(VERSIONS_DIR);
|
|
||||||
if (!Files.exists(PROFILES_DIR)) Files.createDirectory(PROFILES_DIR);
|
|
||||||
FXMLLoader loader = new FXMLLoader(Launcher.class.getResource("/main_view.fxml"));
|
FXMLLoader loader = new FXMLLoader(Launcher.class.getResource("/main_view.fxml"));
|
||||||
Parent rootNode = loader.load();
|
Parent rootNode = loader.load();
|
||||||
Scene scene = new Scene(rootNode);
|
Scene scene = new Scene(rootNode);
|
||||||
addStylesheet(scene, "/font/fonts.css");
|
scene.getStylesheets().add(Launcher.class.getResource("/styles.css").toExternalForm());
|
||||||
addStylesheet(scene, "/styles.css");
|
|
||||||
stage.setScene(scene);
|
stage.setScene(scene);
|
||||||
stage.setTitle("Ace of Shades - Launcher");
|
stage.setTitle("Ace of Shades 2 - Launcher");
|
||||||
stage.show();
|
stage.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addStylesheet(Scene scene, String resource) throws IOException {
|
|
||||||
var url = Launcher.class.getResource(resource);
|
|
||||||
if (url == null) throw new IOException("Could not load resource at " + resource);
|
|
||||||
scene.getStylesheets().add(url.toExternalForm());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
launch(args);
|
launch(args);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,140 +1,11 @@
|
||||||
package nl.andrewl.aos2_launcher;
|
package nl.andrewl.aos2_launcher;
|
||||||
|
|
||||||
import javafx.application.Platform;
|
|
||||||
import javafx.beans.binding.BooleanBinding;
|
|
||||||
import javafx.collections.ListChangeListener;
|
|
||||||
import javafx.fxml.FXML;
|
import javafx.fxml.FXML;
|
||||||
import javafx.scene.control.*;
|
import javafx.scene.layout.TilePane;
|
||||||
import javafx.scene.layout.VBox;
|
|
||||||
import javafx.stage.Window;
|
|
||||||
import nl.andrewl.aos2_launcher.model.Profile;
|
|
||||||
import nl.andrewl.aos2_launcher.model.ProfileSet;
|
|
||||||
import nl.andrewl.aos2_launcher.model.ProgressReporter;
|
|
||||||
import nl.andrewl.aos2_launcher.model.Server;
|
|
||||||
import nl.andrewl.aos2_launcher.view.EditProfileDialog;
|
|
||||||
import nl.andrewl.aos2_launcher.view.ElementList;
|
|
||||||
import nl.andrewl.aos2_launcher.view.ProfileView;
|
|
||||||
import nl.andrewl.aos2_launcher.view.ServerView;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
public class MainViewController implements ProgressReporter {
|
|
||||||
@FXML public Button playButton;
|
|
||||||
@FXML public Button editProfileButton;
|
|
||||||
@FXML public Button removeProfileButton;
|
|
||||||
@FXML public VBox profilesVBox;
|
|
||||||
private ElementList<Profile, ProfileView> profilesList;
|
|
||||||
@FXML public VBox serversVBox;
|
|
||||||
private ElementList<Server, ServerView> serversList;
|
|
||||||
|
|
||||||
@FXML public VBox progressVBox;
|
|
||||||
@FXML public Label progressLabel;
|
|
||||||
@FXML public ProgressBar progressBar;
|
|
||||||
@FXML public TextField registryUrlField;
|
|
||||||
|
|
||||||
private final ProfileSet profileSet = new ProfileSet();
|
|
||||||
|
|
||||||
private ServersFetcher serversFetcher;
|
|
||||||
|
|
||||||
|
public class MainViewController {
|
||||||
@FXML
|
@FXML
|
||||||
public void initialize() {
|
public TilePane profilesTilePane;
|
||||||
profilesList = new ElementList<>(profilesVBox, ProfileView::new, ProfileView.class, ProfileView::getProfile);
|
|
||||||
profileSet.selectedProfileProperty().addListener((observable, oldValue, newValue) -> profileSet.save());
|
|
||||||
// A hack since we can't bind the profilesList's elements to the profileSet's.
|
|
||||||
profileSet.getProfiles().addListener((ListChangeListener<? super Profile>) c -> {
|
|
||||||
var selected = profileSet.getSelectedProfile();
|
|
||||||
profilesList.clear();
|
|
||||||
profilesList.addAll(profileSet.getProfiles());
|
|
||||||
profilesList.selectElement(selected);
|
|
||||||
});
|
|
||||||
profileSet.loadOrCreateStandardFile();
|
|
||||||
profilesList.selectElement(profileSet.getSelectedProfile());
|
|
||||||
profileSet.selectedProfileProperty().bind(profilesList.selectedElementProperty());
|
|
||||||
|
|
||||||
serversList = new ElementList<>(serversVBox, ServerView::new, ServerView.class, ServerView::getServer);
|
|
||||||
|
|
||||||
BooleanBinding playBind = profileSet.selectedProfileProperty().isNull().or(serversList.selectedElementProperty().isNull());
|
|
||||||
playButton.disableProperty().bind(playBind);
|
|
||||||
editProfileButton.disableProperty().bind(profileSet.selectedProfileProperty().isNull());
|
|
||||||
removeProfileButton.disableProperty().bind(profileSet.selectedProfileProperty().isNull());
|
|
||||||
|
|
||||||
progressVBox.managedProperty().bind(progressVBox.visibleProperty());
|
|
||||||
progressVBox.setVisible(false);
|
|
||||||
|
|
||||||
serversFetcher = new ServersFetcher(registryUrlField.textProperty());
|
|
||||||
Platform.runLater(this::refreshServers);
|
|
||||||
}
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
public void refreshServers() {
|
|
||||||
Window owner = this.profilesVBox.getScene().getWindow();
|
|
||||||
serversFetcher.fetchServers(owner)
|
|
||||||
.exceptionally(throwable -> {
|
|
||||||
throwable.printStackTrace();
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
Alert alert = new Alert(Alert.AlertType.ERROR);
|
|
||||||
alert.setHeaderText("Couldn't fetch servers.");
|
|
||||||
alert.setContentText("An error occurred, and the list of servers couldn't be fetched: " + throwable.getMessage() + ". Are you sure that you have the correct registry URL? Check the \"Servers\" tab.");
|
|
||||||
alert.initOwner(owner);
|
|
||||||
alert.show();
|
|
||||||
});
|
|
||||||
return new ArrayList<>();
|
|
||||||
})
|
|
||||||
.thenAccept(newServers -> Platform.runLater(() -> {
|
|
||||||
serversList.clear();
|
|
||||||
serversList.addAll(newServers);
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
public void addProfile() {
|
|
||||||
EditProfileDialog dialog = new EditProfileDialog(profilesVBox.getScene().getWindow());
|
|
||||||
dialog.showAndWait().ifPresent(profileSet::addNewProfile);
|
|
||||||
}
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
public void editProfile() {
|
|
||||||
EditProfileDialog dialog = new EditProfileDialog(profilesVBox.getScene().getWindow(), profileSet.getSelectedProfile());
|
|
||||||
dialog.showAndWait();
|
|
||||||
profileSet.save();
|
|
||||||
}
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
public void removeProfile() {
|
|
||||||
profileSet.removeSelectedProfile();
|
|
||||||
}
|
|
||||||
|
|
||||||
@FXML
|
|
||||||
public void play() {
|
|
||||||
new GameRunner().run(
|
|
||||||
profileSet.getSelectedProfile(),
|
|
||||||
serversList.getSelectedElement(),
|
|
||||||
this,
|
|
||||||
this.profilesVBox.getScene().getWindow()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void enableProgress() {
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
progressVBox.setVisible(true);
|
|
||||||
progressBar.setProgress(ProgressIndicator.INDETERMINATE_PROGRESS);
|
|
||||||
progressLabel.setText(null);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void disableProgress() {
|
|
||||||
Platform.runLater(() -> progressVBox.setVisible(false));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setActionText(String text) {
|
|
||||||
Platform.runLater(() -> progressLabel.setText(text));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setProgress(double progress) {
|
|
||||||
Platform.runLater(() -> progressBar.setProgress(progress));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,74 +0,0 @@
|
||||||
package nl.andrewl.aos2_launcher;
|
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
|
||||||
import com.google.gson.JsonArray;
|
|
||||||
import com.google.gson.JsonElement;
|
|
||||||
import com.google.gson.JsonObject;
|
|
||||||
import javafx.application.Platform;
|
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
|
||||||
import javafx.beans.property.StringProperty;
|
|
||||||
import javafx.scene.control.Alert;
|
|
||||||
import javafx.stage.Window;
|
|
||||||
import nl.andrewl.aos2_launcher.model.Server;
|
|
||||||
|
|
||||||
import java.net.URI;
|
|
||||||
import java.net.http.HttpClient;
|
|
||||||
import java.net.http.HttpRequest;
|
|
||||||
import java.net.http.HttpResponse;
|
|
||||||
import java.time.Duration;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
|
|
||||||
class ServersFetcher {
|
|
||||||
private final HttpClient httpClient;
|
|
||||||
private final Gson gson;
|
|
||||||
private final StringProperty registryUrl;
|
|
||||||
|
|
||||||
public ServersFetcher(StringProperty registryUrlProperty) {
|
|
||||||
httpClient = HttpClient.newBuilder().build();
|
|
||||||
gson = new Gson();
|
|
||||||
this.registryUrl = new SimpleStringProperty("http://localhost:8080");
|
|
||||||
registryUrl.bind(registryUrlProperty);
|
|
||||||
}
|
|
||||||
|
|
||||||
public CompletableFuture<List<Server>> fetchServers(Window owner) {
|
|
||||||
if (registryUrl.get() == null || registryUrl.get().isBlank()) {
|
|
||||||
Platform.runLater(() -> {
|
|
||||||
Alert alert = new Alert(Alert.AlertType.WARNING);
|
|
||||||
alert.setContentText("Invalid or missing registry URL. Can't fetch the list of servers.");
|
|
||||||
alert.initOwner(owner);
|
|
||||||
alert.show();
|
|
||||||
});
|
|
||||||
return CompletableFuture.completedFuture(new ArrayList<>());
|
|
||||||
}
|
|
||||||
HttpRequest req = HttpRequest.newBuilder(URI.create(registryUrl.get() + "/servers"))
|
|
||||||
.GET()
|
|
||||||
.timeout(Duration.ofSeconds(3))
|
|
||||||
.header("Accept", "application/json")
|
|
||||||
.build();
|
|
||||||
return httpClient.sendAsync(req, HttpResponse.BodyHandlers.ofString())
|
|
||||||
.thenApplyAsync(resp -> {
|
|
||||||
if (resp.statusCode() == 200) {
|
|
||||||
JsonArray serversArray = gson.fromJson(resp.body(), JsonArray.class);
|
|
||||||
List<Server> servers = new ArrayList<>(serversArray.size());
|
|
||||||
for (JsonElement serverJson : serversArray) {
|
|
||||||
if (serverJson instanceof JsonObject obj) {
|
|
||||||
servers.add(new Server(
|
|
||||||
obj.get("host").getAsString(),
|
|
||||||
obj.get("port").getAsInt(),
|
|
||||||
obj.get("name").getAsString(),
|
|
||||||
obj.get("description").getAsString(),
|
|
||||||
obj.get("maxPlayers").getAsInt(),
|
|
||||||
obj.get("currentPlayers").getAsInt(),
|
|
||||||
obj.get("lastUpdatedAt").getAsLong()
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return servers;
|
|
||||||
} else {
|
|
||||||
throw new RuntimeException("Invalid response: " + resp.statusCode());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,134 +0,0 @@
|
||||||
package nl.andrewl.aos2_launcher;
|
|
||||||
|
|
||||||
import nl.andrewl.aos2_launcher.model.ProgressReporter;
|
|
||||||
import nl.andrewl.aos2_launcher.util.FileUtils;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.net.http.HttpClient;
|
|
||||||
import java.net.http.HttpRequest;
|
|
||||||
import java.net.http.HttpResponse;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.attribute.BasicFileAttributes;
|
|
||||||
import java.time.Duration;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
import java.util.function.BiPredicate;
|
|
||||||
|
|
||||||
public class SystemVersionValidator {
|
|
||||||
private static final String os = System.getProperty("os.name").trim().toLowerCase();
|
|
||||||
private static final String arch = System.getProperty("os.arch").trim().toLowerCase();
|
|
||||||
|
|
||||||
private static final boolean OS_WINDOWS = os.contains("win");
|
|
||||||
private static final boolean OS_MAC = os.contains("mac");
|
|
||||||
private static final boolean OS_LINUX = os.contains("nix") || os.contains("nux") || os.contains("aix");
|
|
||||||
|
|
||||||
private static final boolean ARCH_X86 = arch.equals("x86");
|
|
||||||
private static final boolean ARCH_X86_64 = arch.equals("x86_64");
|
|
||||||
private static final boolean ARCH_AMD64 = arch.equals("amd64");
|
|
||||||
private static final boolean ARCH_AARCH64 = arch.equals("aarch64");
|
|
||||||
private static final boolean ARCH_ARM = arch.equals("arm");
|
|
||||||
private static final boolean ARCH_ARM32 = arch.equals("arm32");
|
|
||||||
|
|
||||||
private static final String JRE_DOWNLOAD_URL = "https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.4+8/";
|
|
||||||
|
|
||||||
public static String getPreferredVersionSuffix() {
|
|
||||||
if (OS_LINUX) {
|
|
||||||
if (ARCH_AARCH64) return "linux-aarch64";
|
|
||||||
if (ARCH_AMD64) return "linux-amd64";
|
|
||||||
if (ARCH_ARM) return "linux-arm";
|
|
||||||
if (ARCH_ARM32) return "linux-arm32";
|
|
||||||
} else if (OS_MAC) {
|
|
||||||
if (ARCH_AARCH64) return "macos-aarch64";
|
|
||||||
if (ARCH_X86_64) return "macos-x86_64";
|
|
||||||
} else if (OS_WINDOWS) {
|
|
||||||
if (ARCH_AARCH64) return "windows-aarch64";
|
|
||||||
if (ARCH_AMD64) return "windows-amd64";
|
|
||||||
if (ARCH_X86) return "windows-x86";
|
|
||||||
}
|
|
||||||
System.err.println("Couldn't determine the preferred OS/ARCH version. Defaulting to windows-amd64.");
|
|
||||||
return "windows-amd64";
|
|
||||||
}
|
|
||||||
|
|
||||||
public static CompletableFuture<Path> getJreExecutablePath(ProgressReporter progressReporter) {
|
|
||||||
Optional<Path> optionalExecutablePath = findJreExecutable();
|
|
||||||
return optionalExecutablePath.map(CompletableFuture::completedFuture)
|
|
||||||
.orElseGet(() -> downloadAppropriateJre(progressReporter));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static CompletableFuture<Path> downloadAppropriateJre(ProgressReporter progressReporter) {
|
|
||||||
progressReporter.enableProgress();
|
|
||||||
progressReporter.setActionText("Downloading JRE...");
|
|
||||||
HttpClient httpClient = HttpClient.newBuilder().followRedirects(HttpClient.Redirect.NORMAL).build();
|
|
||||||
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder().GET().timeout(Duration.ofMinutes(5));
|
|
||||||
String jreArchiveName = getPreferredJreName();
|
|
||||||
String url = JRE_DOWNLOAD_URL + jreArchiveName;
|
|
||||||
HttpRequest req = requestBuilder.uri(URI.create(url)).build();
|
|
||||||
return httpClient.sendAsync(req, HttpResponse.BodyHandlers.ofInputStream())
|
|
||||||
.thenApplyAsync(resp -> {
|
|
||||||
if (resp.statusCode() == 200) {
|
|
||||||
// Download sequentially, and update the progress.
|
|
||||||
try {
|
|
||||||
if (Files.exists(Launcher.JRE_PATH)) {
|
|
||||||
FileUtils.deleteRecursive(Launcher.JRE_PATH);
|
|
||||||
}
|
|
||||||
Files.createDirectory(Launcher.JRE_PATH);
|
|
||||||
Path jreArchiveFile = Launcher.JRE_PATH.resolve(jreArchiveName);
|
|
||||||
FileUtils.downloadWithProgress(jreArchiveFile, resp, progressReporter);
|
|
||||||
progressReporter.setProgress(-1); // Indefinite progress.
|
|
||||||
progressReporter.setActionText("Unpacking JRE...");
|
|
||||||
ProcessBuilder pb = new ProcessBuilder().inheritIO();
|
|
||||||
if (OS_LINUX || OS_MAC) {
|
|
||||||
pb.command("tar", "-xzf", jreArchiveFile.toAbsolutePath().toString(), "-C", Launcher.JRE_PATH.toAbsolutePath().toString());
|
|
||||||
} else if (OS_WINDOWS) {
|
|
||||||
pb.command("powershell", "-command", "\"Expand-Archive -Force '" + jreArchiveFile.toAbsolutePath() + "' '" + Launcher.JRE_PATH.toAbsolutePath() + "'\"");
|
|
||||||
}
|
|
||||||
Process process = pb.start();
|
|
||||||
int result = process.waitFor();
|
|
||||||
if (result != 0) throw new IOException("Archive extraction process exited with non-zero code: " + result);
|
|
||||||
Files.delete(jreArchiveFile);
|
|
||||||
progressReporter.setActionText("Looking for java executable...");
|
|
||||||
Optional<Path> optionalExecutablePath = findJreExecutable();
|
|
||||||
if (optionalExecutablePath.isEmpty()) throw new IOException("Couldn't find java executable.");
|
|
||||||
progressReporter.disableProgress();
|
|
||||||
return optionalExecutablePath.get();
|
|
||||||
} catch (IOException | InterruptedException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new RuntimeException("JRE download failed: " + resp.statusCode());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Optional<Path> findJreExecutable() {
|
|
||||||
if (!Files.exists(Launcher.JRE_PATH)) return Optional.empty();
|
|
||||||
BiPredicate<Path, BasicFileAttributes> pred = (path, basicFileAttributes) -> {
|
|
||||||
String filename = path.getFileName().toString();
|
|
||||||
return Files.isExecutable(path) && (filename.equals("java") || filename.equals("java.exe"));
|
|
||||||
};
|
|
||||||
try (var s = Files.find(Launcher.JRE_PATH, 3, pred)) {
|
|
||||||
return s.findFirst();
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String getPreferredJreName() {
|
|
||||||
if (OS_LINUX) {
|
|
||||||
if (ARCH_AARCH64) return "OpenJDK17U-jre_aarch64_linux_hotspot_17.0.4_8.tar.gz";
|
|
||||||
if (ARCH_AMD64) return "OpenJDK17U-jre_x64_linux_hotspot_17.0.4_8.tar.gz";
|
|
||||||
if (ARCH_ARM || ARCH_ARM32) return "OpenJDK17U-jre_arm_linux_hotspot_17.0.4_8.tar.gz";
|
|
||||||
} else if (OS_MAC) {
|
|
||||||
if (ARCH_AARCH64) return "OpenJDK17U-jre_aarch64_mac_hotspot_17.0.4_8.tar.gz";
|
|
||||||
if (ARCH_X86_64) return "OpenJDK17U-jre_x64_mac_hotspot_17.0.4_8.tar.gz";
|
|
||||||
} else if (OS_WINDOWS) {
|
|
||||||
if (ARCH_AARCH64 || ARCH_AMD64) return "OpenJDK17U-jre_x64_windows_hotspot_17.0.4_8.zip";
|
|
||||||
if (ARCH_X86) return "OpenJDK17U-jre_x86-32_windows_hotspot_17.0.4_8.zip";
|
|
||||||
}
|
|
||||||
System.err.println("Couldn't determine the preferred JRE version. Defaulting to x64_windows.");
|
|
||||||
return "OpenJDK17U-jre_x64_windows_hotspot_17.0.4_8.zip";
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,177 +0,0 @@
|
||||||
package nl.andrewl.aos2_launcher;
|
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
|
||||||
import com.google.gson.JsonArray;
|
|
||||||
import com.google.gson.JsonObject;
|
|
||||||
import nl.andrewl.aos2_launcher.model.ClientVersionRelease;
|
|
||||||
import nl.andrewl.aos2_launcher.model.ProgressReporter;
|
|
||||||
import nl.andrewl.aos2_launcher.util.FileUtils;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.net.http.HttpClient;
|
|
||||||
import java.net.http.HttpRequest;
|
|
||||||
import java.net.http.HttpResponse;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.time.Duration;
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.time.OffsetDateTime;
|
|
||||||
import java.time.ZoneId;
|
|
||||||
import java.time.format.DateTimeFormatter;
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
public class VersionFetcher {
|
|
||||||
private static final String BASE_GITHUB_URL = "https://api.github.com/repos/andrewlalis/ace-of-shades-2";
|
|
||||||
|
|
||||||
public static final VersionFetcher INSTANCE = new VersionFetcher();
|
|
||||||
|
|
||||||
private final List<ClientVersionRelease> availableReleases;
|
|
||||||
|
|
||||||
private final HttpClient httpClient = HttpClient.newBuilder().followRedirects(HttpClient.Redirect.NORMAL).build();
|
|
||||||
private boolean loaded = false;
|
|
||||||
private CompletableFuture<List<ClientVersionRelease>> activeReleaseFetchFuture;
|
|
||||||
|
|
||||||
public VersionFetcher() {
|
|
||||||
this.availableReleases = new ArrayList<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public CompletableFuture<ClientVersionRelease> getRelease(String versionTag) {
|
|
||||||
return getAvailableReleases().thenApply(releases -> releases.stream()
|
|
||||||
.filter(r -> r.tag().equals(versionTag))
|
|
||||||
.findFirst().orElse(null));
|
|
||||||
}
|
|
||||||
|
|
||||||
public CompletableFuture<List<ClientVersionRelease>> getAvailableReleases() {
|
|
||||||
if (loaded) {
|
|
||||||
return CompletableFuture.completedFuture(Collections.unmodifiableList(availableReleases));
|
|
||||||
}
|
|
||||||
return fetchReleasesFromGitHub();
|
|
||||||
}
|
|
||||||
|
|
||||||
private CompletableFuture<List<ClientVersionRelease>> fetchReleasesFromGitHub() {
|
|
||||||
if (activeReleaseFetchFuture != null) return activeReleaseFetchFuture;
|
|
||||||
HttpRequest req = HttpRequest.newBuilder(URI.create(BASE_GITHUB_URL + "/releases"))
|
|
||||||
.timeout(Duration.ofSeconds(3))
|
|
||||||
.GET()
|
|
||||||
.build();
|
|
||||||
activeReleaseFetchFuture = httpClient.sendAsync(req, HttpResponse.BodyHandlers.ofInputStream())
|
|
||||||
.thenApplyAsync(resp -> {
|
|
||||||
if (resp.statusCode() == 200) {
|
|
||||||
JsonArray releasesArray = new Gson().fromJson(new InputStreamReader(resp.body()), JsonArray.class);
|
|
||||||
availableReleases.clear();
|
|
||||||
for (var element : releasesArray) {
|
|
||||||
if (element.isJsonObject()) {
|
|
||||||
JsonObject obj = element.getAsJsonObject();
|
|
||||||
String tag = obj.get("tag_name").getAsString();
|
|
||||||
String apiUrl = obj.get("url").getAsString();
|
|
||||||
String assetsUrl = obj.get("assets_url").getAsString();
|
|
||||||
OffsetDateTime publishedAt = OffsetDateTime.parse(obj.get("published_at").getAsString(), DateTimeFormatter.ISO_OFFSET_DATE_TIME);
|
|
||||||
LocalDateTime localPublishedAt = publishedAt.atZoneSameInstant(ZoneId.systemDefault()).toLocalDateTime();
|
|
||||||
availableReleases.add(new ClientVersionRelease(tag, apiUrl, assetsUrl, localPublishedAt));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
availableReleases.sort(Comparator.comparing(ClientVersionRelease::publishedAt).reversed());
|
|
||||||
loaded = true;
|
|
||||||
return availableReleases;
|
|
||||||
} else {
|
|
||||||
throw new RuntimeException("Error while requesting releases.");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return activeReleaseFetchFuture;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<String> getDownloadedVersions() {
|
|
||||||
try (var s = Files.list(Launcher.VERSIONS_DIR)) {
|
|
||||||
return s.filter(this::isVersionFile)
|
|
||||||
.map(this::extractVersion)
|
|
||||||
.toList();
|
|
||||||
} catch (IOException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public CompletableFuture<Path> ensureVersionIsDownloaded(String versionTag, ProgressReporter progressReporter) {
|
|
||||||
try (var s = Files.list(Launcher.VERSIONS_DIR)) {
|
|
||||||
Optional<Path> optionalFile = s.filter(f -> isVersionFile(f) && versionTag.equals(extractVersion(f)))
|
|
||||||
.findFirst();
|
|
||||||
if (optionalFile.isPresent()) return CompletableFuture.completedFuture(optionalFile.get());
|
|
||||||
} catch (IOException e) {
|
|
||||||
return CompletableFuture.failedFuture(e);
|
|
||||||
}
|
|
||||||
progressReporter.enableProgress();
|
|
||||||
progressReporter.setActionText("Downloading client " + versionTag + "...");
|
|
||||||
var future = getRelease(versionTag)
|
|
||||||
.thenComposeAsync(release -> downloadVersion(release, progressReporter));
|
|
||||||
future.thenRun(progressReporter::disableProgress);
|
|
||||||
return future;
|
|
||||||
}
|
|
||||||
|
|
||||||
private CompletableFuture<Path> downloadVersion(ClientVersionRelease release, ProgressReporter progressReporter) {
|
|
||||||
System.out.println("Downloading version " + release.tag());
|
|
||||||
HttpRequest req = HttpRequest.newBuilder(URI.create(release.assetsUrl()))
|
|
||||||
.GET().timeout(Duration.ofSeconds(3)).build();
|
|
||||||
CompletableFuture<JsonObject> downloadUrlFuture = httpClient.sendAsync(req, HttpResponse.BodyHandlers.ofInputStream())
|
|
||||||
.thenApplyAsync(resp -> {
|
|
||||||
if (resp.statusCode() == 200) {
|
|
||||||
JsonArray assetsArray = new Gson().fromJson(new InputStreamReader(resp.body()), JsonArray.class);
|
|
||||||
String preferredVersionSuffix = SystemVersionValidator.getPreferredVersionSuffix();
|
|
||||||
String regex = "aos2-client-\\d+\\.\\d+\\.\\d+-" + preferredVersionSuffix + "\\.jar";
|
|
||||||
for (var asset : assetsArray) {
|
|
||||||
JsonObject assetObj = asset.getAsJsonObject();
|
|
||||||
String name = assetObj.get("name").getAsString();
|
|
||||||
if (name.matches(regex)) {
|
|
||||||
return assetObj;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new RuntimeException("Couldn't find a matching release asset for this system.");
|
|
||||||
} else {
|
|
||||||
throw new RuntimeException("Error while requesting release assets from GitHub: " + resp.statusCode());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return downloadUrlFuture.thenComposeAsync(asset -> {
|
|
||||||
String url = asset.get("browser_download_url").getAsString();
|
|
||||||
String fileName = asset.get("name").getAsString();
|
|
||||||
HttpRequest downloadRequest = HttpRequest.newBuilder(URI.create(url))
|
|
||||||
.GET().timeout(Duration.ofMinutes(5)).build();
|
|
||||||
Path file = Launcher.VERSIONS_DIR.resolve(fileName);
|
|
||||||
return httpClient.sendAsync(downloadRequest, HttpResponse.BodyHandlers.ofInputStream())
|
|
||||||
.thenApplyAsync(resp -> {
|
|
||||||
if (resp.statusCode() == 200) {
|
|
||||||
// Download sequentially, and update the progress.
|
|
||||||
try {
|
|
||||||
FileUtils.downloadWithProgress(file, resp, progressReporter);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
return file;
|
|
||||||
} else {
|
|
||||||
throw new RuntimeException("Error while downloading release asset from GitHub: " + resp.statusCode());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isVersionDownloaded(String versionTag) {
|
|
||||||
return getDownloadedVersions().contains(versionTag);
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isVersionFile(Path p) {
|
|
||||||
return Files.isRegularFile(p) && p.getFileName().toString()
|
|
||||||
.matches("aos2-client-\\d+\\.\\d+\\.\\d+-.+\\.jar");
|
|
||||||
}
|
|
||||||
|
|
||||||
private String extractVersion(Path file) {
|
|
||||||
Pattern pattern = Pattern.compile("\\d+\\.\\d+\\.\\d+");
|
|
||||||
Matcher matcher = pattern.matcher(file.getFileName().toString());
|
|
||||||
if (matcher.find()) {
|
|
||||||
return "v" + matcher.group();
|
|
||||||
}
|
|
||||||
throw new IllegalArgumentException("File doesn't contain a valid version pattern.");
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
package nl.andrewl.aos2_launcher.model;
|
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.time.ZonedDateTime;
|
|
||||||
|
|
||||||
public record ClientVersionRelease (
|
|
||||||
String tag,
|
|
||||||
String apiUrl,
|
|
||||||
String assetsUrl,
|
|
||||||
LocalDateTime publishedAt
|
|
||||||
) {}
|
|
|
@ -1,84 +0,0 @@
|
||||||
package nl.andrewl.aos2_launcher.model;
|
|
||||||
|
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
|
||||||
import javafx.beans.property.StringProperty;
|
|
||||||
import nl.andrewl.aos2_launcher.Launcher;
|
|
||||||
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
public class Profile {
|
|
||||||
private final UUID id;
|
|
||||||
private final StringProperty name;
|
|
||||||
private final StringProperty username;
|
|
||||||
private final StringProperty clientVersion;
|
|
||||||
private final StringProperty jvmArgs;
|
|
||||||
|
|
||||||
public Profile() {
|
|
||||||
this(UUID.randomUUID(), "", "Player", null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Profile(UUID id, String name, String username, String clientVersion, String jvmArgs) {
|
|
||||||
this.id = id;
|
|
||||||
this.name = new SimpleStringProperty(name);
|
|
||||||
this.username = new SimpleStringProperty(username);
|
|
||||||
this.clientVersion = new SimpleStringProperty(clientVersion);
|
|
||||||
this.jvmArgs = new SimpleStringProperty(jvmArgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
public UUID getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return name.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
public StringProperty nameProperty() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getUsername() {
|
|
||||||
return username.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
public StringProperty usernameProperty() {
|
|
||||||
return username;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getClientVersion() {
|
|
||||||
return clientVersion.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
public StringProperty clientVersionProperty() {
|
|
||||||
return clientVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getJvmArgs() {
|
|
||||||
return jvmArgs.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
public StringProperty jvmArgsProperty() {
|
|
||||||
return jvmArgs;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setName(String name) {
|
|
||||||
this.name.set(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setUsername(String username) {
|
|
||||||
this.username.set(username);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setClientVersion(String clientVersion) {
|
|
||||||
this.clientVersion.set(clientVersion);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setJvmArgs(String jvmArgs) {
|
|
||||||
this.jvmArgs.set(jvmArgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Path getDir() {
|
|
||||||
return Launcher.PROFILES_DIR.resolve(id.toString());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,155 +0,0 @@
|
||||||
package nl.andrewl.aos2_launcher.model;
|
|
||||||
|
|
||||||
import com.google.gson.*;
|
|
||||||
import javafx.beans.property.ObjectProperty;
|
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
|
||||||
import javafx.collections.FXCollections;
|
|
||||||
import javafx.collections.ObservableList;
|
|
||||||
import nl.andrewl.aos2_launcher.Launcher;
|
|
||||||
import nl.andrewl.aos2_launcher.util.FileUtils;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Model for managing the set of profiles in the app.
|
|
||||||
*/
|
|
||||||
public class ProfileSet {
|
|
||||||
private final ObservableList<Profile> profiles;
|
|
||||||
private final ObjectProperty<Profile> selectedProfile;
|
|
||||||
private Path lastFileUsed = null;
|
|
||||||
|
|
||||||
public ProfileSet() {
|
|
||||||
this.profiles = FXCollections.observableArrayList();
|
|
||||||
this.selectedProfile = new SimpleObjectProperty<>(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ProfileSet(Path file) throws IOException {
|
|
||||||
this();
|
|
||||||
load(file);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addNewProfile(Profile profile) {
|
|
||||||
profiles.add(profile);
|
|
||||||
save();
|
|
||||||
try {
|
|
||||||
if (!Files.exists(profile.getDir())) {
|
|
||||||
Files.createDirectory(profile.getDir());
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeProfile(Profile profile) {
|
|
||||||
if (profile == null) return;
|
|
||||||
boolean removed = profiles.remove(profile);
|
|
||||||
if (removed) {
|
|
||||||
try {
|
|
||||||
if (Files.exists(profile.getDir())) {
|
|
||||||
FileUtils.deleteRecursive(profile.getDir());
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
save();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeSelectedProfile() {
|
|
||||||
removeProfile(getSelectedProfile());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void load(Path file) throws IOException {
|
|
||||||
try (var reader = Files.newBufferedReader(file)) {
|
|
||||||
JsonObject data = new Gson().fromJson(reader, JsonObject.class);
|
|
||||||
profiles.clear();
|
|
||||||
JsonElement selectedProfileIdElement = data.get("selectedProfileId");
|
|
||||||
UUID selectedProfileId = (selectedProfileIdElement == null || selectedProfileIdElement.isJsonNull())
|
|
||||||
? null
|
|
||||||
: UUID.fromString(selectedProfileIdElement.getAsString());
|
|
||||||
JsonArray profilesArray = data.getAsJsonArray("profiles");
|
|
||||||
for (JsonElement element : profilesArray) {
|
|
||||||
JsonObject profileObj = element.getAsJsonObject();
|
|
||||||
UUID id = UUID.fromString(profileObj.get("id").getAsString());
|
|
||||||
String name = profileObj.get("name").getAsString();
|
|
||||||
String clientVersion = profileObj.get("clientVersion").getAsString();
|
|
||||||
String username = profileObj.get("username").getAsString();
|
|
||||||
JsonElement jvmArgsElement = profileObj.get("jvmArgs");
|
|
||||||
String jvmArgs = null;
|
|
||||||
if (jvmArgsElement != null && jvmArgsElement.isJsonPrimitive() && jvmArgsElement.getAsJsonPrimitive().isString()) {
|
|
||||||
jvmArgs = jvmArgsElement.getAsString();
|
|
||||||
}
|
|
||||||
Profile profile = new Profile(id, name, username, clientVersion, jvmArgs);
|
|
||||||
profiles.add(profile);
|
|
||||||
if (selectedProfileId != null && selectedProfileId.equals(profile.getId())) {
|
|
||||||
selectedProfile.set(profile);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lastFileUsed = file;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void loadOrCreateStandardFile() {
|
|
||||||
if (!Files.exists(Launcher.PROFILES_FILE)) {
|
|
||||||
try {
|
|
||||||
save(Launcher.PROFILES_FILE);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
load(Launcher.PROFILES_FILE);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void save(Path file) throws IOException {
|
|
||||||
Gson gson = new GsonBuilder().setPrettyPrinting().create();
|
|
||||||
JsonObject data = new JsonObject();
|
|
||||||
String selectedProfileId = selectedProfile.getValue() == null ? null : selectedProfile.getValue().getId().toString();
|
|
||||||
data.addProperty("selectedProfileId", selectedProfileId);
|
|
||||||
JsonArray profilesArray = new JsonArray(profiles.size());
|
|
||||||
for (Profile profile : profiles) {
|
|
||||||
JsonObject obj = new JsonObject();
|
|
||||||
obj.addProperty("id", profile.getId().toString());
|
|
||||||
obj.addProperty("name", profile.getName());
|
|
||||||
obj.addProperty("username", profile.getUsername());
|
|
||||||
obj.addProperty("clientVersion", profile.getClientVersion());
|
|
||||||
obj.addProperty("jvmArgs", profile.getJvmArgs());
|
|
||||||
profilesArray.add(obj);
|
|
||||||
}
|
|
||||||
data.add("profiles", profilesArray);
|
|
||||||
try (var writer = Files.newBufferedWriter(file)) {
|
|
||||||
gson.toJson(data, writer);
|
|
||||||
}
|
|
||||||
lastFileUsed = file;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void save() {
|
|
||||||
if (lastFileUsed != null) {
|
|
||||||
try {
|
|
||||||
save(lastFileUsed);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ObservableList<Profile> getProfiles() {
|
|
||||||
return profiles;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Profile getSelectedProfile() {
|
|
||||||
return selectedProfile.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ObjectProperty<Profile> selectedProfileProperty() {
|
|
||||||
return selectedProfile;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
package nl.andrewl.aos2_launcher.model;
|
|
||||||
|
|
||||||
public interface ProgressReporter {
|
|
||||||
void enableProgress();
|
|
||||||
void disableProgress();
|
|
||||||
void setActionText(String text);
|
|
||||||
void setProgress(double progress);
|
|
||||||
}
|
|
|
@ -1,84 +0,0 @@
|
||||||
package nl.andrewl.aos2_launcher.model;
|
|
||||||
|
|
||||||
import javafx.beans.property.*;
|
|
||||||
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.time.ZoneId;
|
|
||||||
|
|
||||||
public class Server {
|
|
||||||
private final StringProperty host;
|
|
||||||
private final IntegerProperty port;
|
|
||||||
private final StringProperty name;
|
|
||||||
private final StringProperty description;
|
|
||||||
private final IntegerProperty maxPlayers;
|
|
||||||
private final IntegerProperty currentPlayers;
|
|
||||||
private final ObjectProperty<LocalDateTime> lastUpdatedAt;
|
|
||||||
|
|
||||||
public Server(String host, int port, String name, String description, int maxPlayers, int currentPlayers, long lastUpdatedAt) {
|
|
||||||
this.host = new SimpleStringProperty(host);
|
|
||||||
this.port = new SimpleIntegerProperty(port);
|
|
||||||
this.name = new SimpleStringProperty(name);
|
|
||||||
this.description = new SimpleStringProperty(description);
|
|
||||||
this.maxPlayers = new SimpleIntegerProperty(maxPlayers);
|
|
||||||
this.currentPlayers = new SimpleIntegerProperty(currentPlayers);
|
|
||||||
LocalDateTime ts = Instant.ofEpochMilli(lastUpdatedAt).atZone(ZoneId.systemDefault()).toLocalDateTime();
|
|
||||||
this.lastUpdatedAt = new SimpleObjectProperty<>(ts);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getHost() {
|
|
||||||
return host.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
public StringProperty hostProperty() {
|
|
||||||
return host;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getPort() {
|
|
||||||
return port.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
public IntegerProperty portProperty() {
|
|
||||||
return port;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return name.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
public StringProperty nameProperty() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getDescription() {
|
|
||||||
return description.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
public StringProperty descriptionProperty() {
|
|
||||||
return description;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getMaxPlayers() {
|
|
||||||
return maxPlayers.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
public IntegerProperty maxPlayersProperty() {
|
|
||||||
return maxPlayers;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getCurrentPlayers() {
|
|
||||||
return currentPlayers.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
public IntegerProperty currentPlayersProperty() {
|
|
||||||
return currentPlayers;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LocalDateTime getLastUpdatedAt() {
|
|
||||||
return lastUpdatedAt.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Property<LocalDateTime> lastUpdatedAtProperty() {
|
|
||||||
return lastUpdatedAt;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,74 +0,0 @@
|
||||||
package nl.andrewl.aos2_launcher.util;
|
|
||||||
|
|
||||||
import nl.andrewl.aos2_launcher.model.ProgressReporter;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.net.http.HttpResponse;
|
|
||||||
import java.nio.file.FileVisitResult;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.SimpleFileVisitor;
|
|
||||||
import java.nio.file.attribute.BasicFileAttributes;
|
|
||||||
import java.text.CharacterIterator;
|
|
||||||
import java.text.StringCharacterIterator;
|
|
||||||
|
|
||||||
public class FileUtils {
|
|
||||||
public static String humanReadableByteCountSI(long bytes) {
|
|
||||||
if (-1000 < bytes && bytes < 1000) {
|
|
||||||
return bytes + " B";
|
|
||||||
}
|
|
||||||
CharacterIterator ci = new StringCharacterIterator("kMGTPE");
|
|
||||||
while (bytes <= -999_950 || bytes >= 999_950) {
|
|
||||||
bytes /= 1000;
|
|
||||||
ci.next();
|
|
||||||
}
|
|
||||||
return String.format("%.1f %cB", bytes / 1000.0, ci.current());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String humanReadableByteCountBin(long bytes) {
|
|
||||||
long absB = bytes == Long.MIN_VALUE ? Long.MAX_VALUE : Math.abs(bytes);
|
|
||||||
if (absB < 1024) {
|
|
||||||
return bytes + " B";
|
|
||||||
}
|
|
||||||
long value = absB;
|
|
||||||
CharacterIterator ci = new StringCharacterIterator("KMGTPE");
|
|
||||||
for (int i = 40; i >= 0 && absB > 0xfffccccccccccccL >> i; i -= 10) {
|
|
||||||
value >>= 10;
|
|
||||||
ci.next();
|
|
||||||
}
|
|
||||||
value *= Long.signum(bytes);
|
|
||||||
return String.format("%.1f %ciB", value / 1024.0, ci.current());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void deleteRecursive(Path p) throws IOException {
|
|
||||||
Files.walkFileTree(p, new SimpleFileVisitor<>() {
|
|
||||||
@Override
|
|
||||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
|
||||||
Files.delete(file);
|
|
||||||
return FileVisitResult.CONTINUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
|
|
||||||
Files.delete(dir);
|
|
||||||
return FileVisitResult.CONTINUE;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void downloadWithProgress(Path outputFile, HttpResponse<InputStream> resp, ProgressReporter reporter) throws IOException {
|
|
||||||
reporter.setProgress(0);
|
|
||||||
long size = resp.headers().firstValueAsLong("Content-Length").orElse(1);
|
|
||||||
try (var out = Files.newOutputStream(outputFile); var in = resp.body()) {
|
|
||||||
byte[] buffer = new byte[8192];
|
|
||||||
long bytesRead = 0;
|
|
||||||
while (bytesRead < size) {
|
|
||||||
int readCount = in.read(buffer);
|
|
||||||
out.write(buffer, 0, readCount);
|
|
||||||
bytesRead += readCount;
|
|
||||||
reporter.setProgress((double) bytesRead / size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,90 +0,0 @@
|
||||||
package nl.andrewl.aos2_launcher.view;
|
|
||||||
|
|
||||||
import javafx.beans.WeakListener;
|
|
||||||
import javafx.collections.ListChangeListener;
|
|
||||||
import javafx.collections.ObservableList;
|
|
||||||
|
|
||||||
import java.lang.ref.WeakReference;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
import static java.util.stream.Collectors.toList;
|
|
||||||
|
|
||||||
public class BindingUtil {
|
|
||||||
public static <E, F> void mapContent(ObservableList<F> mapped, ObservableList<? extends E> source,
|
|
||||||
Function<? super E, ? extends F> mapper) {
|
|
||||||
map(mapped, source, mapper);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static <E, F> Object map(ObservableList<F> mapped, ObservableList<? extends E> source,
|
|
||||||
Function<? super E, ? extends F> mapper) {
|
|
||||||
final ListContentMapping<E, F> contentMapping = new ListContentMapping<>(mapped, mapper);
|
|
||||||
mapped.setAll(source.stream().map(mapper).collect(toList()));
|
|
||||||
source.removeListener(contentMapping);
|
|
||||||
source.addListener(contentMapping);
|
|
||||||
return contentMapping;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class ListContentMapping<E, F> implements ListChangeListener<E>, WeakListener {
|
|
||||||
private final WeakReference<List<F>> mappedRef;
|
|
||||||
private final Function<? super E, ? extends F> mapper;
|
|
||||||
|
|
||||||
public ListContentMapping(List<F> mapped, Function<? super E, ? extends F> mapper) {
|
|
||||||
this.mappedRef = new WeakReference<>(mapped);
|
|
||||||
this.mapper = mapper;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onChanged(Change<? extends E> change) {
|
|
||||||
final List<F> mapped = mappedRef.get();
|
|
||||||
if (mapped == null) {
|
|
||||||
change.getList().removeListener(this);
|
|
||||||
} else {
|
|
||||||
while (change.next()) {
|
|
||||||
if (change.wasPermutated()) {
|
|
||||||
mapped.subList(change.getFrom(), change.getTo()).clear();
|
|
||||||
mapped.addAll(change.getFrom(), change.getList().subList(change.getFrom(), change.getTo())
|
|
||||||
.stream().map(mapper).toList());
|
|
||||||
} else {
|
|
||||||
if (change.wasRemoved()) {
|
|
||||||
mapped.subList(change.getFrom(), change.getFrom() + change.getRemovedSize()).clear();
|
|
||||||
}
|
|
||||||
if (change.wasAdded()) {
|
|
||||||
mapped.addAll(change.getFrom(), change.getAddedSubList()
|
|
||||||
.stream().map(mapper).toList());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean wasGarbageCollected() {
|
|
||||||
return mappedRef.get() == null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
final List<F> list = mappedRef.get();
|
|
||||||
return (list == null) ? 0 : list.hashCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
if (this == obj) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
final List<F> mapped1 = mappedRef.get();
|
|
||||||
if (mapped1 == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (obj instanceof final ListContentMapping<?, ?> other) {
|
|
||||||
final List<?> mapped2 = other.mappedRef.get();
|
|
||||||
return mapped1 == mapped2;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,96 +0,0 @@
|
||||||
package nl.andrewl.aos2_launcher.view;
|
|
||||||
|
|
||||||
import javafx.application.Platform;
|
|
||||||
import javafx.beans.binding.BooleanBinding;
|
|
||||||
import javafx.beans.property.ObjectProperty;
|
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
|
||||||
import javafx.collections.FXCollections;
|
|
||||||
import javafx.fxml.FXML;
|
|
||||||
import javafx.fxml.FXMLLoader;
|
|
||||||
import javafx.scene.Parent;
|
|
||||||
import javafx.scene.control.*;
|
|
||||||
import javafx.stage.Modality;
|
|
||||||
import javafx.stage.Window;
|
|
||||||
import nl.andrewl.aos2_launcher.VersionFetcher;
|
|
||||||
import nl.andrewl.aos2_launcher.model.ClientVersionRelease;
|
|
||||||
import nl.andrewl.aos2_launcher.model.Profile;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public class EditProfileDialog extends Dialog<Profile> {
|
|
||||||
@FXML public TextField nameField;
|
|
||||||
@FXML public TextField usernameField;
|
|
||||||
@FXML public ChoiceBox<String> clientVersionChoiceBox;
|
|
||||||
@FXML public TextArea jvmArgsTextArea;
|
|
||||||
|
|
||||||
private final ObjectProperty<Profile> profile;
|
|
||||||
|
|
||||||
public EditProfileDialog(Window owner, Profile profile) {
|
|
||||||
this.profile = new SimpleObjectProperty<>(profile);
|
|
||||||
try {
|
|
||||||
FXMLLoader loader = new FXMLLoader(EditProfileDialog.class.getResource("/dialog/edit_profile.fxml"));
|
|
||||||
loader.setController(this);
|
|
||||||
Parent parent = loader.load();
|
|
||||||
initOwner(owner);
|
|
||||||
initModality(Modality.APPLICATION_MODAL);
|
|
||||||
setResizable(true);
|
|
||||||
setTitle("Edit Profile");
|
|
||||||
|
|
||||||
BooleanBinding formInvalid = nameField.textProperty().isEmpty()
|
|
||||||
.or(clientVersionChoiceBox.valueProperty().isNull())
|
|
||||||
.or(usernameField.textProperty().isEmpty());
|
|
||||||
nameField.setText(profile.getName());
|
|
||||||
usernameField.setText(profile.getUsername());
|
|
||||||
VersionFetcher.INSTANCE.getAvailableReleases()
|
|
||||||
.whenComplete((releases, throwable) -> Platform.runLater(() -> {
|
|
||||||
if (throwable == null) {
|
|
||||||
clientVersionChoiceBox.setItems(FXCollections.observableArrayList(releases.stream().map(ClientVersionRelease::tag).toList()));
|
|
||||||
// If the profile doesn't have a set version, use the latest release.
|
|
||||||
if (profile.getClientVersion() == null || profile.getClientVersion().isBlank()) {
|
|
||||||
String lastRelease = releases.size() == 0 ? null : releases.get(0).tag();
|
|
||||||
if (lastRelease != null) {
|
|
||||||
clientVersionChoiceBox.setValue(lastRelease);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
clientVersionChoiceBox.setValue(profile.getClientVersion());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throwable.printStackTrace();
|
|
||||||
Alert alert = new Alert(Alert.AlertType.ERROR);
|
|
||||||
alert.initOwner(this.getOwner());
|
|
||||||
alert.setContentText("An error occurred while fetching the latest game releases: " + throwable.getMessage());
|
|
||||||
alert.show();
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
jvmArgsTextArea.setText(profile.getJvmArgs());
|
|
||||||
|
|
||||||
DialogPane pane = new DialogPane();
|
|
||||||
pane.setContent(parent);
|
|
||||||
ButtonType okButton = new ButtonType("Ok", ButtonBar.ButtonData.OK_DONE);
|
|
||||||
ButtonType cancelButton = new ButtonType("Cancel", ButtonBar.ButtonData.CANCEL_CLOSE);
|
|
||||||
pane.getButtonTypes().add(okButton);
|
|
||||||
pane.getButtonTypes().add(cancelButton);
|
|
||||||
pane.lookupButton(okButton).disableProperty().bind(formInvalid);
|
|
||||||
setDialogPane(pane);
|
|
||||||
setResultConverter(buttonType -> {
|
|
||||||
if (!Objects.equals(ButtonBar.ButtonData.OK_DONE, buttonType.getButtonData())) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
var prof = this.profile.getValue();
|
|
||||||
prof.setName(nameField.getText().trim());
|
|
||||||
prof.setUsername(usernameField.getText().trim());
|
|
||||||
prof.setClientVersion(clientVersionChoiceBox.getValue());
|
|
||||||
prof.setJvmArgs(jvmArgsTextArea.getText());
|
|
||||||
return this.profile.getValue();
|
|
||||||
});
|
|
||||||
setOnShowing(event -> Platform.runLater(() -> nameField.requestFocus()));
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public EditProfileDialog(Window owner) {
|
|
||||||
this(owner, new Profile());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,111 +0,0 @@
|
||||||
package nl.andrewl.aos2_launcher.view;
|
|
||||||
|
|
||||||
import javafx.beans.property.ObjectProperty;
|
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
|
||||||
import javafx.collections.FXCollections;
|
|
||||||
import javafx.collections.ObservableList;
|
|
||||||
import javafx.css.PseudoClass;
|
|
||||||
import javafx.scene.Node;
|
|
||||||
import javafx.scene.input.MouseEvent;
|
|
||||||
import javafx.scene.layout.Pane;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
public class ElementList<T, V extends Node> {
|
|
||||||
private final Pane container;
|
|
||||||
|
|
||||||
private final ObjectProperty<T> selectedElement = new SimpleObjectProperty<>(null);
|
|
||||||
private final ObservableList<T> elements = FXCollections.observableArrayList();
|
|
||||||
private final Class<V> elementViewType;
|
|
||||||
private final Function<V, T> viewElementMapper;
|
|
||||||
|
|
||||||
public ElementList(
|
|
||||||
Pane container,
|
|
||||||
Function<T, V> elementViewMapper,
|
|
||||||
Class<V> elementViewType,
|
|
||||||
Function<V, T> viewElementMapper
|
|
||||||
) {
|
|
||||||
this.container = container;
|
|
||||||
this.elementViewType = elementViewType;
|
|
||||||
this.viewElementMapper = viewElementMapper;
|
|
||||||
BindingUtil.mapContent(container.getChildren(), elements, element -> {
|
|
||||||
V view = elementViewMapper.apply(element);
|
|
||||||
view.getStyleClass().add("element-list-item");
|
|
||||||
return view;
|
|
||||||
});
|
|
||||||
container.addEventHandler(MouseEvent.MOUSE_CLICKED, this::handleMouseClick);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private void handleMouseClick(MouseEvent event) {
|
|
||||||
Node target = (Node) event.getTarget();
|
|
||||||
while (target != null) {
|
|
||||||
if (target.getClass().equals(elementViewType)) {
|
|
||||||
V elementView = (V) target;
|
|
||||||
T targetElement = viewElementMapper.apply(elementView);
|
|
||||||
if (event.isControlDown()) {
|
|
||||||
if (selectedElement.get() == null) {
|
|
||||||
selectElement(targetElement);
|
|
||||||
} else {
|
|
||||||
selectElement(null);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
selectElement(targetElement);
|
|
||||||
}
|
|
||||||
return; // Exit since we found a valid target.
|
|
||||||
}
|
|
||||||
target = target.getParent();
|
|
||||||
}
|
|
||||||
selectElement(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void selectElement(T element) {
|
|
||||||
if (element != null && !elements.contains(element)) return;
|
|
||||||
selectedElement.set(element);
|
|
||||||
updateSelectedPseudoClass();
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private void updateSelectedPseudoClass() {
|
|
||||||
PseudoClass selectedClass = PseudoClass.getPseudoClass("selected");
|
|
||||||
for (var node : container.getChildren()) {
|
|
||||||
if (!node.getClass().equals(elementViewType)) continue;
|
|
||||||
V view = (V) node;
|
|
||||||
T thisElement = viewElementMapper.apply(view);
|
|
||||||
view.pseudoClassStateChanged(selectedClass, thisElement.equals(selectedElement.get()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public T getSelectedElement() {
|
|
||||||
return selectedElement.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ObjectProperty<T> selectedElementProperty() {
|
|
||||||
return selectedElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ObservableList<T> getElements() {
|
|
||||||
return elements;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void clear() {
|
|
||||||
elements.clear();
|
|
||||||
selectElement(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void add(T element) {
|
|
||||||
elements.add(element);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addAll(Collection<T> newElements) {
|
|
||||||
elements.addAll(newElements);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void remove(T element) {
|
|
||||||
elements.remove(element);
|
|
||||||
if (element != null && element.equals(selectedElement.get())) {
|
|
||||||
selectElement(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,38 +0,0 @@
|
||||||
package nl.andrewl.aos2_launcher.view;
|
|
||||||
|
|
||||||
import javafx.fxml.FXML;
|
|
||||||
import javafx.fxml.FXMLLoader;
|
|
||||||
import javafx.scene.Node;
|
|
||||||
import javafx.scene.control.Label;
|
|
||||||
import javafx.scene.layout.Pane;
|
|
||||||
import nl.andrewl.aos2_launcher.model.Profile;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
public class ProfileView extends Pane {
|
|
||||||
private final Profile profile;
|
|
||||||
|
|
||||||
@FXML public Label nameLabel;
|
|
||||||
@FXML public Label clientVersionLabel;
|
|
||||||
@FXML public Label usernameLabel;
|
|
||||||
|
|
||||||
public ProfileView(Profile profile) {
|
|
||||||
this.profile = profile;
|
|
||||||
|
|
||||||
try {
|
|
||||||
FXMLLoader loader = new FXMLLoader(ProfileView.class.getResource("/profile_view.fxml"));
|
|
||||||
loader.setController(this);
|
|
||||||
Node node = loader.load();
|
|
||||||
getChildren().add(node);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
nameLabel.textProperty().bind(profile.nameProperty());
|
|
||||||
clientVersionLabel.textProperty().bind(profile.clientVersionProperty());
|
|
||||||
usernameLabel.textProperty().bind(profile.usernameProperty());
|
|
||||||
}
|
|
||||||
|
|
||||||
public Profile getProfile() {
|
|
||||||
return this.profile;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
package nl.andrewl.aos2_launcher.view;
|
|
||||||
|
|
||||||
import javafx.scene.control.Label;
|
|
||||||
import javafx.scene.layout.VBox;
|
|
||||||
import nl.andrewl.aos2_launcher.model.Server;
|
|
||||||
|
|
||||||
public class ServerView extends VBox {
|
|
||||||
private final Server server;
|
|
||||||
|
|
||||||
public ServerView(Server server) {
|
|
||||||
this.server = server;
|
|
||||||
var hostLabel = new Label();
|
|
||||||
hostLabel.textProperty().bind(server.hostProperty());
|
|
||||||
var portLabel = new Label();
|
|
||||||
portLabel.setText(Integer.toString(server.getPort()));
|
|
||||||
server.portProperty().addListener((observableValue, x1, x2) -> {
|
|
||||||
portLabel.setText(x2.toString());
|
|
||||||
});
|
|
||||||
var nameLabel = new Label();
|
|
||||||
nameLabel.textProperty().bind(server.nameProperty());
|
|
||||||
var descriptionLabel = new Label();
|
|
||||||
descriptionLabel.textProperty().bind(server.descriptionProperty());
|
|
||||||
var playersLabel = new Label();
|
|
||||||
|
|
||||||
var nodes = getChildren();
|
|
||||||
nodes.addAll(hostLabel, portLabel, nameLabel, descriptionLabel);
|
|
||||||
getStyleClass().add("list-item");
|
|
||||||
}
|
|
||||||
|
|
||||||
public Server getServer() {
|
|
||||||
return server;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
|
|
||||||
<?import javafx.geometry.*?>
|
|
||||||
<?import javafx.scene.control.*?>
|
|
||||||
<?import javafx.scene.layout.*?>
|
|
||||||
|
|
||||||
<AnchorPane minHeight="-Infinity" minWidth="-Infinity" prefHeight="300.0" prefWidth="400.0" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1">
|
|
||||||
<VBox maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" spacing="10.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
|
|
||||||
<padding>
|
|
||||||
<Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
|
|
||||||
</padding>
|
|
||||||
<AnchorPane VBox.vgrow="NEVER">
|
|
||||||
<Label text="Profile Name" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="0.0" />
|
|
||||||
<TextField fx:id="nameField" promptText="Enter a name for the profile..." AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="150.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
|
|
||||||
</AnchorPane>
|
|
||||||
<AnchorPane VBox.vgrow="NEVER">
|
|
||||||
<Label text="Username" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="0.0" />
|
|
||||||
<TextField fx:id="usernameField" promptText="Enter a username..." AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="150.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
|
|
||||||
</AnchorPane>
|
|
||||||
<AnchorPane VBox.vgrow="NEVER">
|
|
||||||
<Label text="Client Version" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="0.0" />
|
|
||||||
<ChoiceBox fx:id="clientVersionChoiceBox" prefWidth="150.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="150.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
|
|
||||||
</AnchorPane>
|
|
||||||
<AnchorPane>
|
|
||||||
<Label text="JVM Arguments" wrapText="true" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="0.0"/>
|
|
||||||
<TextArea fx:id="jvmArgsTextArea" prefHeight="100.0" prefWidth="200.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="150.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"/>
|
|
||||||
</AnchorPane>
|
|
||||||
</VBox>
|
|
||||||
</AnchorPane>
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,27 +0,0 @@
|
||||||
@font-face {
|
|
||||||
src: url('JetBrainsMono-Regular.ttf');
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
src: url('JetBrainsMono-Bold.ttf');
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
src: url('JetBrainsMono-Light.ttf');
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
src: url('JetBrainsMono-Italic.ttf');
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
src: url('JetBrainsMono-BoldItalic.ttf');
|
|
||||||
}
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
src: url('JetBrainsMono-LightItalic.ttf');
|
|
||||||
}
|
|
||||||
|
|
||||||
.root {
|
|
||||||
-fx-font-family: "JetBrains Mono";
|
|
||||||
}
|
|
|
@ -1,50 +1,24 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
<?import javafx.geometry.*?>
|
|
||||||
<?import javafx.scene.control.*?>
|
<?import javafx.scene.control.*?>
|
||||||
<?import javafx.scene.layout.*?>
|
<?import javafx.scene.layout.*?>
|
||||||
<?import javafx.scene.text.*?>
|
|
||||||
|
|
||||||
<VBox minHeight="300.0" minWidth="300.0" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/17.0.2-ea" xmlns:fx="http://javafx.com/fxml/1" fx:controller="nl.andrewl.aos2_launcher.MainViewController">
|
<VBox xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/16"
|
||||||
<TabPane tabClosingPolicy="UNAVAILABLE" VBox.vgrow="ALWAYS">
|
maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0"
|
||||||
<Tab text="Profiles">
|
fx:controller="nl.andrewl.aos2_launcher.MainViewController"
|
||||||
<VBox>
|
>
|
||||||
<HBox alignment="CENTER" styleClass="button-bar" VBox.vgrow="NEVER">
|
<MenuBar>
|
||||||
<Button onAction="#addProfile" text="Add Profile" />
|
<Menu mnemonicParsing="false" text="File">
|
||||||
<Button fx:id="editProfileButton" onAction="#editProfile" text="Edit Profile" />
|
<MenuItem mnemonicParsing="false" text="Exit"/>
|
||||||
<Button fx:id="removeProfileButton" onAction="#removeProfile" text="Remove Profile" />
|
</Menu>
|
||||||
</HBox>
|
<Menu mnemonicParsing="false" text="Profiles">
|
||||||
<ScrollPane fitToWidth="true" VBox.vgrow="ALWAYS">
|
<MenuItem mnemonicParsing="false" text="New Profile"/>
|
||||||
<VBox fx:id="profilesVBox" styleClass="banner-list" />
|
</Menu>
|
||||||
</ScrollPane>
|
<Menu mnemonicParsing="false" text="Help">
|
||||||
</VBox>
|
<MenuItem mnemonicParsing="false" text="About"/>
|
||||||
</Tab>
|
</Menu>
|
||||||
<Tab text="Servers">
|
</MenuBar>
|
||||||
<VBox>
|
<ScrollPane VBox.vgrow="ALWAYS">
|
||||||
<HBox alignment="CENTER" styleClass="button-bar" VBox.vgrow="NEVER">
|
<TilePane fx:id="profilesTilePane"/>
|
||||||
<Button onAction="#refreshServers" text="Refresh" />
|
</ScrollPane>
|
||||||
<TextField fx:id="registryUrlField" prefWidth="300.0" promptText="Registry URL" text="http://localhost:8080" style="-fx-font-size: 10px;" />
|
|
||||||
</HBox>
|
|
||||||
<ScrollPane fitToWidth="true" VBox.vgrow="ALWAYS">
|
|
||||||
<VBox fx:id="serversVBox" styleClass="banner-list" />
|
|
||||||
</ScrollPane>
|
|
||||||
</VBox>
|
|
||||||
</Tab>
|
|
||||||
</TabPane>
|
|
||||||
<HBox alignment="CENTER" styleClass="button-bar" VBox.vgrow="NEVER">
|
|
||||||
<Button fx:id="playButton" mnemonicParsing="false" onAction="#play" text="Play" />
|
|
||||||
</HBox>
|
|
||||||
<VBox fx:id="progressVBox" VBox.vgrow="NEVER">
|
|
||||||
<AnchorPane VBox.vgrow="NEVER">
|
|
||||||
<padding>
|
|
||||||
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
|
||||||
</padding>
|
|
||||||
<Label fx:id="progressLabel" text="Work in progress..." AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.topAnchor="0.0">
|
|
||||||
<font>
|
|
||||||
<Font size="10.0" />
|
|
||||||
</font>
|
|
||||||
</Label>
|
|
||||||
<ProgressBar fx:id="progressBar" prefWidth="200.0" progress="0.0" AnchorPane.bottomAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
|
|
||||||
</AnchorPane>
|
|
||||||
</VBox>
|
|
||||||
</VBox>
|
</VBox>
|
||||||
|
|
|
@ -1,31 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
|
|
||||||
<?import javafx.geometry.Insets?>
|
|
||||||
<?import javafx.scene.control.*?>
|
|
||||||
<?import javafx.scene.layout.*?>
|
|
||||||
<BorderPane prefWidth="300.0" xmlns="http://javafx.com/javafx/16" xmlns:fx="http://javafx.com/fxml/1">
|
|
||||||
<padding><Insets top="5" bottom="5" left="5" right="5"/></padding>
|
|
||||||
<top>
|
|
||||||
<Label fx:id="nameLabel" text="Profile Name" BorderPane.alignment="CENTER_LEFT" style="-fx-font-size: 16px; -fx-font-weight: bold;">
|
|
||||||
<BorderPane.margin>
|
|
||||||
<Insets bottom="5.0" />
|
|
||||||
</BorderPane.margin>
|
|
||||||
</Label>
|
|
||||||
</top>
|
|
||||||
<center>
|
|
||||||
<VBox BorderPane.alignment="CENTER">
|
|
||||||
<AnchorPane VBox.vgrow="NEVER">
|
|
||||||
<Label text="Client Version" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0"
|
|
||||||
AnchorPane.topAnchor="0.0"/>
|
|
||||||
<Label fx:id="clientVersionLabel" text="v1.0.0" textAlignment="RIGHT" AnchorPane.bottomAnchor="0.0"
|
|
||||||
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" style="-fx-font-weight: bold;"/>
|
|
||||||
</AnchorPane>
|
|
||||||
<AnchorPane VBox.vgrow="NEVER">
|
|
||||||
<Label text="Username" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0"
|
|
||||||
AnchorPane.topAnchor="0.0"/>
|
|
||||||
<Label fx:id="usernameLabel" text="Player" textAlignment="RIGHT" AnchorPane.bottomAnchor="0.0"
|
|
||||||
AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" style="-fx-font-weight: bold;"/>
|
|
||||||
</AnchorPane>
|
|
||||||
</VBox>
|
|
||||||
</center>
|
|
||||||
</BorderPane>
|
|
|
@ -1,22 +0,0 @@
|
||||||
.test{
|
|
||||||
-fx-background-color: blue;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-bar {
|
|
||||||
-fx-padding: 5 0 5 0;
|
|
||||||
-fx-spacing: 5;
|
|
||||||
-fx-font-weight: bold;
|
|
||||||
-fx-font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.banner-list {
|
|
||||||
-fx-spacing: 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.element-list-item:selected {
|
|
||||||
-fx-background-color: #e3e3e3;
|
|
||||||
}
|
|
||||||
|
|
||||||
#playButton {
|
|
||||||
-fx-border-radius: 0;
|
|
||||||
}
|
|
2
pom.xml
2
pom.xml
|
@ -7,7 +7,7 @@
|
||||||
<groupId>nl.andrewl</groupId>
|
<groupId>nl.andrewl</groupId>
|
||||||
<artifactId>ace-of-shades-2</artifactId>
|
<artifactId>ace-of-shades-2</artifactId>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
<version>1.5.0</version>
|
<version>1.1.0</version>
|
||||||
<modules>
|
<modules>
|
||||||
<module>core</module>
|
<module>core</module>
|
||||||
<module>server</module>
|
<module>server</module>
|
||||||
|
|
|
@ -1,33 +0,0 @@
|
||||||
HELP.md
|
|
||||||
target/
|
|
||||||
!.mvn/wrapper/maven-wrapper.jar
|
|
||||||
!**/src/main/**/target/
|
|
||||||
!**/src/test/**/target/
|
|
||||||
|
|
||||||
### STS ###
|
|
||||||
.apt_generated
|
|
||||||
.classpath
|
|
||||||
.factorypath
|
|
||||||
.project
|
|
||||||
.settings
|
|
||||||
.springBeans
|
|
||||||
.sts4-cache
|
|
||||||
|
|
||||||
### IntelliJ IDEA ###
|
|
||||||
.idea
|
|
||||||
*.iws
|
|
||||||
*.iml
|
|
||||||
*.ipr
|
|
||||||
|
|
||||||
### NetBeans ###
|
|
||||||
/nbproject/private/
|
|
||||||
/nbbuild/
|
|
||||||
/dist/
|
|
||||||
/nbdist/
|
|
||||||
/.nb-gradle/
|
|
||||||
build/
|
|
||||||
!**/src/main/**/build/
|
|
||||||
!**/src/test/**/build/
|
|
||||||
|
|
||||||
### VS Code ###
|
|
||||||
.vscode/
|
|
Binary file not shown.
|
@ -1,2 +0,0 @@
|
||||||
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.6/apache-maven-3.8.6-bin.zip
|
|
||||||
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar
|
|
|
@ -1,34 +0,0 @@
|
||||||
# Ace of Shades Server Registry
|
|
||||||
The registry is a REST API that keeps track of any servers that have recently announced their status to it. Servers can periodically send a simple JSON object with metadata about the server (name, description, players, etc.) so that players can more easily search for a server to play on.
|
|
||||||
|
|
||||||
### Fetching
|
|
||||||
Client/launcher applications that want to get a list of servers from the registry should send a GET request to the API's `/servers` endpoint.
|
|
||||||
|
|
||||||
The following array of servers is returned from GET requests to the API's `/servers` endpoint:
|
|
||||||
```json
|
|
||||||
[
|
|
||||||
{
|
|
||||||
"host": "0:0:0:0:0:0:0:1",
|
|
||||||
"port": 1234,
|
|
||||||
"name": "Andrew's Server",
|
|
||||||
"description": "A good server.",
|
|
||||||
"maxPlayers": 32,
|
|
||||||
"currentPlayers": 2,
|
|
||||||
"lastUpdatedAt": 1659710488855
|
|
||||||
}
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
### Posting
|
|
||||||
The following payload should be sent by servers to the API's `/servers` endpoint via POST:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"port": 1234,
|
|
||||||
"token": "abc123",
|
|
||||||
"name": "Andrew's Server",
|
|
||||||
"description": "A good server.",
|
|
||||||
"maxPlayers": 32,
|
|
||||||
"currentPlayers": 2
|
|
||||||
}
|
|
||||||
```
|
|
||||||
Note that this should only be done at most once per minute. Any more frequent, and you'll receive 429 Too-Many-Requests responses, and continued spam may permanently block your server.
|
|
|
@ -1,5 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
# Put your GRAALVM location here.
|
|
||||||
export GRAALVM_HOME=/home/andrew/Downloads/graalvm-ce-java17-22.2.0
|
|
||||||
mvn -Pnative -DskipTests clean package
|
|
|
@ -1,316 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
# Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
# or more contributor license agreements. See the NOTICE file
|
|
||||||
# distributed with this work for additional information
|
|
||||||
# regarding copyright ownership. The ASF licenses this file
|
|
||||||
# to you under the Apache License, Version 2.0 (the
|
|
||||||
# "License"); you may not use this file except in compliance
|
|
||||||
# with the License. You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# https://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing,
|
|
||||||
# software distributed under the License is distributed on an
|
|
||||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
# KIND, either express or implied. See the License for the
|
|
||||||
# specific language governing permissions and limitations
|
|
||||||
# under the License.
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
# Maven Start Up Batch script
|
|
||||||
#
|
|
||||||
# Required ENV vars:
|
|
||||||
# ------------------
|
|
||||||
# JAVA_HOME - location of a JDK home dir
|
|
||||||
#
|
|
||||||
# Optional ENV vars
|
|
||||||
# -----------------
|
|
||||||
# M2_HOME - location of maven2's installed home dir
|
|
||||||
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
|
|
||||||
# e.g. to debug Maven itself, use
|
|
||||||
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
|
|
||||||
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
|
|
||||||
# ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
if [ -z "$MAVEN_SKIP_RC" ] ; then
|
|
||||||
|
|
||||||
if [ -f /usr/local/etc/mavenrc ] ; then
|
|
||||||
. /usr/local/etc/mavenrc
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -f /etc/mavenrc ] ; then
|
|
||||||
. /etc/mavenrc
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -f "$HOME/.mavenrc" ] ; then
|
|
||||||
. "$HOME/.mavenrc"
|
|
||||||
fi
|
|
||||||
|
|
||||||
fi
|
|
||||||
|
|
||||||
# OS specific support. $var _must_ be set to either true or false.
|
|
||||||
cygwin=false;
|
|
||||||
darwin=false;
|
|
||||||
mingw=false
|
|
||||||
case "`uname`" in
|
|
||||||
CYGWIN*) cygwin=true ;;
|
|
||||||
MINGW*) mingw=true;;
|
|
||||||
Darwin*) darwin=true
|
|
||||||
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
|
|
||||||
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
|
|
||||||
if [ -z "$JAVA_HOME" ]; then
|
|
||||||
if [ -x "/usr/libexec/java_home" ]; then
|
|
||||||
export JAVA_HOME="`/usr/libexec/java_home`"
|
|
||||||
else
|
|
||||||
export JAVA_HOME="/Library/Java/Home"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
if [ -z "$JAVA_HOME" ] ; then
|
|
||||||
if [ -r /etc/gentoo-release ] ; then
|
|
||||||
JAVA_HOME=`java-config --jre-home`
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "$M2_HOME" ] ; then
|
|
||||||
## resolve links - $0 may be a link to maven's home
|
|
||||||
PRG="$0"
|
|
||||||
|
|
||||||
# need this for relative symlinks
|
|
||||||
while [ -h "$PRG" ] ; do
|
|
||||||
ls=`ls -ld "$PRG"`
|
|
||||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
|
||||||
if expr "$link" : '/.*' > /dev/null; then
|
|
||||||
PRG="$link"
|
|
||||||
else
|
|
||||||
PRG="`dirname "$PRG"`/$link"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
saveddir=`pwd`
|
|
||||||
|
|
||||||
M2_HOME=`dirname "$PRG"`/..
|
|
||||||
|
|
||||||
# make it fully qualified
|
|
||||||
M2_HOME=`cd "$M2_HOME" && pwd`
|
|
||||||
|
|
||||||
cd "$saveddir"
|
|
||||||
# echo Using m2 at $M2_HOME
|
|
||||||
fi
|
|
||||||
|
|
||||||
# For Cygwin, ensure paths are in UNIX format before anything is touched
|
|
||||||
if $cygwin ; then
|
|
||||||
[ -n "$M2_HOME" ] &&
|
|
||||||
M2_HOME=`cygpath --unix "$M2_HOME"`
|
|
||||||
[ -n "$JAVA_HOME" ] &&
|
|
||||||
JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
|
|
||||||
[ -n "$CLASSPATH" ] &&
|
|
||||||
CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
|
|
||||||
fi
|
|
||||||
|
|
||||||
# For Mingw, ensure paths are in UNIX format before anything is touched
|
|
||||||
if $mingw ; then
|
|
||||||
[ -n "$M2_HOME" ] &&
|
|
||||||
M2_HOME="`(cd "$M2_HOME"; pwd)`"
|
|
||||||
[ -n "$JAVA_HOME" ] &&
|
|
||||||
JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "$JAVA_HOME" ]; then
|
|
||||||
javaExecutable="`which javac`"
|
|
||||||
if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
|
|
||||||
# readlink(1) is not available as standard on Solaris 10.
|
|
||||||
readLink=`which readlink`
|
|
||||||
if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
|
|
||||||
if $darwin ; then
|
|
||||||
javaHome="`dirname \"$javaExecutable\"`"
|
|
||||||
javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
|
|
||||||
else
|
|
||||||
javaExecutable="`readlink -f \"$javaExecutable\"`"
|
|
||||||
fi
|
|
||||||
javaHome="`dirname \"$javaExecutable\"`"
|
|
||||||
javaHome=`expr "$javaHome" : '\(.*\)/bin'`
|
|
||||||
JAVA_HOME="$javaHome"
|
|
||||||
export JAVA_HOME
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "$JAVACMD" ] ; then
|
|
||||||
if [ -n "$JAVA_HOME" ] ; then
|
|
||||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
|
||||||
# IBM's JDK on AIX uses strange locations for the executables
|
|
||||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
|
||||||
else
|
|
||||||
JAVACMD="$JAVA_HOME/bin/java"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
JAVACMD="`\\unset -f command; \\command -v java`"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ ! -x "$JAVACMD" ] ; then
|
|
||||||
echo "Error: JAVA_HOME is not defined correctly." >&2
|
|
||||||
echo " We cannot execute $JAVACMD" >&2
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "$JAVA_HOME" ] ; then
|
|
||||||
echo "Warning: JAVA_HOME environment variable is not set."
|
|
||||||
fi
|
|
||||||
|
|
||||||
CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
|
|
||||||
|
|
||||||
# traverses directory structure from process work directory to filesystem root
|
|
||||||
# first directory with .mvn subdirectory is considered project base directory
|
|
||||||
find_maven_basedir() {
|
|
||||||
|
|
||||||
if [ -z "$1" ]
|
|
||||||
then
|
|
||||||
echo "Path not specified to find_maven_basedir"
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
basedir="$1"
|
|
||||||
wdir="$1"
|
|
||||||
while [ "$wdir" != '/' ] ; do
|
|
||||||
if [ -d "$wdir"/.mvn ] ; then
|
|
||||||
basedir=$wdir
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
|
|
||||||
if [ -d "${wdir}" ]; then
|
|
||||||
wdir=`cd "$wdir/.."; pwd`
|
|
||||||
fi
|
|
||||||
# end of workaround
|
|
||||||
done
|
|
||||||
echo "${basedir}"
|
|
||||||
}
|
|
||||||
|
|
||||||
# concatenates all lines of a file
|
|
||||||
concat_lines() {
|
|
||||||
if [ -f "$1" ]; then
|
|
||||||
echo "$(tr -s '\n' ' ' < "$1")"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
BASE_DIR=`find_maven_basedir "$(pwd)"`
|
|
||||||
if [ -z "$BASE_DIR" ]; then
|
|
||||||
exit 1;
|
|
||||||
fi
|
|
||||||
|
|
||||||
##########################################################################################
|
|
||||||
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
|
|
||||||
# This allows using the maven wrapper in projects that prohibit checking in binary data.
|
|
||||||
##########################################################################################
|
|
||||||
if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
|
|
||||||
if [ "$MVNW_VERBOSE" = true ]; then
|
|
||||||
echo "Found .mvn/wrapper/maven-wrapper.jar"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
if [ "$MVNW_VERBOSE" = true ]; then
|
|
||||||
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
|
|
||||||
fi
|
|
||||||
if [ -n "$MVNW_REPOURL" ]; then
|
|
||||||
jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
|
|
||||||
else
|
|
||||||
jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
|
|
||||||
fi
|
|
||||||
while IFS="=" read key value; do
|
|
||||||
case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
|
|
||||||
esac
|
|
||||||
done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
|
|
||||||
if [ "$MVNW_VERBOSE" = true ]; then
|
|
||||||
echo "Downloading from: $jarUrl"
|
|
||||||
fi
|
|
||||||
wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
|
|
||||||
if $cygwin; then
|
|
||||||
wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
|
|
||||||
fi
|
|
||||||
|
|
||||||
if command -v wget > /dev/null; then
|
|
||||||
if [ "$MVNW_VERBOSE" = true ]; then
|
|
||||||
echo "Found wget ... using wget"
|
|
||||||
fi
|
|
||||||
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
|
|
||||||
wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
|
|
||||||
else
|
|
||||||
wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
|
|
||||||
fi
|
|
||||||
elif command -v curl > /dev/null; then
|
|
||||||
if [ "$MVNW_VERBOSE" = true ]; then
|
|
||||||
echo "Found curl ... using curl"
|
|
||||||
fi
|
|
||||||
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
|
|
||||||
curl -o "$wrapperJarPath" "$jarUrl" -f
|
|
||||||
else
|
|
||||||
curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
|
|
||||||
fi
|
|
||||||
|
|
||||||
else
|
|
||||||
if [ "$MVNW_VERBOSE" = true ]; then
|
|
||||||
echo "Falling back to using Java to download"
|
|
||||||
fi
|
|
||||||
javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
|
|
||||||
# For Cygwin, switch paths to Windows format before running javac
|
|
||||||
if $cygwin; then
|
|
||||||
javaClass=`cygpath --path --windows "$javaClass"`
|
|
||||||
fi
|
|
||||||
if [ -e "$javaClass" ]; then
|
|
||||||
if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
|
|
||||||
if [ "$MVNW_VERBOSE" = true ]; then
|
|
||||||
echo " - Compiling MavenWrapperDownloader.java ..."
|
|
||||||
fi
|
|
||||||
# Compiling the Java class
|
|
||||||
("$JAVA_HOME/bin/javac" "$javaClass")
|
|
||||||
fi
|
|
||||||
if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
|
|
||||||
# Running the downloader
|
|
||||||
if [ "$MVNW_VERBOSE" = true ]; then
|
|
||||||
echo " - Running MavenWrapperDownloader.java ..."
|
|
||||||
fi
|
|
||||||
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
##########################################################################################
|
|
||||||
# End of extension
|
|
||||||
##########################################################################################
|
|
||||||
|
|
||||||
export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
|
|
||||||
if [ "$MVNW_VERBOSE" = true ]; then
|
|
||||||
echo $MAVEN_PROJECTBASEDIR
|
|
||||||
fi
|
|
||||||
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
|
|
||||||
|
|
||||||
# For Cygwin, switch paths to Windows format before running java
|
|
||||||
if $cygwin; then
|
|
||||||
[ -n "$M2_HOME" ] &&
|
|
||||||
M2_HOME=`cygpath --path --windows "$M2_HOME"`
|
|
||||||
[ -n "$JAVA_HOME" ] &&
|
|
||||||
JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
|
|
||||||
[ -n "$CLASSPATH" ] &&
|
|
||||||
CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
|
|
||||||
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
|
|
||||||
MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Provide a "standardized" way to retrieve the CLI args that will
|
|
||||||
# work with both Windows and non-Windows executions.
|
|
||||||
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
|
|
||||||
export MAVEN_CMD_LINE_ARGS
|
|
||||||
|
|
||||||
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
|
|
||||||
|
|
||||||
exec "$JAVACMD" \
|
|
||||||
$MAVEN_OPTS \
|
|
||||||
$MAVEN_DEBUG_OPTS \
|
|
||||||
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
|
|
||||||
"-Dmaven.home=${M2_HOME}" \
|
|
||||||
"-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
|
|
||||||
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
|
|
|
@ -1,188 +0,0 @@
|
||||||
@REM ----------------------------------------------------------------------------
|
|
||||||
@REM Licensed to the Apache Software Foundation (ASF) under one
|
|
||||||
@REM or more contributor license agreements. See the NOTICE file
|
|
||||||
@REM distributed with this work for additional information
|
|
||||||
@REM regarding copyright ownership. The ASF licenses this file
|
|
||||||
@REM to you under the Apache License, Version 2.0 (the
|
|
||||||
@REM "License"); you may not use this file except in compliance
|
|
||||||
@REM with the License. You may obtain a copy of the License at
|
|
||||||
@REM
|
|
||||||
@REM https://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
@REM
|
|
||||||
@REM Unless required by applicable law or agreed to in writing,
|
|
||||||
@REM software distributed under the License is distributed on an
|
|
||||||
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
||||||
@REM KIND, either express or implied. See the License for the
|
|
||||||
@REM specific language governing permissions and limitations
|
|
||||||
@REM under the License.
|
|
||||||
@REM ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
@REM ----------------------------------------------------------------------------
|
|
||||||
@REM Maven Start Up Batch script
|
|
||||||
@REM
|
|
||||||
@REM Required ENV vars:
|
|
||||||
@REM JAVA_HOME - location of a JDK home dir
|
|
||||||
@REM
|
|
||||||
@REM Optional ENV vars
|
|
||||||
@REM M2_HOME - location of maven2's installed home dir
|
|
||||||
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
|
|
||||||
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
|
|
||||||
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
|
|
||||||
@REM e.g. to debug Maven itself, use
|
|
||||||
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
|
|
||||||
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
|
|
||||||
@REM ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
|
|
||||||
@echo off
|
|
||||||
@REM set title of command window
|
|
||||||
title %0
|
|
||||||
@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
|
|
||||||
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
|
|
||||||
|
|
||||||
@REM set %HOME% to equivalent of $HOME
|
|
||||||
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
|
|
||||||
|
|
||||||
@REM Execute a user defined script before this one
|
|
||||||
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
|
|
||||||
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
|
|
||||||
if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
|
|
||||||
if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
|
|
||||||
:skipRcPre
|
|
||||||
|
|
||||||
@setlocal
|
|
||||||
|
|
||||||
set ERROR_CODE=0
|
|
||||||
|
|
||||||
@REM To isolate internal variables from possible post scripts, we use another setlocal
|
|
||||||
@setlocal
|
|
||||||
|
|
||||||
@REM ==== START VALIDATION ====
|
|
||||||
if not "%JAVA_HOME%" == "" goto OkJHome
|
|
||||||
|
|
||||||
echo.
|
|
||||||
echo Error: JAVA_HOME not found in your environment. >&2
|
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the >&2
|
|
||||||
echo location of your Java installation. >&2
|
|
||||||
echo.
|
|
||||||
goto error
|
|
||||||
|
|
||||||
:OkJHome
|
|
||||||
if exist "%JAVA_HOME%\bin\java.exe" goto init
|
|
||||||
|
|
||||||
echo.
|
|
||||||
echo Error: JAVA_HOME is set to an invalid directory. >&2
|
|
||||||
echo JAVA_HOME = "%JAVA_HOME%" >&2
|
|
||||||
echo Please set the JAVA_HOME variable in your environment to match the >&2
|
|
||||||
echo location of your Java installation. >&2
|
|
||||||
echo.
|
|
||||||
goto error
|
|
||||||
|
|
||||||
@REM ==== END VALIDATION ====
|
|
||||||
|
|
||||||
:init
|
|
||||||
|
|
||||||
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
|
|
||||||
@REM Fallback to current working directory if not found.
|
|
||||||
|
|
||||||
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
|
|
||||||
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
|
|
||||||
|
|
||||||
set EXEC_DIR=%CD%
|
|
||||||
set WDIR=%EXEC_DIR%
|
|
||||||
:findBaseDir
|
|
||||||
IF EXIST "%WDIR%"\.mvn goto baseDirFound
|
|
||||||
cd ..
|
|
||||||
IF "%WDIR%"=="%CD%" goto baseDirNotFound
|
|
||||||
set WDIR=%CD%
|
|
||||||
goto findBaseDir
|
|
||||||
|
|
||||||
:baseDirFound
|
|
||||||
set MAVEN_PROJECTBASEDIR=%WDIR%
|
|
||||||
cd "%EXEC_DIR%"
|
|
||||||
goto endDetectBaseDir
|
|
||||||
|
|
||||||
:baseDirNotFound
|
|
||||||
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
|
|
||||||
cd "%EXEC_DIR%"
|
|
||||||
|
|
||||||
:endDetectBaseDir
|
|
||||||
|
|
||||||
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
|
|
||||||
|
|
||||||
@setlocal EnableExtensions EnableDelayedExpansion
|
|
||||||
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
|
|
||||||
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
|
|
||||||
|
|
||||||
:endReadAdditionalConfig
|
|
||||||
|
|
||||||
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
|
|
||||||
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
|
|
||||||
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
|
|
||||||
|
|
||||||
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
|
|
||||||
|
|
||||||
FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
|
|
||||||
IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
|
|
||||||
)
|
|
||||||
|
|
||||||
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
|
|
||||||
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
|
|
||||||
if exist %WRAPPER_JAR% (
|
|
||||||
if "%MVNW_VERBOSE%" == "true" (
|
|
||||||
echo Found %WRAPPER_JAR%
|
|
||||||
)
|
|
||||||
) else (
|
|
||||||
if not "%MVNW_REPOURL%" == "" (
|
|
||||||
SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
|
|
||||||
)
|
|
||||||
if "%MVNW_VERBOSE%" == "true" (
|
|
||||||
echo Couldn't find %WRAPPER_JAR%, downloading it ...
|
|
||||||
echo Downloading from: %DOWNLOAD_URL%
|
|
||||||
)
|
|
||||||
|
|
||||||
powershell -Command "&{"^
|
|
||||||
"$webclient = new-object System.Net.WebClient;"^
|
|
||||||
"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
|
|
||||||
"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
|
|
||||||
"}"^
|
|
||||||
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
|
|
||||||
"}"
|
|
||||||
if "%MVNW_VERBOSE%" == "true" (
|
|
||||||
echo Finished downloading %WRAPPER_JAR%
|
|
||||||
)
|
|
||||||
)
|
|
||||||
@REM End of extension
|
|
||||||
|
|
||||||
@REM Provide a "standardized" way to retrieve the CLI args that will
|
|
||||||
@REM work with both Windows and non-Windows executions.
|
|
||||||
set MAVEN_CMD_LINE_ARGS=%*
|
|
||||||
|
|
||||||
%MAVEN_JAVA_EXE% ^
|
|
||||||
%JVM_CONFIG_MAVEN_PROPS% ^
|
|
||||||
%MAVEN_OPTS% ^
|
|
||||||
%MAVEN_DEBUG_OPTS% ^
|
|
||||||
-classpath %WRAPPER_JAR% ^
|
|
||||||
"-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
|
|
||||||
%WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
|
|
||||||
if ERRORLEVEL 1 goto error
|
|
||||||
goto end
|
|
||||||
|
|
||||||
:error
|
|
||||||
set ERROR_CODE=1
|
|
||||||
|
|
||||||
:end
|
|
||||||
@endlocal & set ERROR_CODE=%ERROR_CODE%
|
|
||||||
|
|
||||||
if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
|
|
||||||
@REM check for post script, once with legacy .bat ending and once with .cmd ending
|
|
||||||
if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
|
|
||||||
if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
|
|
||||||
:skipRcPost
|
|
||||||
|
|
||||||
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
|
|
||||||
if "%MAVEN_BATCH_PAUSE%"=="on" pause
|
|
||||||
|
|
||||||
if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
|
|
||||||
|
|
||||||
cmd /C exit /B %ERROR_CODE%
|
|
150
registry/pom.xml
150
registry/pom.xml
|
@ -1,150 +0,0 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
|
||||||
<parent>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-parent</artifactId>
|
|
||||||
<version>2.7.2</version>
|
|
||||||
<relativePath/> <!-- lookup parent from repository -->
|
|
||||||
</parent>
|
|
||||||
<groupId>nl.andrewl</groupId>
|
|
||||||
<artifactId>aos2-registry-api</artifactId>
|
|
||||||
<version>0.0.1-SNAPSHOT</version>
|
|
||||||
<name>aos2-registry-api</name>
|
|
||||||
<description>Registry API for Ace of Shades 2 servers.</description>
|
|
||||||
<properties>
|
|
||||||
<java.version>17</java.version>
|
|
||||||
<repackage.classifier/>
|
|
||||||
<spring-native.version>0.12.1</spring-native.version>
|
|
||||||
</properties>
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-webflux</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.experimental</groupId>
|
|
||||||
<artifactId>spring-native</artifactId>
|
|
||||||
<version>${spring-native.version}</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-devtools</artifactId>
|
|
||||||
<scope>runtime</scope>
|
|
||||||
<optional>true</optional>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-test</artifactId>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>io.projectreactor</groupId>
|
|
||||||
<artifactId>reactor-test</artifactId>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
|
|
||||||
<build>
|
|
||||||
<plugins>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
|
||||||
<configuration>
|
|
||||||
<classifier>${repackage.classifier}</classifier>
|
|
||||||
<image>
|
|
||||||
<builder>paketobuildpacks/builder:tiny</builder>
|
|
||||||
<env>
|
|
||||||
<BP_NATIVE_IMAGE>true</BP_NATIVE_IMAGE>
|
|
||||||
</env>
|
|
||||||
</image>
|
|
||||||
</configuration>
|
|
||||||
</plugin>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.springframework.experimental</groupId>
|
|
||||||
<artifactId>spring-aot-maven-plugin</artifactId>
|
|
||||||
<version>${spring-native.version}</version>
|
|
||||||
<executions>
|
|
||||||
<execution>
|
|
||||||
<id>test-generate</id>
|
|
||||||
<goals>
|
|
||||||
<goal>test-generate</goal>
|
|
||||||
</goals>
|
|
||||||
</execution>
|
|
||||||
<execution>
|
|
||||||
<id>generate</id>
|
|
||||||
<goals>
|
|
||||||
<goal>generate</goal>
|
|
||||||
</goals>
|
|
||||||
</execution>
|
|
||||||
</executions>
|
|
||||||
</plugin>
|
|
||||||
</plugins>
|
|
||||||
</build>
|
|
||||||
<repositories>
|
|
||||||
<repository>
|
|
||||||
<id>spring-releases</id>
|
|
||||||
<name>Spring Releases</name>
|
|
||||||
<url>https://repo.spring.io/release</url>
|
|
||||||
<snapshots>
|
|
||||||
<enabled>false</enabled>
|
|
||||||
</snapshots>
|
|
||||||
</repository>
|
|
||||||
</repositories>
|
|
||||||
<pluginRepositories>
|
|
||||||
<pluginRepository>
|
|
||||||
<id>spring-releases</id>
|
|
||||||
<name>Spring Releases</name>
|
|
||||||
<url>https://repo.spring.io/release</url>
|
|
||||||
<snapshots>
|
|
||||||
<enabled>false</enabled>
|
|
||||||
</snapshots>
|
|
||||||
</pluginRepository>
|
|
||||||
</pluginRepositories>
|
|
||||||
|
|
||||||
<profiles>
|
|
||||||
<profile>
|
|
||||||
<id>native</id>
|
|
||||||
<properties>
|
|
||||||
<repackage.classifier>exec</repackage.classifier>
|
|
||||||
<native-buildtools.version>0.9.13</native-buildtools.version>
|
|
||||||
</properties>
|
|
||||||
<dependencies>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.junit.platform</groupId>
|
|
||||||
<artifactId>junit-platform-launcher</artifactId>
|
|
||||||
<scope>test</scope>
|
|
||||||
</dependency>
|
|
||||||
</dependencies>
|
|
||||||
<build>
|
|
||||||
<plugins>
|
|
||||||
<plugin>
|
|
||||||
<groupId>org.graalvm.buildtools</groupId>
|
|
||||||
<artifactId>native-maven-plugin</artifactId>
|
|
||||||
<version>${native-buildtools.version}</version>
|
|
||||||
<extensions>true</extensions>
|
|
||||||
<executions>
|
|
||||||
<execution>
|
|
||||||
<id>test-native</id>
|
|
||||||
<phase>test</phase>
|
|
||||||
<goals>
|
|
||||||
<goal>test</goal>
|
|
||||||
</goals>
|
|
||||||
</execution>
|
|
||||||
<execution>
|
|
||||||
<id>build-native</id>
|
|
||||||
<phase>package</phase>
|
|
||||||
<goals>
|
|
||||||
<goal>build</goal>
|
|
||||||
</goals>
|
|
||||||
</execution>
|
|
||||||
</executions>
|
|
||||||
</plugin>
|
|
||||||
</plugins>
|
|
||||||
</build>
|
|
||||||
</profile>
|
|
||||||
</profiles>
|
|
||||||
|
|
||||||
</project>
|
|
|
@ -1,15 +0,0 @@
|
||||||
package nl.andrewl.aos2registryapi;
|
|
||||||
|
|
||||||
import org.springframework.boot.SpringApplication;
|
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
|
||||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
|
||||||
|
|
||||||
@SpringBootApplication
|
|
||||||
@EnableScheduling
|
|
||||||
public class Aos2RegistryApiApplication {
|
|
||||||
|
|
||||||
public static void main(String[] args) {
|
|
||||||
SpringApplication.run(Aos2RegistryApiApplication.class, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
package nl.andrewl.aos2registryapi;
|
|
||||||
|
|
||||||
import nl.andrewl.aos2registryapi.dto.ServerInfoPayload;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
public class ServerInfoValidator {
|
|
||||||
|
|
||||||
public boolean validateName(String name) {
|
|
||||||
return name != null && !name.isBlank() && name.length() <= 64;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean validateDescription(String description) {
|
|
||||||
return description == null ||
|
|
||||||
(!description.isBlank() && description.length() <= 256);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean validatePlayerCounts(int max, int current) {
|
|
||||||
return max > 0 && current >= 0 && current <= max && max < 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Optional<List<String>> validatePayload(ServerInfoPayload payload) {
|
|
||||||
List<String> messages = new ArrayList<>(3);
|
|
||||||
if (payload.port() < 0 || payload.port() > 65535) messages.add("Invalid port.");
|
|
||||||
if (!validateName(payload.name())) messages.add("Invalid name.");
|
|
||||||
if (!validateDescription(payload.description())) messages.add("Invalid description.");
|
|
||||||
if (!validatePlayerCounts(payload.maxPlayers(), payload.currentPlayers())) messages.add("Invalid player counts.");
|
|
||||||
if (messages.size() > 0) return Optional.of(messages);
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,89 +0,0 @@
|
||||||
package nl.andrewl.aos2registryapi;
|
|
||||||
|
|
||||||
import nl.andrewl.aos2registryapi.dto.ServerInfoPayload;
|
|
||||||
import nl.andrewl.aos2registryapi.dto.ServerInfoResponse;
|
|
||||||
import nl.andrewl.aos2registryapi.model.ServerIdentifier;
|
|
||||||
import nl.andrewl.aos2registryapi.model.ServerInfo;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.scheduling.annotation.Scheduled;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
import org.springframework.web.server.ResponseStatusException;
|
|
||||||
import reactor.core.publisher.Flux;
|
|
||||||
|
|
||||||
import java.time.Duration;
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Queue;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
public class ServerRegistry {
|
|
||||||
private static final Logger log = LoggerFactory.getLogger(ServerRegistry.class);
|
|
||||||
|
|
||||||
public static final Duration SERVER_TIMEOUT = Duration.ofMinutes(3);
|
|
||||||
public static final Duration SERVER_MIN_UPDATE = Duration.ofSeconds(5);
|
|
||||||
|
|
||||||
private final Map<ServerIdentifier, ServerInfo> servers = new ConcurrentHashMap<>();
|
|
||||||
private final ServerInfoValidator infoValidator = new ServerInfoValidator();
|
|
||||||
|
|
||||||
public Flux<ServerInfoResponse> getServers() {
|
|
||||||
Stream<ServerInfoResponse> stream = servers.entrySet().stream()
|
|
||||||
.sorted(Comparator.comparing(entry -> entry.getValue().getLastUpdatedAt()))
|
|
||||||
.map(entry -> new ServerInfoResponse(
|
|
||||||
entry.getKey().host(),
|
|
||||||
entry.getKey().port(),
|
|
||||||
entry.getValue().getName(),
|
|
||||||
entry.getValue().getDescription(),
|
|
||||||
entry.getValue().getMaxPlayers(),
|
|
||||||
entry.getValue().getCurrentPlayers(),
|
|
||||||
entry.getValue().getLastUpdatedAt().toEpochMilli()
|
|
||||||
));
|
|
||||||
return Flux.fromStream(stream);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void acceptInfo(ServerIdentifier ident, ServerInfoPayload payload) {
|
|
||||||
var result = infoValidator.validatePayload(payload);
|
|
||||||
if (result.isPresent()) {
|
|
||||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, String.join(" ", result.get()));
|
|
||||||
}
|
|
||||||
ServerInfo info = servers.get(ident);
|
|
||||||
if (info != null) {
|
|
||||||
Instant now = Instant.now();
|
|
||||||
// Check if this update was sent too fast.
|
|
||||||
if (Duration.between(info.getLastUpdatedAt(), now).compareTo(SERVER_MIN_UPDATE) < 0) {
|
|
||||||
throw new ResponseStatusException(HttpStatus.TOO_MANY_REQUESTS, "Server update rate limit exceeded.");
|
|
||||||
}
|
|
||||||
// Update existing server.
|
|
||||||
info.setName(payload.name());
|
|
||||||
info.setDescription(payload.description());
|
|
||||||
info.setMaxPlayers(payload.maxPlayers());
|
|
||||||
info.setCurrentPlayers(payload.currentPlayers());
|
|
||||||
info.setLastUpdatedAt(now);
|
|
||||||
} else {
|
|
||||||
// Save new server.
|
|
||||||
servers.put(ident, new ServerInfo(payload.name(), payload.description(), payload.maxPlayers(), payload.currentPlayers()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Scheduled(fixedRate = 1, timeUnit = TimeUnit.MINUTES, initialDelay = 1)
|
|
||||||
public void purgeOldServers() {
|
|
||||||
Queue<ServerIdentifier> removalQueue = new LinkedList<>();
|
|
||||||
final Instant cutoff = Instant.now().minus(SERVER_TIMEOUT);
|
|
||||||
for (var entry : servers.entrySet()) {
|
|
||||||
var ident = entry.getKey();
|
|
||||||
var server = entry.getValue();
|
|
||||||
if (server.getLastUpdatedAt().isBefore(cutoff)) {
|
|
||||||
removalQueue.add(ident);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
while (!removalQueue.isEmpty()) {
|
|
||||||
servers.remove(removalQueue.remove());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
package nl.andrewl.aos2registryapi.api;
|
|
||||||
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
|
||||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
|
||||||
import org.springframework.web.server.ResponseStatusException;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
@RestControllerAdvice
|
|
||||||
public class ErrorAdvice {
|
|
||||||
|
|
||||||
@ExceptionHandler(ResponseStatusException.class)
|
|
||||||
public ResponseEntity<?> handleRSE(ResponseStatusException e) {
|
|
||||||
Map<String, Object> data = new HashMap<>();
|
|
||||||
data.put("code", e.getRawStatusCode());
|
|
||||||
data.put("message", e.getReason());
|
|
||||||
return ResponseEntity.status(e.getStatus()).body(data);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,36 +0,0 @@
|
||||||
package nl.andrewl.aos2registryapi.api;
|
|
||||||
|
|
||||||
import nl.andrewl.aos2registryapi.ServerRegistry;
|
|
||||||
import nl.andrewl.aos2registryapi.dto.ServerInfoPayload;
|
|
||||||
import nl.andrewl.aos2registryapi.dto.ServerInfoResponse;
|
|
||||||
import nl.andrewl.aos2registryapi.model.ServerIdentifier;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
|
||||||
import org.springframework.web.bind.annotation.*;
|
|
||||||
import reactor.core.publisher.Flux;
|
|
||||||
import reactor.core.publisher.Mono;
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
@RequestMapping(path = "/servers")
|
|
||||||
public class ServersController {
|
|
||||||
private final ServerRegistry serverRegistry;
|
|
||||||
|
|
||||||
public ServersController(ServerRegistry serverRegistry) {
|
|
||||||
this.serverRegistry = serverRegistry;
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping
|
|
||||||
public Flux<ServerInfoResponse> getServers() {
|
|
||||||
return serverRegistry.getServers();
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping
|
|
||||||
public Mono<ResponseEntity<Object>> updateServer(ServerHttpRequest req, @RequestBody Mono<ServerInfoPayload> payloadMono) {
|
|
||||||
String host = req.getRemoteAddress().getAddress().getHostAddress();
|
|
||||||
return payloadMono.mapNotNull(payload -> {
|
|
||||||
ServerIdentifier ident = new ServerIdentifier(host, payload.port());
|
|
||||||
serverRegistry.acceptInfo(ident, payload);
|
|
||||||
return ResponseEntity.ok(null);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
package nl.andrewl.aos2registryapi.dto;
|
|
||||||
|
|
||||||
public record ServerInfoPayload (
|
|
||||||
int port,
|
|
||||||
String name,
|
|
||||||
String description,
|
|
||||||
int maxPlayers,
|
|
||||||
int currentPlayers
|
|
||||||
) {}
|
|
|
@ -1,11 +0,0 @@
|
||||||
package nl.andrewl.aos2registryapi.dto;
|
|
||||||
|
|
||||||
public record ServerInfoResponse (
|
|
||||||
String host,
|
|
||||||
int port,
|
|
||||||
String name,
|
|
||||||
String description,
|
|
||||||
int maxPlayers,
|
|
||||||
int currentPlayers,
|
|
||||||
long lastUpdatedAt
|
|
||||||
) {}
|
|
|
@ -1,3 +0,0 @@
|
||||||
package nl.andrewl.aos2registryapi.model;
|
|
||||||
|
|
||||||
public record ServerIdentifier(String host, int port) {}
|
|
|
@ -1,59 +0,0 @@
|
||||||
package nl.andrewl.aos2registryapi.model;
|
|
||||||
|
|
||||||
import java.time.Instant;
|
|
||||||
|
|
||||||
public class ServerInfo {
|
|
||||||
private String name;
|
|
||||||
private String description;
|
|
||||||
private int maxPlayers;
|
|
||||||
private int currentPlayers;
|
|
||||||
private Instant lastUpdatedAt;
|
|
||||||
|
|
||||||
public ServerInfo(String name, String description, int maxPlayers, int currentPlayers) {
|
|
||||||
this.name = name;
|
|
||||||
this.description = description;
|
|
||||||
this.maxPlayers = maxPlayers;
|
|
||||||
this.currentPlayers = currentPlayers;
|
|
||||||
this.lastUpdatedAt = Instant.now();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getDescription() {
|
|
||||||
return description;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getMaxPlayers() {
|
|
||||||
return maxPlayers;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getCurrentPlayers() {
|
|
||||||
return currentPlayers;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Instant getLastUpdatedAt() {
|
|
||||||
return lastUpdatedAt;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setName(String name) {
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setDescription(String description) {
|
|
||||||
this.description = description;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMaxPlayers(int maxPlayers) {
|
|
||||||
this.maxPlayers = maxPlayers;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCurrentPlayers(int currentPlayers) {
|
|
||||||
this.currentPlayers = currentPlayers;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLastUpdatedAt(Instant lastUpdatedAt) {
|
|
||||||
this.lastUpdatedAt = lastUpdatedAt;
|
|
||||||
}
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue