Compare commits
6 Commits
f8f150cdf0
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
f2b6200355
|
|||
|
e15d4ec874
|
|||
|
dac6d666b2
|
|||
|
3dd2c389b8
|
|||
|
f7d878f430
|
|||
|
32f8521951
|
37
README.md
Normal file
37
README.md
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# Terminal Game
|
||||||
|
|
||||||
|
A multiplayer terminal-based game built with Java, utilizing WebSockets for communication and Lanterna for the text-based user interface.
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
* **game**: Client application (TUI).
|
||||||
|
* **server**: WebSocket server.
|
||||||
|
* **common**: Shared libraries and logic.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
* Java 25
|
||||||
|
* Maven
|
||||||
|
|
||||||
|
## How to Run
|
||||||
|
|
||||||
|
1. Build the project:
|
||||||
|
```bash
|
||||||
|
mvn clean install
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Start the server:
|
||||||
|
```bash
|
||||||
|
mvn compile exec:java -pl server -am
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Start the client (in a new terminal):
|
||||||
|
```bash
|
||||||
|
mvn compile exec:java -pl game -am
|
||||||
|
```
|
||||||
|
|
||||||
|
## Controls
|
||||||
|
|
||||||
|
* **Left Click**: Interact with objects and fight.
|
||||||
|
* **WASD**: Move the character.
|
||||||
|
* **CTRL**: Hold to run (sprint).
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package cz.jzitnik.common.socket.messages.game;
|
||||||
|
|
||||||
|
import cz.jzitnik.common.socket.SocketMessage;
|
||||||
|
|
||||||
|
public record GameWin() implements SocketMessage {
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package cz.jzitnik.common.socket.messages.game;
|
||||||
|
|
||||||
|
import cz.jzitnik.common.socket.SocketMessage;
|
||||||
|
|
||||||
|
public record PlayerDeath(int playerId) implements SocketMessage {
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ import cz.jzitnik.common.models.coordinates.RoomCords;
|
|||||||
import cz.jzitnik.client.utils.events.AbstractEventHandler;
|
import cz.jzitnik.client.utils.events.AbstractEventHandler;
|
||||||
import cz.jzitnik.client.utils.events.EventManager;
|
import cz.jzitnik.client.utils.events.EventManager;
|
||||||
import cz.jzitnik.client.utils.roomtasks.RoomTaskScheduler;
|
import cz.jzitnik.client.utils.roomtasks.RoomTaskScheduler;
|
||||||
|
import cz.jzitnik.common.socket.messages.game.GameWin;
|
||||||
import cz.jzitnik.common.socket.messages.room.MovePlayerRoom;
|
import cz.jzitnik.common.socket.messages.room.MovePlayerRoom;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
@@ -71,6 +72,10 @@ public class RoomChangeEventHandler extends AbstractEventHandler<RoomChangeEvent
|
|||||||
eventManager.emitEvent(new SendSocketMessageEvent(new MovePlayerRoom(newRoom.getId(), oldCords, playerCords)));
|
eventManager.emitEvent(new SendSocketMessageEvent(new MovePlayerRoom(newRoom.getId(), oldCords, playerCords)));
|
||||||
|
|
||||||
gameState.setCurrentRoom(newRoom);
|
gameState.setCurrentRoom(newRoom);
|
||||||
|
if (newRoom.isEnd()) {
|
||||||
|
eventManager.emitEvent(new SendSocketMessageEvent(new GameWin()));
|
||||||
|
} else {
|
||||||
scheduler.schedule(() -> roomTaskScheduler.setupNewSchedulers(newRoom), 200, TimeUnit.MILLISECONDS);
|
scheduler.schedule(() -> roomTaskScheduler.setupNewSchedulers(newRoom), 200, TimeUnit.MILLISECONDS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -37,6 +37,9 @@ public class GameRoom {
|
|||||||
@JsonProperty("requirement")
|
@JsonProperty("requirement")
|
||||||
private Requirement requirement;
|
private Requirement requirement;
|
||||||
|
|
||||||
|
@JsonProperty("end")
|
||||||
|
private boolean end;
|
||||||
|
|
||||||
private GameRoom left;
|
private GameRoom left;
|
||||||
private GameRoom right;
|
private GameRoom right;
|
||||||
private GameRoom up;
|
private GameRoom up;
|
||||||
|
|||||||
@@ -64,6 +64,8 @@ public class Player implements GamePlayer {
|
|||||||
public boolean dealDamage(int amount, DependencyManager dependencyManager) {
|
public boolean dealDamage(int amount, DependencyManager dependencyManager) {
|
||||||
if (health - amount <= 0) {
|
if (health - amount <= 0) {
|
||||||
health = 0;
|
health = 0;
|
||||||
|
EventManager eventManager = dependencyManager.getDependencyOrThrow(EventManager.class);
|
||||||
|
eventManager.emitEvent(new cz.jzitnik.client.events.SendSocketMessageEvent(new cz.jzitnik.common.socket.messages.game.PlayerDeath(id)));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package cz.jzitnik.client.game.items.types;
|
||||||
|
|
||||||
|
public class BeastSkin implements ItemType<BeastSkin> {
|
||||||
|
@Override
|
||||||
|
public Class<BeastSkin> getItemType() {
|
||||||
|
return BeastSkin.class;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,7 +12,9 @@ import cz.jzitnik.client.game.items.types.weapons.Sword;
|
|||||||
@JsonSubTypes({
|
@JsonSubTypes({
|
||||||
@JsonSubTypes.Type(value = Food.class, name = "food"),
|
@JsonSubTypes.Type(value = Food.class, name = "food"),
|
||||||
@JsonSubTypes.Type(value = Sword.class, name = "weapon_sword"),
|
@JsonSubTypes.Type(value = Sword.class, name = "weapon_sword"),
|
||||||
@JsonSubTypes.Type(value = Junk.class, name = "junk")
|
@JsonSubTypes.Type(value = Junk.class, name = "junk"),
|
||||||
|
@JsonSubTypes.Type(value = Key.class, name = "key"),
|
||||||
|
@JsonSubTypes.Type(value = BeastSkin.class, name = "beast_skin"),
|
||||||
})
|
})
|
||||||
public interface ItemType<T> {
|
public interface ItemType<T> {
|
||||||
Class<T> getItemType();
|
Class<T> getItemType();
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package cz.jzitnik.client.game.items.types;
|
||||||
|
|
||||||
|
public class Key implements ItemType<Key> {
|
||||||
|
@Override
|
||||||
|
public Class<Key> getItemType() {
|
||||||
|
return Key.class;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package cz.jzitnik.client.game.setup.scenes;
|
||||||
|
|
||||||
|
import cz.jzitnik.client.screens.DeathScreen;
|
||||||
|
import cz.jzitnik.client.screens.Screen;
|
||||||
|
import cz.jzitnik.client.screens.scenes.Scene;
|
||||||
|
|
||||||
|
public class DeathScene extends Scene {
|
||||||
|
public DeathScene() {
|
||||||
|
super(new Screen[]{new DeathScreen()}, new OnEndAction.Repeat());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package cz.jzitnik.client.game.setup.scenes;
|
||||||
|
|
||||||
|
import cz.jzitnik.client.screens.WinScreen;
|
||||||
|
import cz.jzitnik.client.screens.Screen;
|
||||||
|
import cz.jzitnik.client.screens.scenes.Scene;
|
||||||
|
|
||||||
|
public class WinScene extends Scene {
|
||||||
|
public WinScene() {
|
||||||
|
super(new Screen[]{new WinScreen()}, new OnEndAction.Repeat());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package cz.jzitnik.client.screens;
|
||||||
|
|
||||||
|
import com.googlecode.lanterna.SGR;
|
||||||
|
import com.googlecode.lanterna.TextColor;
|
||||||
|
import com.googlecode.lanterna.graphics.TextGraphics;
|
||||||
|
import com.googlecode.lanterna.screen.TerminalScreen;
|
||||||
|
import cz.jzitnik.client.annotations.injectors.InjectState;
|
||||||
|
import cz.jzitnik.client.events.KeyboardPressEvent;
|
||||||
|
import cz.jzitnik.client.events.MouseAction;
|
||||||
|
import cz.jzitnik.client.states.TerminalState;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class DeathScreen extends Screen {
|
||||||
|
@InjectState
|
||||||
|
private TerminalState terminalState;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void fullRender() {
|
||||||
|
TerminalScreen screen = terminalState.getTerminalScreen();
|
||||||
|
screen.clear();
|
||||||
|
TextGraphics tg = terminalState.getTextGraphics();
|
||||||
|
|
||||||
|
int termWidth = screen.getTerminalSize().getColumns();
|
||||||
|
int termHeight = screen.getTerminalSize().getRows();
|
||||||
|
|
||||||
|
String message = "GAME OVER";
|
||||||
|
tg.setForegroundColor(TextColor.ANSI.RED);
|
||||||
|
tg.enableModifiers(SGR.BOLD);
|
||||||
|
tg.putString((termWidth - message.length()) / 2, termHeight / 2, message);
|
||||||
|
|
||||||
|
try {
|
||||||
|
screen.refresh(com.googlecode.lanterna.screen.Screen.RefreshType.COMPLETE);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleMouseAction(MouseAction event) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleKeyboardAction(KeyboardPressEvent event) {
|
||||||
|
}
|
||||||
|
}
|
||||||
46
game/src/main/java/cz/jzitnik/client/screens/WinScreen.java
Normal file
46
game/src/main/java/cz/jzitnik/client/screens/WinScreen.java
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
package cz.jzitnik.client.screens;
|
||||||
|
|
||||||
|
import com.googlecode.lanterna.SGR;
|
||||||
|
import com.googlecode.lanterna.TextColor;
|
||||||
|
import com.googlecode.lanterna.graphics.TextGraphics;
|
||||||
|
import com.googlecode.lanterna.screen.TerminalScreen;
|
||||||
|
import cz.jzitnik.client.annotations.injectors.InjectState;
|
||||||
|
import cz.jzitnik.client.events.KeyboardPressEvent;
|
||||||
|
import cz.jzitnik.client.events.MouseAction;
|
||||||
|
import cz.jzitnik.client.states.TerminalState;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class WinScreen extends Screen {
|
||||||
|
@InjectState
|
||||||
|
private TerminalState terminalState;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void fullRender() {
|
||||||
|
TerminalScreen screen = terminalState.getTerminalScreen();
|
||||||
|
screen.clear();
|
||||||
|
TextGraphics tg = terminalState.getTextGraphics();
|
||||||
|
|
||||||
|
int termWidth = screen.getTerminalSize().getColumns();
|
||||||
|
int termHeight = screen.getTerminalSize().getRows();
|
||||||
|
|
||||||
|
String message = "YOU WON!";
|
||||||
|
tg.setForegroundColor(TextColor.ANSI.GREEN);
|
||||||
|
tg.enableModifiers(SGR.BOLD);
|
||||||
|
tg.putString((termWidth - message.length()) / 2, termHeight / 2, message);
|
||||||
|
|
||||||
|
try {
|
||||||
|
screen.refresh(com.googlecode.lanterna.screen.Screen.RefreshType.COMPLETE);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleMouseAction(MouseAction event) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleKeyboardAction(KeyboardPressEvent event) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package cz.jzitnik.client.socket.events;
|
||||||
|
|
||||||
|
import cz.jzitnik.client.annotations.SocketEventHandler;
|
||||||
|
import cz.jzitnik.client.annotations.injectors.InjectDependency;
|
||||||
|
import cz.jzitnik.client.annotations.injectors.InjectState;
|
||||||
|
import cz.jzitnik.client.game.GameState;
|
||||||
|
import cz.jzitnik.client.game.setup.scenes.WinScene;
|
||||||
|
import cz.jzitnik.client.socket.AbstractSocketEventHandler;
|
||||||
|
import cz.jzitnik.common.socket.messages.game.GameWin;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@SocketEventHandler(GameWin.class)
|
||||||
|
public class GameWinHandler extends AbstractSocketEventHandler<GameWin> {
|
||||||
|
@InjectState
|
||||||
|
private GameState gameState;
|
||||||
|
|
||||||
|
@InjectDependency
|
||||||
|
private cz.jzitnik.client.utils.roomtasks.RoomTaskScheduler roomTaskScheduler;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(GameWin event) {
|
||||||
|
log.debug("Game won!");
|
||||||
|
roomTaskScheduler.finalShutdown();
|
||||||
|
WinScene winScene = new WinScene();
|
||||||
|
gameState.setScreen(winScene);
|
||||||
|
try {
|
||||||
|
Thread.sleep(500);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
winScene.fullRender();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package cz.jzitnik.client.socket.events;
|
||||||
|
|
||||||
|
import cz.jzitnik.client.annotations.SocketEventHandler;
|
||||||
|
import cz.jzitnik.client.annotations.injectors.InjectDependency;
|
||||||
|
import cz.jzitnik.client.annotations.injectors.InjectState;
|
||||||
|
import cz.jzitnik.client.game.GameState;
|
||||||
|
import cz.jzitnik.client.game.setup.scenes.DeathScene;
|
||||||
|
import cz.jzitnik.client.socket.AbstractSocketEventHandler;
|
||||||
|
import cz.jzitnik.client.utils.DependencyManager;
|
||||||
|
import cz.jzitnik.common.socket.messages.game.PlayerDeath;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@SocketEventHandler(PlayerDeath.class)
|
||||||
|
public class PlayerDeathHandler extends AbstractSocketEventHandler<PlayerDeath> {
|
||||||
|
@InjectState
|
||||||
|
private GameState gameState;
|
||||||
|
|
||||||
|
@InjectDependency
|
||||||
|
private DependencyManager dependencyManager;
|
||||||
|
|
||||||
|
@InjectDependency
|
||||||
|
private cz.jzitnik.client.utils.roomtasks.RoomTaskScheduler roomTaskScheduler;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(PlayerDeath event) {
|
||||||
|
log.debug("Player death: {}", event.playerId());
|
||||||
|
roomTaskScheduler.finalShutdown();
|
||||||
|
DeathScene deathScene = new DeathScene();
|
||||||
|
dependencyManager.inject(deathScene);
|
||||||
|
gameState.setScreen(deathScene);
|
||||||
|
deathScene.fullRender();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -75,15 +75,15 @@
|
|||||||
answers:
|
answers:
|
||||||
- answer: "I have it"
|
- answer: "I have it"
|
||||||
requirement:
|
requirement:
|
||||||
item: "quest_item_boss_skin"
|
item: "BeastSkin"
|
||||||
dialog:
|
dialog:
|
||||||
text: "Well done. Here is the key."
|
text: "Well done. Here is the key."
|
||||||
onEnd:
|
onEnd:
|
||||||
type: "give_item"
|
type: "give_item"
|
||||||
item:
|
item:
|
||||||
id: 800
|
id: 800
|
||||||
name: "Something"
|
name: "Key"
|
||||||
type: { name: "junk" }
|
type: { name: "key" }
|
||||||
texture: "APPLE"
|
texture: "APPLE"
|
||||||
then: { type: end }
|
then: { type: end }
|
||||||
- answer: "Not yet"
|
- answer: "Not yet"
|
||||||
@@ -114,7 +114,7 @@
|
|||||||
itemsDrops:
|
itemsDrops:
|
||||||
- id: 200
|
- id: 200
|
||||||
name: "Beast Skin"
|
name: "Beast Skin"
|
||||||
type: { name: "junk" }
|
type: { name: "beast_skin" }
|
||||||
texture: "BOSS_SKIN"
|
texture: "BOSS_SKIN"
|
||||||
tasks:
|
tasks:
|
||||||
- type: "following_player"
|
- type: "following_player"
|
||||||
@@ -137,10 +137,8 @@
|
|||||||
- id: "final_room"
|
- id: "final_room"
|
||||||
texture: "ROOM6"
|
texture: "ROOM6"
|
||||||
requirement:
|
requirement:
|
||||||
item: "quest_item_final_key"
|
item: "Key"
|
||||||
#objects:
|
end: true
|
||||||
# - objectType: "exit"
|
|
||||||
# cords: { x: 140, y: 40 }
|
|
||||||
west: null
|
west: null
|
||||||
east: null
|
east: null
|
||||||
north: null
|
north: null
|
||||||
@@ -171,7 +169,7 @@
|
|||||||
updateRateMs: 700
|
updateRateMs: 700
|
||||||
west: "filler_2"
|
west: "filler_2"
|
||||||
east: "spawn"
|
east: "spawn"
|
||||||
north: null
|
north: "empty_c"
|
||||||
south: "filler_deadend_1"
|
south: "filler_deadend_1"
|
||||||
|
|
||||||
|
|
||||||
@@ -200,7 +198,7 @@
|
|||||||
west: "filler_c_west"
|
west: "filler_c_west"
|
||||||
east: null
|
east: null
|
||||||
north: "boss"
|
north: "boss"
|
||||||
south: null
|
south: "empty_a"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -243,7 +241,7 @@
|
|||||||
|
|
||||||
- id: "filler_1b"
|
- id: "filler_1b"
|
||||||
texture: "ROOM1"
|
texture: "ROOM1"
|
||||||
west: "filler_7"
|
west: "filler_1"
|
||||||
east: null
|
east: null
|
||||||
north: null
|
north: null
|
||||||
south: null
|
south: null
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package cz.jzitnik.server.events.handlers;
|
||||||
|
|
||||||
|
import cz.jzitnik.common.socket.messages.game.GameWin;
|
||||||
|
import cz.jzitnik.server.annotations.EventHandler;
|
||||||
|
import cz.jzitnik.server.context.GlobalContext;
|
||||||
|
import cz.jzitnik.server.events.AbstractEventHandler;
|
||||||
|
import cz.jzitnik.server.game.Client;
|
||||||
|
|
||||||
|
@EventHandler(GameWin.class)
|
||||||
|
public class GameWinHandler extends AbstractEventHandler<GameWin> {
|
||||||
|
public GameWinHandler(GlobalContext globalContext) {
|
||||||
|
super(globalContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(GameWin event, Client client) {
|
||||||
|
for (Client player : client.getGame().getPlayers()) {
|
||||||
|
player.getSession().sendMessage(new GameWin());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package cz.jzitnik.server.events.handlers;
|
||||||
|
|
||||||
|
import cz.jzitnik.common.socket.messages.game.PlayerDeath;
|
||||||
|
import cz.jzitnik.server.annotations.EventHandler;
|
||||||
|
import cz.jzitnik.server.context.GlobalContext;
|
||||||
|
import cz.jzitnik.server.events.AbstractEventHandler;
|
||||||
|
import cz.jzitnik.server.game.Client;
|
||||||
|
|
||||||
|
@EventHandler(PlayerDeath.class)
|
||||||
|
public class PlayerDeathHandler extends AbstractEventHandler<PlayerDeath> {
|
||||||
|
public PlayerDeathHandler(GlobalContext globalContext) {
|
||||||
|
super(globalContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handle(PlayerDeath event, Client client) {
|
||||||
|
for (Client player : client.getGame().getPlayers()) {
|
||||||
|
player.getSession().sendMessage(new PlayerDeath(event.playerId()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user