feat: Implemented moving items between players

This commit is contained in:
jzitnik-dev 2025-01-02 13:38:15 +01:00
parent 5dc191c842
commit d8969df0d3
Signed by: jzitnik
GPG Key ID: C577A802A6AF4EF3
12 changed files with 207 additions and 2 deletions

View File

@ -6,7 +6,7 @@ Jednoduchá multiplayer hra napsaná v Javě.
Backend je napsaný v Javě pomocí Spring. Jedná se o jednoduchý HTTP server. Backend je napsaný v Javě pomocí Spring. Jedná se o jednoduchý HTTP server.
**Ano jsem debil, využít relační databázi nebyl rozhodě dobrý nápad. Došlo mi to uprostřed programování a už se mi nechtělo vracet zpět.** **Ano jsem debil, využít relační databázi nebyl rozhodně dobrý nápad. Došlo mi to uprostřed programování a už se mi nechtělo vracet zpět.**
### Enviromental variables ### Enviromental variables

View File

@ -1,13 +1,17 @@
package cz.jzitnik.chronos.controllers; package cz.jzitnik.chronos.controllers;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import cz.jzitnik.chronos.entities.Item; import cz.jzitnik.chronos.entities.Item;
import cz.jzitnik.chronos.entities.Message; import cz.jzitnik.chronos.entities.Message;
import cz.jzitnik.chronos.entities.MessageType; import cz.jzitnik.chronos.entities.MessageType;
import cz.jzitnik.chronos.entities.Player; import cz.jzitnik.chronos.entities.Player;
import cz.jzitnik.chronos.payload.errors.FullInventoryError;
import cz.jzitnik.chronos.payload.errors.ItemNotUsableException; import cz.jzitnik.chronos.payload.errors.ItemNotUsableException;
import cz.jzitnik.chronos.payload.errors.NotFoundError; import cz.jzitnik.chronos.payload.errors.NotFoundError;
import cz.jzitnik.chronos.payload.requests.PlayerNameRequest; import cz.jzitnik.chronos.payload.requests.PlayerNameRequest;
import cz.jzitnik.chronos.payload.responses.InventoryFullResponse; import cz.jzitnik.chronos.payload.responses.InventoryFullResponse;
import cz.jzitnik.chronos.payload.responses.ItemMoveMessageResponse;
import cz.jzitnik.chronos.payload.responses.UnifiedResponse; import cz.jzitnik.chronos.payload.responses.UnifiedResponse;
import cz.jzitnik.chronos.repository.GameRepository; import cz.jzitnik.chronos.repository.GameRepository;
import cz.jzitnik.chronos.repository.ItemRepository; import cz.jzitnik.chronos.repository.ItemRepository;
@ -19,6 +23,8 @@ import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.util.List;
@CrossOrigin(origins = "*", maxAge = 3600) @CrossOrigin(origins = "*", maxAge = 3600)
@RestController @RestController
@RequestMapping("/game/players") @RequestMapping("/game/players")
@ -116,4 +122,58 @@ public class PlayerController {
return ResponseEntity.ok(UnifiedResponse.success(item)); return ResponseEntity.ok(UnifiedResponse.success(item));
} }
@GetMapping
@CheckUser
public ResponseEntity<UnifiedResponse<List<Player>, Error>> getAllPlayers(@RequestParam String playerKey) {
var player = playerRepository.findByPlayerKey(playerKey).get();
var game = player.getGame();
var players = game.getPlayers();
return ResponseEntity.ok(UnifiedResponse.success(players));
}
@PostMapping("/send_item")
@CheckUser
public ResponseEntity<UnifiedResponse<Object, Error>> sendItem(@RequestParam String playerKey, @RequestParam Long itemId, @RequestParam Long playerId) {
var player = playerRepository.findByPlayerKey(playerKey).get();
var player2Optional = playerRepository.findById(playerId);
if (player2Optional.isEmpty()) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(UnifiedResponse.failure(new NotFoundError("Player with id " + playerId + " was not found!")));
}
var player2 = player2Optional.get();
var itemOptional = itemRepository.findById(itemId);
if (itemOptional.isEmpty()) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(UnifiedResponse.failure(new NotFoundError("Item with id " + itemId + " was not found!")));
}
var item = itemOptional.get();
if (item.getOwner() == null || !item.getOwner().getId().equals(player.getId())) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(UnifiedResponse.failure(new Error("You don't own this item!")));
}
if (player.getInventorySize() - player.getInventory().size() == 0) {
// Inventory is full
return ResponseEntity.status(HttpStatus.CONFLICT).body(UnifiedResponse.failure(new FullInventoryError("Player has full inventory!")));
}
item.setOwner(player2);
itemRepository.save(item);
ObjectMapper objectMapper = new ObjectMapper();
try {
var json = objectMapper.writeValueAsString(new ItemMoveMessageResponse(item.getItemType(), player2.getName()));
var message = new Message(player, json, MessageType.ITEM_MOVED);
player.getMessages().add(message);
playerRepository.save(player);
return ResponseEntity.ok(UnifiedResponse.success(null));
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
} }

View File

@ -7,5 +7,6 @@ public enum MessageType {
LOST_ITEM, LOST_ITEM,
ITEM_USED, ITEM_USED,
KEY_FRAGMENT_HANDED_OVER, KEY_FRAGMENT_HANDED_OVER,
ITEM_MOVED,
JOINED, JOINED,
} }

View File

@ -0,0 +1,7 @@
package cz.jzitnik.chronos.payload.errors;
public class FullInventoryError extends Error {
public FullInventoryError(String message) {
super(message);
}
}

View File

@ -0,0 +1,14 @@
package cz.jzitnik.chronos.payload.responses;
import cz.jzitnik.chronos.entities.ItemType;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
@NoArgsConstructor
@AllArgsConstructor
@Getter
public class ItemMoveMessageResponse {
private ItemType itemType;
private String playerToName;
}

View File

@ -138,4 +138,16 @@ public interface ApiService {
Call<UnifiedResponse<GameWonResponse, Error>> gameWon( Call<UnifiedResponse<GameWonResponse, Error>> gameWon(
@Query("playerKey") String playerKey @Query("playerKey") String playerKey
); );
@GET("game/players")
Call<UnifiedResponse<List<Player>, Error>> getAllPlayers(
@Query("playerKey") String playerKey
);
@POST("game/players/send_item")
Call<UnifiedResponse<Object, Error>> sendItem(
@Query("playerKey") String playerKey,
@Query("itemId") Long itemId,
@Query("playerId") Long playerId
);
} }

View File

@ -0,0 +1,10 @@
package cz.jzitnik.api.responses;
import cz.jzitnik.api.types.ItemType;
import lombok.Getter;
@Getter
public class ItemMoveMessageResponse {
private ItemType itemType;
private String playerToName;
}

View File

@ -3,6 +3,7 @@ package cz.jzitnik.api.types;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import cz.jzitnik.api.ApiService; import cz.jzitnik.api.ApiService;
import cz.jzitnik.api.responses.ItemMoveMessageResponse;
import cz.jzitnik.utils.Cli; import cz.jzitnik.utils.Cli;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
@ -85,6 +86,17 @@ public class Message {
yield "Hráč " + Cli.Colors.BLUE + author.getName() + Cli.Colors.RESET + " využil item unknown."; yield "Hráč " + Cli.Colors.BLUE + author.getName() + Cli.Colors.RESET + " využil item unknown.";
} }
} }
case ITEM_MOVED -> {
ObjectMapper objectMapper = new ObjectMapper();
try {
var data = objectMapper.readValue(content, ItemMoveMessageResponse.class);
yield "Hráč " + Cli.Colors.BLUE + author.getName() + Cli.Colors.RESET + " dal " + Cli.Colors.BLUE + data.getItemType() + Cli.Colors.RESET + " hráči " + Cli.Colors.BLUE + data.getPlayerToName() + Cli.Colors.RESET + ".";
} catch (JsonProcessingException e) {
yield "Hráč " + Cli.Colors.BLUE + author.getName() + Cli.Colors.RESET + " dal neznámý item neznámému hráči.";
}
}
case KEY_FRAGMENT_HANDED_OVER -> "Hráč " + Cli.Colors.BLUE + author.getName() + Cli.Colors.RESET + " odevzdal " + Cli.Colors.BLUE + "Fragment klíče" + Cli.Colors.RESET + "."; case KEY_FRAGMENT_HANDED_OVER -> "Hráč " + Cli.Colors.BLUE + author.getName() + Cli.Colors.RESET + " odevzdal " + Cli.Colors.BLUE + "Fragment klíče" + Cli.Colors.RESET + ".";
}; };
} }

View File

@ -7,5 +7,6 @@ public enum MessageType {
LOST_ITEM, LOST_ITEM,
ITEM_USED, ITEM_USED,
KEY_FRAGMENT_HANDED_OVER, KEY_FRAGMENT_HANDED_OVER,
ITEM_MOVED,
JOINED, JOINED,
} }

View File

@ -9,6 +9,7 @@ import lombok.Setter;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -21,6 +22,7 @@ public class CommandPalette {
USER_PROFILE, USER_PROFILE,
CHAT_OPEN, CHAT_OPEN,
CHAT_SEND, CHAT_SEND,
PLAYER_LIST_OPEN,
CHECK_WINNABLE, CHECK_WINNABLE,
EXIT, EXIT,
} }
@ -68,7 +70,7 @@ public class CommandPalette {
var configPath = ConfigPathProvider.getPath(); var configPath = ConfigPathProvider.getPath();
File file = new File(configPath); File file = new File(Path.of(configPath, "config.json").toString());
file.delete(); file.delete();
System.exit(0); System.exit(0);
@ -81,6 +83,7 @@ public class CommandPalette {
allCommands.add(new Command("Otevřít profil uživatele", CommandType.USER_PROFILE)); allCommands.add(new Command("Otevřít profil uživatele", CommandType.USER_PROFILE));
allCommands.add(new Command("Otevřít chat", CommandType.CHAT_OPEN)); allCommands.add(new Command("Otevřít chat", CommandType.CHAT_OPEN));
allCommands.add(new Command("Odeslat zprávu do chatu", CommandType.CHAT_SEND)); allCommands.add(new Command("Odeslat zprávu do chatu", CommandType.CHAT_SEND));
allCommands.add(new Command("Zobrazit list všech hráčů", CommandType.PLAYER_LIST_OPEN));
allCommands.add(new Command("Zkontrolovat vyhratelnost hry", CommandType.CHECK_WINNABLE)); allCommands.add(new Command("Zkontrolovat vyhratelnost hry", CommandType.CHECK_WINNABLE));
allCommands.add(new Command("Odejít ze hry", CommandType.EXIT)); allCommands.add(new Command("Odejít ze hry", CommandType.EXIT));
@ -148,6 +151,12 @@ public class CommandPalette {
Cli.info("Hra je dost možná nevyhratelná!"); Cli.info("Hra je dost možná nevyhratelná!");
} }
yield displayIndex();
}
case PLAYER_LIST_OPEN -> {
var playerList = new PlayerList(apiService, playerKey);
playerList.display();
yield displayIndex(); yield displayIndex();
} }
}; };

View File

@ -21,6 +21,11 @@ public class Inventory {
System.out.println(); System.out.println();
Cli.info("Váš inventář:"); Cli.info("Váš inventář:");
if (player.getInventory().isEmpty()) {
System.out.println("Inventář je prázdný!");
return;
}
// Create a map to group items by ItemType and count occurrences // Create a map to group items by ItemType and count occurrences
Map<ItemType, Integer> itemCounts = new HashMap<>(); Map<ItemType, Integer> itemCounts = new HashMap<>();
List<Item> usableItemList = new ArrayList<>(); List<Item> usableItemList = new ArrayList<>();

View File

@ -0,0 +1,74 @@
package cz.jzitnik.game;
import cz.jzitnik.api.ApiService;
import cz.jzitnik.api.types.Item;
import cz.jzitnik.api.types.Player;
import cz.jzitnik.utils.Cli;
import lombok.AllArgsConstructor;
import org.apache.hc.core5.http.HttpStatus;
import java.io.IOException;
import java.util.Arrays;
@AllArgsConstructor
public class PlayerList {
private ApiService apiService;
private String playerKey;
public void display() throws IOException {
var response = apiService.getAllPlayers(playerKey).execute();
var players = response.body().getData().get();
Cli.printHeader("List všech hráčů");
for (Player player1 : players) {
System.out.println("Jméno: " + Cli.Colors.BLUE + player1.getName() + Cli.Colors.RESET + ", V místnosti: " + Cli.Colors.BLUE + player1.getCurrentRoom() + Cli.Colors.RESET);
}
if (players.size() == 1) {
// There is only one player so can't send item to another player
return;
}
System.out.println("\n");
var options = new String[] { "Předat item hráči", "Zavřít list hráčů"};
var selectedIndex = Cli.selectOptionIndex(Arrays.asList(options));
if (selectedIndex == 1) {
return;
}
// Select item from inventory
var res = apiService.getMe(playerKey).execute();
var me = res.body().getData().get();
var inventory = me.getInventory();
if (inventory.isEmpty()) {
Cli.error("Nemáte žádné itemy v inventáři");
return;
}
var playerList = players.stream().filter(player -> !player.getId().equals(me.getId())).toList();
var playerOptions = playerList.stream().map(Player::getName).toList();
System.out.println("\n\nVyberte hráče kterému chcete dát item");
var selectedPlayerIndex = Cli.selectOptionIndex(playerOptions);
var selectedPlayer = playerList.get(selectedPlayerIndex);
System.out.println("\n\nVyberte item který chcete dát hráči");
var items = inventory.stream().map(Item::toString).toList();
var selectedItemIndex = Cli.selectOptionIndex(items);
var selectedItem = inventory.get(selectedItemIndex);
var finalResponse = apiService.sendItem(playerKey, selectedItem.getId(), selectedPlayer.getId()).execute();
if (finalResponse.code() == HttpStatus.SC_CONFLICT) {
Cli.error("Hráč " + Cli.Colors.BLUE + selectedPlayer.getName() + Cli.Colors.RESET + " má plný inventář!");
return;
}
Cli.success("Item byl úspěšně předán hráči " + Cli.Colors.BLUE + selectedPlayer.getName() + Cli.Colors.RESET + ".");
}
}