diff --git a/squirrelbattle/display/display_manager.py b/squirrelbattle/display/display_manager.py index 743baef..203fc82 100644 --- a/squirrelbattle/display/display_manager.py +++ b/squirrelbattle/display/display_manager.py @@ -8,7 +8,7 @@ from squirrelbattle.display.mapdisplay import MapDisplay from squirrelbattle.display.messagedisplay import MessageDisplay from squirrelbattle.display.statsdisplay import StatsDisplay from squirrelbattle.display.menudisplay import MainMenuDisplay, \ - InventoryDisplay, SettingsMenuDisplay + PlayerInventoryDisplay, StoreInventoryDisplay, SettingsMenuDisplay from squirrelbattle.display.logsdisplay import LogsDisplay from squirrelbattle.display.texturepack import TexturePack from typing import Any, List @@ -25,7 +25,8 @@ class DisplayManager: self.mapdisplay = MapDisplay(screen, pack) self.statsdisplay = StatsDisplay(screen, pack) self.logsdisplay = LogsDisplay(screen, pack) - self.inventorydisplay = InventoryDisplay(screen, pack) + self.playerinventorydisplay = PlayerInventoryDisplay(screen, pack) + self.storeinventorydisplay = StoreInventoryDisplay(screen, pack) self.mainmenudisplay = MainMenuDisplay(self.game.main_menu, screen, pack) self.settingsmenudisplay = SettingsMenuDisplay(screen, pack) @@ -34,7 +35,9 @@ class DisplayManager: self.vbar = VerticalSplit(screen, pack) self.displays = [self.statsdisplay, self.mapdisplay, self.mainmenudisplay, self.settingsmenudisplay, - self.logsdisplay, self.messagedisplay] + self.logsdisplay, self.messagedisplay, + self.playerinventorydisplay, + self.storeinventorydisplay] self.update_game_components() def handle_display_action(self, action: DisplayActions, *params) -> None: @@ -50,7 +53,10 @@ class DisplayManager: d.pack = TexturePack.get_pack(self.game.settings.TEXTURE_PACK) self.mapdisplay.update_map(self.game.map) self.statsdisplay.update_player(self.game.player) - self.inventorydisplay.update_menu(self.game.inventory_menu) + self.game.inventory_menu.update_player(self.game.player) + self.game.store_menu.update_merchant(self.game.player) + self.playerinventorydisplay.update_menu(self.game.inventory_menu) + self.storeinventorydisplay.update_menu(self.game.store_menu) self.settingsmenudisplay.update_menu(self.game.settings_menu) self.logsdisplay.update_logs(self.game.logs) self.messagedisplay.update_message(self.game.message) @@ -71,7 +77,8 @@ class DisplayManager: displays = [] if self.game.state == GameMode.PLAY \ - or self.game.state == GameMode.INVENTORY: + or self.game.state == GameMode.INVENTORY \ + or self.game.state == GameMode.STORE: # The map pad has already the good size self.mapdisplay.refresh(0, 0, self.rows * 4 // 5, self.mapdisplay.pack.tile_width @@ -89,11 +96,15 @@ class DisplayManager: self.hbar, self.vbar] if self.game.state == GameMode.INVENTORY: - self.inventorydisplay.refresh(self.rows // 10, - self.cols // 2, - 8 * self.rows // 10, - 2 * self.cols // 5) + self.playerinventorydisplay.refresh( + self.rows // 10, self.cols // 2, + 8 * self.rows // 10, 2 * self.cols // 5) displays.append(self.inventorydisplay) + elif self.game.state == GameMode.STORE: + self.storeinventorydisplay.refresh( + self.rows // 10, self.cols // 2, + 8 * self.rows // 10, 2 * self.cols // 5) + displays.append(self.storeinventorydisplay) elif self.game.state == GameMode.MAINMENU: self.mainmenudisplay.refresh(0, 0, self.rows, self.cols) displays.append(self.mainmenudisplay) diff --git a/squirrelbattle/display/menudisplay.py b/squirrelbattle/display/menudisplay.py index eee4606..8c2cce9 100644 --- a/squirrelbattle/display/menudisplay.py +++ b/squirrelbattle/display/menudisplay.py @@ -13,6 +13,9 @@ from ..translations import gettext as _ class MenuDisplay(Display): + """ + A class to display the menu objects + """ position: int def __init__(self, *args, **kwargs): @@ -73,6 +76,9 @@ class MenuDisplay(Display): class SettingsMenuDisplay(MenuDisplay): + """ + A class to display specifically a settingsmenu object + """ @property def values(self) -> List[str]: return [_(a[1][1]) + (" : " @@ -83,6 +89,9 @@ class SettingsMenuDisplay(MenuDisplay): class MainMenuDisplay(Display): + """ + A class to display specifically a mainmenu object + """ def __init__(self, menu: MainMenu, *args): super().__init__(*args) self.menu = menu @@ -118,11 +127,12 @@ class MainMenuDisplay(Display): self.menudisplay.handle_click(y - menuy, x - menux, game) -class InventoryDisplay(MenuDisplay): +class PlayerInventoryDisplay(MenuDisplay): + message = _("== INVENTORY ==") + def update_pad(self) -> None: - message = _("== INVENTORY ==") - self.addstr(self.pad, 0, (self.width - len(message)) // 2, message, - curses.A_BOLD | curses.A_ITALIC) + self.addstr(self.pad, 0, (self.width - len(self.message)) // 2, + self.message, curses.A_BOLD | curses.A_ITALIC) for i, item in enumerate(self.menu.values): rep = self.pack[item.name.upper()] selection = f"[{rep}]" if i == self.menu.position else f" {rep} " @@ -143,3 +153,32 @@ class InventoryDisplay(MenuDisplay): """ self.menu.position = max(0, min(len(self.menu.values) - 1, y - 3)) game.handle_key_pressed(KeyValues.ENTER) + + +class StoreInventoryDisplay(MenuDisplay): + message = _("== STALL ==") + + def update_pad(self) -> None: + self.addstr(self.pad, 0, (self.width - len(self.message)) // 2, + self.message, curses.A_BOLD | curses.A_ITALIC) + for i, item in enumerate(self.menu.values): + rep = self.pack[item.name.upper()] + selection = f"[{rep}]" if i == self.menu.position else f" {rep} " + self.addstr(self.pad, 2 + i, 0, selection + + " " + item.translated_name.capitalize() + + ": " + str(item.price) + " Hazels") + + @property + def truewidth(self) -> int: + return max(1, self.height if hasattr(self, "height") else 10) + + @property + def trueheight(self) -> int: + return 2 + super().trueheight + + def handle_click(self, y: int, x: int, game: Game) -> None: + """ + We can select a menu item with the mouse. + """ + self.menu.position = max(0, min(len(self.menu.values) - 1, y - 3)) + game.handle_key_pressed(KeyValues.ENTER) diff --git a/squirrelbattle/display/statsdisplay.py b/squirrelbattle/display/statsdisplay.py index ac1a89c..ec0c90a 100644 --- a/squirrelbattle/display/statsdisplay.py +++ b/squirrelbattle/display/statsdisplay.py @@ -46,8 +46,11 @@ class StatsDisplay(Display): printed_items.append(item) self.addstr(self.pad, 8, 0, inventory_str) + self.addstr(self.pad, 9, 0, f"{self.pack.HAZELNUT} " + f"x{self.player.hazel}") + if self.player.dead: - self.addstr(self.pad, 10, 0, _("YOU ARE DEAD"), + self.addstr(self.pad, 11, 0, _("YOU ARE DEAD"), curses.A_BOLD | curses.A_BLINK | curses.A_STANDOUT | self.color_pair(3)) diff --git a/squirrelbattle/display/texturepack.py b/squirrelbattle/display/texturepack.py index 7fc4a9a..a6ec3af 100644 --- a/squirrelbattle/display/texturepack.py +++ b/squirrelbattle/display/texturepack.py @@ -14,10 +14,22 @@ class TexturePack: tile_bg_color: int entity_fg_color: int entity_bg_color: int + + BODY_SNATCH_POTION: str + BOMB: str + HEART: str + HEDGEHOG: str EMPTY: str - WALL: str FLOOR: str + HAZELNUT: str + MERCHANT: str PLAYER: str + RABBIT: str + SUNFLOWER: str + SWORD: str + TEDDY_BEAR: str + TIGER: str + WALL: str ASCII_PACK: "TexturePack" SQUIRREL_PACK: "TexturePack" @@ -46,17 +58,22 @@ TexturePack.ASCII_PACK = TexturePack( tile_bg_color=curses.COLOR_BLACK, entity_fg_color=curses.COLOR_WHITE, entity_bg_color=curses.COLOR_BLACK, - EMPTY=' ', - WALL='#', - FLOOR='.', - PLAYER='@', - HEDGEHOG='*', - HEART='❤', - BOMB='o', - RABBIT='Y', - TIGER='n', - TEDDY_BEAR='8', + BODY_SNATCH_POTION='S', + BOMB='o', + EMPTY=' ', + FLOOR='.', + HAZELNUT='¤', + HEART='❤', + HEDGEHOG='*', + MERCHANT='M', + PLAYER='@', + RABBIT='Y', + SUNFLOWER='I', + SWORD='\u2020', + TEDDY_BEAR='8', + TIGER='n', + WALL='#', ) TexturePack.SQUIRREL_PACK = TexturePack( @@ -66,15 +83,20 @@ TexturePack.SQUIRREL_PACK = TexturePack( tile_bg_color=curses.COLOR_BLACK, entity_fg_color=curses.COLOR_WHITE, entity_bg_color=curses.COLOR_WHITE, - EMPTY=' ', - WALL='🧱', - FLOOR='██', - PLAYER='🐿️ ️', - HEDGEHOG='🦔', - HEART='💜', - BOMB='💣', - RABBIT='🐇', - TIGER='🐅', - TEDDY_BEAR='🧸', + BODY_SNATCH_POTION='🔀', + BOMB='💣', + EMPTY=' ', + FLOOR='██', + HAZELNUT='🌰', + HEART='💜', + HEDGEHOG='🦔', + PLAYER='🐿️ ️', + MERCHANT='🦜', + RABBIT='🐇', + SUNFLOWER='🌻', + SWORD='🗡️', + TEDDY_BEAR='🧸', + TIGER='🐅', + WALL='🧱', ) diff --git a/squirrelbattle/entities/friendly.py b/squirrelbattle/entities/friendly.py new file mode 100644 index 0000000..3e965d5 --- /dev/null +++ b/squirrelbattle/entities/friendly.py @@ -0,0 +1,50 @@ +from ..interfaces import FriendlyEntity, InventoryHolder +from ..translations import gettext as _ +from .player import Player +from .items import Item +from random import choice + + +class Merchant(InventoryHolder, FriendlyEntity): + """ + The class for merchants in the dungeon + """ + def keys(self) -> list: + """ + Returns a friendly entitie's specific attributes + """ + return super().keys() + ["inventory", "hazel"] + + def __init__(self, name: str = "merchant", inventory: list = None, + hazel: int = 75, *args, **kwargs): + super().__init__(name=name, *args, **kwargs) + self.inventory = self.translate_inventory(inventory or []) + self.hazel = hazel + + if not self.inventory: + for i in range(5): + self.inventory.append(choice(Item.get_all_items())()) + + def talk_to(self, player: Player) -> str: + """ + This function is used to open the merchant's inventory in a menu, + and allow the player to buy/sell objects + """ + return _("I don't sell any squirrel") + + def change_hazel_balance(self, hz: int) -> None: + """ + Change the number of hazel the merchant has by hz. + """ + self.hazel += hz + + +class Sunflower(FriendlyEntity): + """ + A friendly sunflower + """ + dialogue_option = [_("Flower power!!"), _("The sun is warm today")] + + def __init__(self, maxhealth: int = 15, + *args, **kwargs) -> None: + super().__init__(name="sunflower", maxhealth=maxhealth, *args, **kwargs) diff --git a/squirrelbattle/entities/items.py b/squirrelbattle/entities/items.py index a1f3bd4..b115f8d 100644 --- a/squirrelbattle/entities/items.py +++ b/squirrelbattle/entities/items.py @@ -5,7 +5,7 @@ from random import choice, randint from typing import Optional from .player import Player -from ..interfaces import Entity, FightingEntity, Map +from ..interfaces import Entity, FightingEntity, Map, InventoryHolder from ..translations import gettext as _ @@ -14,13 +14,16 @@ class Item(Entity): A class for items """ held: bool - held_by: Optional[Player] + held_by: Optional[InventoryHolder] + price: int - def __init__(self, held: bool = False, held_by: Optional[Player] = None, - *args, **kwargs): + def __init__(self, held: bool = False, + held_by: Optional[InventoryHolder] = None, + price: int = 2, *args, **kwargs): super().__init__(*args, **kwargs) self.held = held self.held_by = held_by + self.price = price def drop(self) -> None: """ @@ -43,14 +46,14 @@ class Item(Entity): Indicates what should be done when the item is equipped. """ - def hold(self, player: "Player") -> None: + def hold(self, player: InventoryHolder) -> None: """ The item is taken from the floor and put into the inventory """ self.held = True self.held_by = player self.held_by.map.remove_entity(self) - player.inventory.append(self) + player.add_to_inventory(self) def save_state(self) -> dict: """ @@ -60,6 +63,25 @@ class Item(Entity): d["held"] = self.held return d + @staticmethod + def get_all_items() -> list: + return [BodySnatchPotion, Bomb, Heart, Sword] + + def be_sold(self, buyer: InventoryHolder, seller: InventoryHolder) -> bool: + """ + Does all necessary actions when an object is to be sold. + Is overwritten by some classes that cannot exist in the player's + inventory + """ + if buyer.hazel >= self.price: + self.hold(buyer) + seller.remove_from_inventory(self) + buyer.change_hazel_balance(-self.price) + seller.change_hazel_balance(self.price) + return True + else: + return False + class Heart(Item): """ @@ -67,16 +89,17 @@ class Heart(Item): """ healing: int - def __init__(self, name: str = "heart", healing: int = 5, *args, **kwargs): - super().__init__(name=name, *args, **kwargs) + def __init__(self, name: str = "heart", healing: int = 5, price: int = 3, + *args, **kwargs): + super().__init__(name=name, price=price, *args, **kwargs) self.healing = healing - def hold(self, player: "Player") -> None: + def hold(self, entity: InventoryHolder) -> None: """ When holding a heart, heal the player and don't put item in inventory. """ - player.health = min(player.maxhealth, player.health + self.healing) - self.map.remove_entity(self) + entity.health = min(entity.maxhealth, entity.health + self.healing) + entity.map.remove_entity(self) def save_state(self) -> dict: """ @@ -97,8 +120,8 @@ class Bomb(Item): tick: int def __init__(self, name: str = "bomb", damage: int = 5, - exploding: bool = False, *args, **kwargs): - super().__init__(name=name, *args, **kwargs) + exploding: bool = False, price: int = 4, *args, **kwargs): + super().__init__(name=name, price=price, *args, **kwargs) self.damage = damage self.exploding = exploding self.tick = 4 @@ -145,14 +168,43 @@ class Bomb(Item): return d +class Weapon(Item): + """ + Non-throwable items that improve player damage + """ + damage: int + + def __init__(self, damage: int = 3, *args, **kwargs): + super().__init__(*args, **kwargs) + self.damage = damage + + def save_state(self) -> dict: + """ + Saves the state of the weapon into a dictionary + """ + d = super().save_state() + d["damage"] = self.damage + return d + + +class Sword(Weapon): + """ + A basic weapon + """ + def __init__(self, name: str = "sword", price: int = 20, *args, **kwargs): + super().__init__(name=name, price=price, *args, **kwargs) + self.name = name + + class BodySnatchPotion(Item): """ The body-snatch potion allows to exchange all characteristics with a random other entity. """ - def __init__(self, name: str = "body_snatch_potion", *args, **kwargs): - super().__init__(name=name, *args, **kwargs) + def __init__(self, name: str = "body_snatch_potion", price: int = 14, + *args, **kwargs): + super().__init__(name=name, price=price, *args, **kwargs) def use(self) -> None: """ diff --git a/squirrelbattle/entities/player.py b/squirrelbattle/entities/player.py index 45e2bdf..19c8348 100644 --- a/squirrelbattle/entities/player.py +++ b/squirrelbattle/entities/player.py @@ -6,23 +6,22 @@ from queue import PriorityQueue from random import randint from typing import Dict, Tuple -from ..interfaces import FightingEntity +from ..interfaces import FightingEntity, InventoryHolder -class Player(FightingEntity): +class Player(InventoryHolder, FightingEntity): """ The class of the player """ current_xp: int = 0 max_xp: int = 10 - inventory: list paths: Dict[Tuple[int, int], Tuple[int, int]] def __init__(self, name: str = "player", maxhealth: int = 20, strength: int = 5, intelligence: int = 1, charisma: int = 1, dexterity: int = 1, constitution: int = 1, level: int = 1, current_xp: int = 0, max_xp: int = 10, inventory: list = None, - *args, **kwargs) \ + hazel: int = 42, *args, **kwargs) \ -> None: super().__init__(name=name, maxhealth=maxhealth, strength=strength, intelligence=intelligence, charisma=charisma, @@ -30,13 +29,9 @@ class Player(FightingEntity): level=level, *args, **kwargs) self.current_xp = current_xp self.max_xp = max_xp - self.inventory = inventory if inventory else list() - for i in range(len(self.inventory)): - if isinstance(self.inventory[i], dict): - entity_classes = self.get_all_entity_classes_in_a_dict() - item_class = entity_classes[self.inventory[i]["type"]] - self.inventory[i] = item_class(**self.inventory[i]) + self.inventory = self.translate_inventory(inventory or []) self.paths = dict() + self.hazel = hazel def move(self, y: int, x: int) -> None: """ @@ -149,5 +144,4 @@ class Player(FightingEntity): d = super().save_state() d["current_xp"] = self.current_xp d["max_xp"] = self.max_xp - d["inventory"] = [item.save_state() for item in self.inventory] return d diff --git a/squirrelbattle/enums.py b/squirrelbattle/enums.py index 11f5c17..c04011b 100644 --- a/squirrelbattle/enums.py +++ b/squirrelbattle/enums.py @@ -27,6 +27,7 @@ class GameMode(Enum): PLAY = auto() SETTINGS = auto() INVENTORY = auto() + STORE = auto() class KeyValues(Enum): @@ -44,6 +45,7 @@ class KeyValues(Enum): EQUIP = auto() DROP = auto() SPACE = auto() + CHAT = auto() @staticmethod def translate_key(key: str, settings: Settings) -> Optional["KeyValues"]: @@ -74,4 +76,6 @@ class KeyValues(Enum): return KeyValues.DROP elif key == ' ': return KeyValues.SPACE + elif key == settings.KEY_CHAT: + return KeyValues.CHAT return None diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index a37f1d2..558f25a 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -24,6 +24,7 @@ class Game: """ map: Map player: Player + screen: Any # display_actions is a display interface set by the bootstrapper display_actions: callable @@ -32,6 +33,7 @@ class Game: Init the game. """ self.state = GameMode.MAINMENU + self.waiting_for_friendly_key = False self.settings = Settings() self.settings.load_settings() self.settings.write_settings() @@ -40,6 +42,7 @@ class Game: self.settings_menu = menus.SettingsMenu() self.settings_menu.update_values(self.settings) self.inventory_menu = menus.InventoryMenu() + self.store_menu = menus.StoreMenu() self.logs = Logs() self.message = None @@ -87,13 +90,19 @@ class Game: return if self.state == GameMode.PLAY: - self.handle_key_pressed_play(key) + if self.waiting_for_friendly_key: + # The player requested to talk with a friendly entity + self.handle_friendly_entity_chat(key) + else: + self.handle_key_pressed_play(key) elif self.state == GameMode.INVENTORY: self.handle_key_pressed_inventory(key) elif self.state == GameMode.MAINMENU: self.handle_key_pressed_main_menu(key) elif self.state == GameMode.SETTINGS: self.settings_menu.handle_key_pressed(key, raw_key, self) + elif self.state == GameMode.STORE: + self.handle_key_pressed_store(key) self.display_actions(DisplayActions.REFRESH) def handle_key_pressed_play(self, key: KeyValues) -> None: @@ -116,6 +125,42 @@ class Game: self.state = GameMode.INVENTORY elif key == KeyValues.SPACE: self.state = GameMode.MAINMENU + elif key == KeyValues.CHAT: + # Wait for the direction of the friendly entity + self.waiting_for_friendly_key = True + + def handle_friendly_entity_chat(self, key: KeyValues) -> None: + """ + If the player is talking to a friendly entity, we get the direction + where the entity is, then we interact with it. + """ + if not self.waiting_for_friendly_key: + return + self.waiting_for_friendly_key = False + + if key == KeyValues.UP: + xp = self.player.x + yp = self.player.y - 1 + elif key == KeyValues.DOWN: + xp = self.player.x + yp = self.player.y + 1 + elif key == KeyValues.LEFT: + xp = self.player.x - 1 + yp = self.player.y + elif key == KeyValues.RIGHT: + xp = self.player.x + 1 + yp = self.player.y + else: + return + if self.map.entity_is_present(yp, xp): + for entity in self.map.entities: + if entity.is_friendly() and entity.x == xp and \ + entity.y == yp: + msg = entity.talk_to(self.player) + self.logs.add_message(msg) + if entity.is_merchant(): + self.state = GameMode.STORE + self.store_menu.update_merchant(entity) def handle_key_pressed_inventory(self, key: KeyValues) -> None: """ @@ -140,6 +185,27 @@ class Game: len(self.inventory_menu.values) - 1) + def handle_key_pressed_store(self, key: KeyValues) -> None: + """ + In a store menu, we can buy items or close the menu. + """ + if key == KeyValues.SPACE: + self.state = GameMode.PLAY + elif key == KeyValues.UP: + self.store_menu.go_up() + elif key == KeyValues.DOWN: + self.store_menu.go_down() + if self.store_menu.values and not self.player.dead: + if key == KeyValues.ENTER: + item = self.store_menu.validate() + flag = item.be_sold(self.player, self.store_menu.merchant) + if not flag: + self.message = _("You do not have enough money") + self.display_actions(DisplayActions.UPDATE) + # Ensure that the cursor has a good position + self.store_menu.position = min(self.store_menu.position, + len(self.store_menu.values) - 1) + def handle_key_pressed_main_menu(self, key: KeyValues) -> None: """ In the main menu, we can navigate through options. diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index 92b4498..c035a38 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -4,7 +4,7 @@ from enum import Enum, auto from math import sqrt from random import choice, randint -from typing import List, Optional +from typing import List, Optional, Any from .display.texturepack import TexturePack from .translations import gettext as _ @@ -77,12 +77,21 @@ class Map: def is_free(self, y: int, x: int) -> bool: """ - Indicates that the case at the coordinates (y, x) is empty. + Indicates that the tile at the coordinates (y, x) is empty. """ return 0 <= y < self.height and 0 <= x < self.width and \ self.tiles[y][x].can_walk() and \ not any(entity.x == x and entity.y == y for entity in self.entities) + def entity_is_present(self, y: int, x: int) -> bool: + """ + Indicates that the tile at the coordinates (y, x) contains a killable + entity + """ + return 0 <= y < self.height and 0 <= x < self.width and \ + any(entity.x == x and entity.y == y and entity.is_friendly() + for entity in self.entities) + @staticmethod def load(filename: str) -> "Map": """ @@ -128,7 +137,7 @@ class Map: def spawn_random_entities(self, count: int) -> None: """ - Put randomly {count} hedgehogs on the map, where it is available. + Put randomly {count} entities on the map, where it is available. """ for ignored in range(count): y, x = 0, 0 @@ -316,20 +325,34 @@ class Entity: from squirrelbattle.entities.items import Item return isinstance(self, Item) + def is_friendly(self) -> bool: + """ + Is this entity a friendly entity? + """ + return isinstance(self, FriendlyEntity) + + def is_merchant(self) -> bool: + """ + Is this entity a merchant? + """ + from squirrelbattle.entities.friendly import Merchant + return isinstance(self, Merchant) + @property def translated_name(self) -> str: return _(self.name.replace("_", " ")) @staticmethod - def get_all_entity_classes(): + def get_all_entity_classes() -> list: """ Returns all entities subclasses """ from squirrelbattle.entities.items import BodySnatchPotion, Bomb, Heart from squirrelbattle.entities.monsters import Tiger, Hedgehog, \ Rabbit, TeddyBear - return [BodySnatchPotion, Bomb, Heart, Hedgehog, - Rabbit, TeddyBear, Tiger] + from squirrelbattle.entities.friendly import Merchant, Sunflower + return [BodySnatchPotion, Bomb, Heart, Hedgehog, Rabbit, TeddyBear, + Sunflower, Tiger, Merchant] @staticmethod def get_all_entity_classes_in_a_dict() -> dict: @@ -339,7 +362,9 @@ class Entity: from squirrelbattle.entities.player import Player from squirrelbattle.entities.monsters import Tiger, Hedgehog, Rabbit, \ TeddyBear - from squirrelbattle.entities.items import BodySnatchPotion, Bomb, Heart + from squirrelbattle.entities.friendly import Merchant, Sunflower + from squirrelbattle.entities.items import BodySnatchPotion, Bomb, \ + Heart, Sword return { "Tiger": Tiger, "Bomb": Bomb, @@ -349,6 +374,9 @@ class Entity: "Rabbit": Rabbit, "TeddyBear": TeddyBear, "Player": Player, + "Merchant": Merchant, + "Sunflower": Sunflower, + "Sword": Sword, } def save_state(self) -> dict: @@ -424,7 +452,7 @@ class FightingEntity(Entity): def keys(self) -> list: """ - Returns a fighting entities specific attributes + Returns a fighting entity's specific attributes """ return ["name", "maxhealth", "health", "level", "strength", "intelligence", "charisma", "dexterity", "constitution"] @@ -437,3 +465,74 @@ class FightingEntity(Entity): for name in self.keys(): d[name] = getattr(self, name) return d + + +class FriendlyEntity(FightingEntity): + """ + Friendly entities are living entities which do not attack the player + """ + dialogue_option: list + + def talk_to(self, player: Any) -> str: + a = randint(0, len(self.dialogue_option) - 1) + return "The " + self.translated_name \ + + " said : " + self.dialogue_option[a] + + def keys(self) -> list: + """ + Returns a friendly entity's specific attributes + """ + return ["maxhealth", "health"] + + +class InventoryHolder(Entity): + hazel: int # Currency of the game + inventory: list + + def translate_inventory(self, inventory: list) -> list: + """ + Translate the JSON-state of the inventory into a list of the items in + the inventory. + """ + for i in range(len(inventory)): + if isinstance(inventory[i], dict): + inventory[i] = self.dict_to_inventory(inventory[i]) + return inventory + + def dict_to_inventory(self, item_dict: dict) -> Entity: + """ + Translate a dict object that contains the state of an item + into an item object. + """ + entity_classes = self.get_all_entity_classes_in_a_dict() + + item_class = entity_classes[item_dict["type"]] + return item_class(**item_dict) + + def save_state(self) -> dict: + """ + We save the inventory of the merchant formatted as JSON + """ + d = super().save_state() + d["hazel"] = self.hazel + d["inventory"] = [item.save_state() for item in self.inventory] + return d + + def add_to_inventory(self, obj: Any) -> None: + """ + Adds an object to inventory + """ + self.inventory.append(obj) + + def remove_from_inventory(self, obj: Any) -> None: + """ + Removes an object from the inventory + """ + self.inventory.remove(obj) + + def change_hazel_balance(self, hz: int) -> None: + """ + Change the number of hazel the entity has by hz. hz is negative + when the player loses money and positive when he gains money + """ + self.hazel += hz diff --git a/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po index 38d16a6..b5e8225 100644 --- a/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po +++ b/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po @@ -6,7 +6,7 @@ msgid "" msgstr "" "Project-Id-Version: squirrelbattle 3.14.1\n" "Report-Msgid-Bugs-To: squirrel-battle@crans.org\n" -"POT-Creation-Date: 2020-12-05 14:46+0100\n" +"POT-Creation-Date: 2020-12-11 18:06+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -15,31 +15,52 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: squirrelbattle/display/menudisplay.py:105 +#: squirrelbattle/display/menudisplay.py:113 msgid "== INVENTORY ==" msgstr "== BESTAND ==" +#: squirrelbattle/display/menudisplay.py:134 +msgid "== STALL ==" +msgstr "== STAND ==" + #: squirrelbattle/display/statsdisplay.py:34 msgid "Inventory:" msgstr "Bestand:" -#: squirrelbattle/display/statsdisplay.py:50 +#: squirrelbattle/display/statsdisplay.py:53 msgid "YOU ARE DEAD" msgstr "SIE WURDEN GESTORBEN" +#. TODO +#: squirrelbattle/entities/friendly.py:33 +msgid "I don't sell any squirrel" +msgstr "Ich verkaufe keinen Eichhörnchen." + +#: squirrelbattle/entities/friendly.py:46 +msgid "Flower power!!" +msgstr "Blumenmacht!!" + +#: squirrelbattle/entities/friendly.py:46 +msgid "The sun is warm today" +msgstr "Die Sonne ist warm heute" + #. The bomb is exploding. #. Each entity that is close to the bomb takes damages. #. The player earn XP if the entity was killed. -#: squirrelbattle/entities/items.py:128 +#: squirrelbattle/entities/items.py:151 msgid "Bomb is exploding." msgstr "Die Bombe explodiert." -#: squirrelbattle/entities/items.py:172 +#: squirrelbattle/entities/items.py:224 #, python-brace-format msgid "{player} exchanged its body with {entity}." msgstr "{player} täuscht seinem Körper mit {entity} aus." -#: squirrelbattle/game.py:177 +#: squirrelbattle/game.py:199 squirrelbattle/tests/game_test.py:537 +msgid "You do not have enough money" +msgstr "" + +#: squirrelbattle/game.py:243 msgid "" "Some keys are missing in your save file.\n" "Your save seems to be corrupt. It got deleted." @@ -47,7 +68,7 @@ msgstr "" "In Ihrer Speicherdatei fehlen einige Schlüssel.\n" "Ihre Speicherung scheint korrupt zu sein. Es wird gelöscht." -#: squirrelbattle/game.py:185 +#: squirrelbattle/game.py:251 msgid "" "No player was found on this map!\n" "Maybe you died?" @@ -55,7 +76,7 @@ msgstr "" "Auf dieser Karte wurde kein Spieler gefunden!\n" "Vielleicht sind Sie gestorben?" -#: squirrelbattle/game.py:205 +#: squirrelbattle/game.py:271 msgid "" "The JSON file is not correct.\n" "Your save seems corrupted. It got deleted." @@ -63,27 +84,27 @@ msgstr "" "Die JSON-Datei ist nicht korrekt.\n" "Ihre Speicherung scheint korrumpiert. Sie wurde gelöscht." -#: squirrelbattle/interfaces.py:400 +#: squirrelbattle/interfaces.py:429 #, python-brace-format msgid "{name} hits {opponent}." msgstr "{name} schlägt {opponent}." -#: squirrelbattle/interfaces.py:412 +#: squirrelbattle/interfaces.py:441 #, python-brace-format msgid "{name} takes {amount} damage." msgstr "{name} nimmt {amount} Schadenspunkte." -#: squirrelbattle/interfaces.py:414 +#: squirrelbattle/interfaces.py:443 #, python-brace-format msgid "{name} dies." msgstr "{name} stirbt." -#: squirrelbattle/menus.py:72 +#: squirrelbattle/menus.py:73 msgid "Back" msgstr "Zurück" -#: squirrelbattle/tests/game_test.py:300 squirrelbattle/tests/game_test.py:303 -#: squirrelbattle/tests/game_test.py:306 +#: squirrelbattle/tests/game_test.py:314 squirrelbattle/tests/game_test.py:317 +#: squirrelbattle/tests/game_test.py:320 #: squirrelbattle/tests/translations_test.py:16 msgid "New game" msgstr "Neu Spiel" @@ -161,41 +182,49 @@ msgid "Key used to drop an item in the inventory" msgstr "Taste um eines Objekts im Bestand zu werfen" #: squirrelbattle/tests/translations_test.py:53 +msgid "Key used to talk to a friendly entity" +msgstr "Taste um mit einer friedlicher Entität zu sprechen" + +#: squirrelbattle/tests/translations_test.py:55 msgid "Texture pack" msgstr "Textur-Packung" -#: squirrelbattle/tests/translations_test.py:54 +#: squirrelbattle/tests/translations_test.py:56 msgid "Language" msgstr "Sprache" -#: squirrelbattle/tests/translations_test.py:57 +#: squirrelbattle/tests/translations_test.py:59 msgid "player" msgstr "Spieler" -#: squirrelbattle/tests/translations_test.py:59 +#: squirrelbattle/tests/translations_test.py:61 msgid "tiger" msgstr "Tiger" -#: squirrelbattle/tests/translations_test.py:60 +#: squirrelbattle/tests/translations_test.py:62 msgid "hedgehog" msgstr "Igel" -#: squirrelbattle/tests/translations_test.py:61 +#: squirrelbattle/tests/translations_test.py:63 msgid "rabbit" msgstr "Kanninchen" -#: squirrelbattle/tests/translations_test.py:62 +#: squirrelbattle/tests/translations_test.py:64 msgid "teddy bear" msgstr "Teddybär" -#: squirrelbattle/tests/translations_test.py:64 +#: squirrelbattle/tests/translations_test.py:66 msgid "body snatch potion" msgstr "Leichenfleddererzaubertrank" -#: squirrelbattle/tests/translations_test.py:65 +#: squirrelbattle/tests/translations_test.py:67 msgid "bomb" msgstr "Bombe" -#: squirrelbattle/tests/translations_test.py:66 +#: squirrelbattle/tests/translations_test.py:68 msgid "heart" msgstr "Herz" + +#: squirrelbattle/tests/translations_test.py:69 +msgid "sword" +msgstr "schwert" diff --git a/squirrelbattle/locale/en/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/en/LC_MESSAGES/squirrelbattle.po new file mode 100644 index 0000000..c45e893 --- /dev/null +++ b/squirrelbattle/locale/en/LC_MESSAGES/squirrelbattle.po @@ -0,0 +1,207 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR ÿnérant, eichhornchen, nicomarg, charlse +# This file is distributed under the same license as the squirrelbattle package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: squirrelbattle 3.14.1\n" +"Report-Msgid-Bugs-To: squirrel-battle@crans.org\n" +"POT-Creation-Date: 2020-12-01 17:10+0100\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: squirrelbattle/display/statsdisplay.py:34 +msgid "Inventory:" +msgstr "" + +#: squirrelbattle/display/statsdisplay.py:39 +msgid "YOU ARE DEAD" +msgstr "" + +#: squirrelbattle/interfaces.py:394 squirrelbattle/interfaces.py:398 +#: squirrelbattle/interfaces.py:408 +#, python-brace-format +msgid "{name} hits {opponent}." +msgstr "" + +#: squirrelbattle/interfaces.py:405 squirrelbattle/interfaces.py:410 +#: squirrelbattle/interfaces.py:420 +#, python-brace-format +msgid "{name} takes {amount} damage." +msgstr "" + +#: squirrelbattle/menus.py:45 squirrelbattle/tests/translations_test.py:14 +#: squirrelbattle/tests/game_test.py:284 squirrelbattle/tests/game_test.py:287 +#: squirrelbattle/tests/translations_test.py:16 +#: squirrelbattle/tests/game_test.py:290 +msgid "New game" +msgstr "" + +#: squirrelbattle/menus.py:46 squirrelbattle/tests/translations_test.py:15 +#: squirrelbattle/tests/translations_test.py:17 +msgid "Resume" +msgstr "" + +#: squirrelbattle/menus.py:47 squirrelbattle/tests/translations_test.py:17 +#: squirrelbattle/tests/translations_test.py:19 +msgid "Save" +msgstr "" + +#: squirrelbattle/menus.py:48 squirrelbattle/tests/translations_test.py:16 +#: squirrelbattle/tests/translations_test.py:18 +msgid "Load" +msgstr "" + +#: squirrelbattle/menus.py:49 squirrelbattle/tests/translations_test.py:18 +#: squirrelbattle/tests/translations_test.py:20 +msgid "Settings" +msgstr "" + +#: squirrelbattle/menus.py:50 squirrelbattle/tests/translations_test.py:19 +#: squirrelbattle/tests/translations_test.py:21 +msgid "Exit" +msgstr "" + +#: squirrelbattle/menus.py:71 +msgid "Back" +msgstr "" + +#: squirrelbattle/game.py:147 squirrelbattle/game.py:148 +msgid "" +"Some keys are missing in your save file.\n" +"Your save seems to be corrupt. It got deleted." +msgstr "" + +#: squirrelbattle/game.py:155 squirrelbattle/game.py:156 +msgid "" +"No player was found on this map!\n" +"Maybe you died?" +msgstr "" + +#: squirrelbattle/game.py:175 squirrelbattle/game.py:176 +msgid "" +"The JSON file is not correct.\n" +"Your save seems corrupted. It got deleted." +msgstr "" + +#: squirrelbattle/settings.py:21 squirrelbattle/tests/translations_test.py:21 +#: squirrelbattle/tests/translations_test.py:25 +#: squirrelbattle/tests/translations_test.py:27 +msgid "Main key to move up" +msgstr "" + +#: squirrelbattle/settings.py:22 squirrelbattle/tests/translations_test.py:23 +#: squirrelbattle/tests/translations_test.py:27 +#: squirrelbattle/tests/translations_test.py:29 +msgid "Secondary key to move up" +msgstr "" + +#: squirrelbattle/settings.py:23 squirrelbattle/tests/translations_test.py:25 +#: squirrelbattle/tests/translations_test.py:29 +#: squirrelbattle/tests/translations_test.py:31 +msgid "Main key to move down" +msgstr "" + +#: squirrelbattle/settings.py:24 squirrelbattle/tests/translations_test.py:27 +#: squirrelbattle/tests/translations_test.py:31 +#: squirrelbattle/tests/translations_test.py:33 +msgid "Secondary key to move down" +msgstr "" + +#: squirrelbattle/settings.py:25 squirrelbattle/tests/translations_test.py:29 +#: squirrelbattle/tests/translations_test.py:33 +#: squirrelbattle/tests/translations_test.py:35 +msgid "Main key to move left" +msgstr "" + +#: squirrelbattle/settings.py:26 squirrelbattle/tests/translations_test.py:31 +#: squirrelbattle/tests/translations_test.py:35 +#: squirrelbattle/tests/translations_test.py:37 +msgid "Secondary key to move left" +msgstr "" + +#: squirrelbattle/settings.py:27 squirrelbattle/tests/translations_test.py:33 +#: squirrelbattle/tests/translations_test.py:37 +#: squirrelbattle/tests/translations_test.py:39 +msgid "Main key to move right" +msgstr "" + +#: squirrelbattle/settings.py:29 squirrelbattle/tests/translations_test.py:35 +#: squirrelbattle/tests/translations_test.py:39 +#: squirrelbattle/tests/translations_test.py:41 +msgid "Secondary key to move right" +msgstr "" + +#: squirrelbattle/settings.py:30 squirrelbattle/tests/translations_test.py:37 +#: squirrelbattle/tests/translations_test.py:41 +#: squirrelbattle/tests/translations_test.py:43 +msgid "Key to validate a menu" +msgstr "" + +#: squirrelbattle/settings.py:31 squirrelbattle/tests/translations_test.py:39 +#: squirrelbattle/tests/translations_test.py:43 +#: squirrelbattle/tests/translations_test.py:45 +msgid "Texture pack" +msgstr "" + +#: squirrelbattle/settings.py:32 squirrelbattle/tests/translations_test.py:40 +#: squirrelbattle/tests/translations_test.py:44 +#: squirrelbattle/tests/translations_test.py:46 +msgid "Language" +msgstr "" + +#: squirrelbattle/interfaces.py:407 squirrelbattle/interfaces.py:412 +#: squirrelbattle/interfaces.py:422 +#, python-brace-format +msgid "{name} dies." +msgstr "" + +#: squirrelbattle/tests/translations_test.py:47 +#: squirrelbattle/tests/translations_test.py:49 +msgid "player" +msgstr "" + +#: squirrelbattle/tests/translations_test.py:49 +#: squirrelbattle/tests/translations_test.py:51 +msgid "tiger" +msgstr "" + +#: squirrelbattle/tests/translations_test.py:50 +#: squirrelbattle/tests/translations_test.py:52 +msgid "hedgehog" +msgstr "" + +#: squirrelbattle/tests/translations_test.py:51 +#: squirrelbattle/tests/translations_test.py:53 +msgid "rabbit" +msgstr "" + +#: squirrelbattle/tests/translations_test.py:52 +#: squirrelbattle/tests/translations_test.py:54 +msgid "teddy bear" +msgstr "" + +#: squirrelbattle/tests/translations_test.py:54 +#: squirrelbattle/tests/translations_test.py:56 +msgid "bomb" +msgstr "" + +#: squirrelbattle/tests/translations_test.py:55 +#: squirrelbattle/tests/translations_test.py:57 +msgid "heart" +msgstr "" + +#: squirrelbattle/entities/friendly.py:31 +msgid "Flower power!!" +msgstr "" + +#: squirrelbattle/entities/friendly.py:31 +msgid "The sun is warm today" +msgstr "" diff --git a/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po index 85bd728..9c9f5b0 100644 --- a/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po +++ b/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: squirrelbattle 3.14.1\n" "Report-Msgid-Bugs-To: squirrel-battle@crans.org\n" -"POT-Creation-Date: 2020-12-05 14:46+0100\n" +"POT-Creation-Date: 2020-12-11 18:06+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -16,31 +16,52 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: squirrelbattle/display/menudisplay.py:105 +#: squirrelbattle/display/menudisplay.py:113 msgid "== INVENTORY ==" msgstr "== INVENTAIRE ==" +#: squirrelbattle/display/menudisplay.py:134 +msgid "== STALL ==" +msgstr "== STAND ==" + #: squirrelbattle/display/statsdisplay.py:34 msgid "Inventory:" msgstr "Inventaire :" -#: squirrelbattle/display/statsdisplay.py:50 +#: squirrelbattle/display/statsdisplay.py:53 msgid "YOU ARE DEAD" msgstr "VOUS ÊTES MORT" +#. TODO +#: squirrelbattle/entities/friendly.py:33 +msgid "I don't sell any squirrel" +msgstr "Je ne vends pas d'écureuil" + +#: squirrelbattle/entities/friendly.py:46 +msgid "Flower power!!" +msgstr "Pouvoir des fleurs !" + +#: squirrelbattle/entities/friendly.py:46 +msgid "The sun is warm today" +msgstr "Le soleil est chaud aujourd'hui" + #. The bomb is exploding. #. Each entity that is close to the bomb takes damages. #. The player earn XP if the entity was killed. -#: squirrelbattle/entities/items.py:128 +#: squirrelbattle/entities/items.py:151 msgid "Bomb is exploding." msgstr "La bombe explose." -#: squirrelbattle/entities/items.py:172 +#: squirrelbattle/entities/items.py:224 #, python-brace-format msgid "{player} exchanged its body with {entity}." msgstr "{player} a échangé son corps avec {entity}." -#: squirrelbattle/game.py:177 +#: squirrelbattle/game.py:199 squirrelbattle/tests/game_test.py:537 +msgid "You do not have enough money" +msgstr "" + +#: squirrelbattle/game.py:243 msgid "" "Some keys are missing in your save file.\n" "Your save seems to be corrupt. It got deleted." @@ -48,7 +69,7 @@ msgstr "" "Certaines clés de votre ficher de sauvegarde sont manquantes.\n" "Votre sauvegarde semble corrompue. Elle a été supprimée." -#: squirrelbattle/game.py:185 +#: squirrelbattle/game.py:251 msgid "" "No player was found on this map!\n" "Maybe you died?" @@ -56,7 +77,7 @@ msgstr "" "Aucun joueur n'a été trouvé sur la carte !\n" "Peut-être êtes-vous mort ?" -#: squirrelbattle/game.py:205 +#: squirrelbattle/game.py:271 msgid "" "The JSON file is not correct.\n" "Your save seems corrupted. It got deleted." @@ -64,27 +85,27 @@ msgstr "" "Le fichier JSON de sauvegarde est incorrect.\n" "Votre sauvegarde semble corrompue. Elle a été supprimée." -#: squirrelbattle/interfaces.py:400 +#: squirrelbattle/interfaces.py:429 #, python-brace-format msgid "{name} hits {opponent}." msgstr "{name} frappe {opponent}." -#: squirrelbattle/interfaces.py:412 +#: squirrelbattle/interfaces.py:441 #, python-brace-format msgid "{name} takes {amount} damage." msgstr "{name} prend {amount} points de dégât." -#: squirrelbattle/interfaces.py:414 +#: squirrelbattle/interfaces.py:443 #, python-brace-format msgid "{name} dies." msgstr "{name} meurt." -#: squirrelbattle/menus.py:72 +#: squirrelbattle/menus.py:73 msgid "Back" msgstr "Retour" -#: squirrelbattle/tests/game_test.py:300 squirrelbattle/tests/game_test.py:303 -#: squirrelbattle/tests/game_test.py:306 +#: squirrelbattle/tests/game_test.py:314 squirrelbattle/tests/game_test.py:317 +#: squirrelbattle/tests/game_test.py:320 #: squirrelbattle/tests/translations_test.py:16 msgid "New game" msgstr "Nouvelle partie" @@ -162,41 +183,49 @@ msgid "Key used to drop an item in the inventory" msgstr "Touche pour jeter un objet de l'inventaire" #: squirrelbattle/tests/translations_test.py:53 +msgid "Key used to talk to a friendly entity" +msgstr "Touche pour parler à une entité pacifique" + +#: squirrelbattle/tests/translations_test.py:55 msgid "Texture pack" msgstr "Pack de textures" -#: squirrelbattle/tests/translations_test.py:54 +#: squirrelbattle/tests/translations_test.py:56 msgid "Language" msgstr "Langue" -#: squirrelbattle/tests/translations_test.py:57 +#: squirrelbattle/tests/translations_test.py:59 msgid "player" msgstr "joueur" -#: squirrelbattle/tests/translations_test.py:59 +#: squirrelbattle/tests/translations_test.py:61 msgid "tiger" msgstr "tigre" -#: squirrelbattle/tests/translations_test.py:60 +#: squirrelbattle/tests/translations_test.py:62 msgid "hedgehog" msgstr "hérisson" -#: squirrelbattle/tests/translations_test.py:61 +#: squirrelbattle/tests/translations_test.py:63 msgid "rabbit" msgstr "lapin" -#: squirrelbattle/tests/translations_test.py:62 +#: squirrelbattle/tests/translations_test.py:64 msgid "teddy bear" msgstr "nounours" -#: squirrelbattle/tests/translations_test.py:64 +#: squirrelbattle/tests/translations_test.py:66 msgid "body snatch potion" msgstr "potion d'arrachage de corps" -#: squirrelbattle/tests/translations_test.py:65 +#: squirrelbattle/tests/translations_test.py:67 msgid "bomb" msgstr "bombe" -#: squirrelbattle/tests/translations_test.py:66 +#: squirrelbattle/tests/translations_test.py:68 msgid "heart" msgstr "cœur" + +#: squirrelbattle/tests/translations_test.py:69 +msgid "sword" +msgstr "épée" diff --git a/squirrelbattle/menus.py b/squirrelbattle/menus.py index 3a536f3..e0087a3 100644 --- a/squirrelbattle/menus.py +++ b/squirrelbattle/menus.py @@ -6,6 +6,7 @@ from typing import Any, Optional from .display.texturepack import TexturePack from .entities.player import Player +from .entities.friendly import Merchant from .enums import GameMode, KeyValues, DisplayActions from .settings import Settings from .translations import gettext as _, Translator @@ -128,3 +129,14 @@ class InventoryMenu(Menu): @property def values(self) -> list: return self.player.inventory + + +class StoreMenu(Menu): + merchant: Merchant + + def update_merchant(self, merchant: Merchant) -> None: + self.merchant = merchant + + @property + def values(self) -> list: + return self.merchant.inventory diff --git a/squirrelbattle/settings.py b/squirrelbattle/settings.py index 4004645..4e6040c 100644 --- a/squirrelbattle/settings.py +++ b/squirrelbattle/settings.py @@ -31,6 +31,7 @@ class Settings: self.KEY_USE = ['u', 'Key used to use an item in the inventory'] self.KEY_EQUIP = ['e', 'Key used to equip an item in the inventory'] self.KEY_DROP = ['r', 'Key used to drop an item in the inventory'] + self.KEY_CHAT = ['t', 'Key used to talk to a friendly entity'] self.TEXTURE_PACK = ['ascii', 'Texture pack'] self.LOCALE = [locale.getlocale()[0][:2], 'Language'] diff --git a/squirrelbattle/tests/game_test.py b/squirrelbattle/tests/game_test.py index 2bb84c5..e079035 100644 --- a/squirrelbattle/tests/game_test.py +++ b/squirrelbattle/tests/game_test.py @@ -7,7 +7,8 @@ import unittest from ..bootstrap import Bootstrap from ..display.display import Display from ..display.display_manager import DisplayManager -from ..entities.items import Bomb +from ..entities.friendly import Merchant, Sunflower +from ..entities.items import Bomb, Heart, Sword from ..entities.player import Player from ..enums import DisplayActions from ..game import Game, KeyValues, GameMode @@ -34,7 +35,17 @@ class TestGame(unittest.TestCase): """ bomb = Bomb() self.game.map.add_entity(bomb) + sword = Sword() + self.game.map.add_entity(sword) + # Add items in the inventory to check that it is well loaded bomb.hold(self.game.player) + sword.hold(self.game.player) + + # Ensure that merchants can be saved + merchant = Merchant() + merchant.move(3, 6) + self.game.map.add_entity(merchant) + old_state = self.game.save_state() self.game.handle_key_pressed(KeyValues.DOWN) @@ -117,6 +128,9 @@ class TestGame(unittest.TestCase): self.assertEqual(KeyValues.translate_key( self.game.settings.KEY_INVENTORY, self.game.settings), KeyValues.INVENTORY) + self.assertEqual(KeyValues.translate_key( + self.game.settings.KEY_CHAT, self.game.settings), + KeyValues.CHAT) self.assertEqual(KeyValues.translate_key( self.game.settings.KEY_USE, self.game.settings), KeyValues.USE) @@ -307,7 +321,7 @@ class TestGame(unittest.TestCase): self.assertEqual(self.game.settings.KEY_LEFT_PRIMARY, 'a') # Navigate to "texture pack" - for ignored in range(9): + for ignored in range(10): self.game.handle_key_pressed(KeyValues.DOWN) # Change texture pack @@ -444,3 +458,115 @@ class TestGame(unittest.TestCase): self.assertTrue(bomb.exploding) self.assertEqual(bomb.y, self.game.player.y) self.assertEqual(bomb.x, self.game.player.x) + + def test_talk_to_sunflowers(self) -> None: + """ + Interact with sunflowers + """ + self.game.state = GameMode.PLAY + + sunflower = Sunflower() + sunflower.move(2, 6) + self.game.map.add_entity(sunflower) + + # Does nothing + self.assertIsNone(self.game.handle_friendly_entity_chat(KeyValues.UP)) + + # Talk to sunflower... or not + self.game.handle_key_pressed(KeyValues.CHAT) + self.assertTrue(self.game.waiting_for_friendly_key) + # Wrong key + self.game.handle_key_pressed(KeyValues.EQUIP) + self.assertFalse(self.game.waiting_for_friendly_key) + self.game.handle_key_pressed(KeyValues.CHAT) + self.assertTrue(self.game.waiting_for_friendly_key) + self.game.handle_key_pressed(KeyValues.UP) + self.assertFalse(self.game.waiting_for_friendly_key) + self.assertEqual(self.game.state, GameMode.PLAY) + self.assertFalse(len(self.game.logs.messages) > 1) + + # Talk to sunflower + self.game.handle_key_pressed(KeyValues.CHAT) + self.assertTrue(self.game.waiting_for_friendly_key) + self.game.handle_key_pressed(KeyValues.DOWN) + self.assertFalse(self.game.waiting_for_friendly_key) + self.assertEqual(self.game.state, GameMode.PLAY) + self.assertTrue(self.game.logs.messages) + # Ensure that the message is a good message + self.assertIn(self.game.logs.messages[1][21:], + Sunflower.dialogue_option) + + # Test all directions to detect the friendly entity + self.game.player.move(3, 6) + self.game.handle_key_pressed(KeyValues.CHAT) + self.game.handle_key_pressed(KeyValues.UP) + self.assertEqual(len(self.game.logs.messages), 3) + self.game.player.move(2, 7) + self.game.handle_key_pressed(KeyValues.CHAT) + self.game.handle_key_pressed(KeyValues.LEFT) + self.assertEqual(len(self.game.logs.messages), 4) + self.game.player.move(2, 5) + self.game.handle_key_pressed(KeyValues.CHAT) + self.game.handle_key_pressed(KeyValues.RIGHT) + self.assertEqual(len(self.game.logs.messages), 5) + + def test_talk_to_merchant(self) -> None: + """ + Interact with merchants + """ + self.game.state = GameMode.PLAY + + merchant = Merchant() + merchant.move(2, 6) + self.game.map.add_entity(merchant) + + # Does nothing + self.assertIsNone(self.game.handle_friendly_entity_chat(KeyValues.UP)) + + # Talk to merchant + self.game.handle_key_pressed(KeyValues.CHAT) + self.assertTrue(self.game.waiting_for_friendly_key) + self.game.handle_key_pressed(KeyValues.DOWN) + self.assertFalse(self.game.waiting_for_friendly_key) + self.assertEqual(self.game.state, GameMode.STORE) + self.assertTrue(self.game.logs.messages) + + # Navigate in the menu + self.game.handle_key_pressed(KeyValues.DOWN) + self.game.handle_key_pressed(KeyValues.DOWN) + self.game.handle_key_pressed(KeyValues.UP) + self.assertEqual(self.game.store_menu.position, 1) + + # The second item is not a heart + merchant.inventory[1] = Sword() + # Buy the second item + item = self.game.store_menu.validate() + self.assertIn(item, merchant.inventory) + self.game.handle_key_pressed(KeyValues.ENTER) + self.assertIn(item, self.game.player.inventory) + self.assertNotIn(item, merchant.inventory) + + # Buy a heart + merchant.inventory[1] = Heart() + item = self.game.store_menu.validate() + self.assertIn(item, merchant.inventory) + self.assertEqual(item, merchant.inventory[1]) + self.game.player.health = self.game.player.maxhealth - 1 - item.healing + self.game.handle_key_pressed(KeyValues.ENTER) + self.assertNotIn(item, self.game.player.inventory) + self.assertNotIn(item, merchant.inventory) + self.assertEqual(self.game.player.health, + self.game.player.maxhealth - 1) + + # We don't have enough of money + self.game.player.hazel = 0 + item = self.game.store_menu.validate() + self.game.handle_key_pressed(KeyValues.ENTER) + self.assertNotIn(item, self.game.player.inventory) + self.assertIn(item, merchant.inventory) + self.assertEqual(self.game.message, _("You do not have enough money")) + self.game.handle_key_pressed(KeyValues.ENTER) + + # Exit the menu + self.game.handle_key_pressed(KeyValues.SPACE) + self.assertEqual(self.game.state, GameMode.PLAY) diff --git a/squirrelbattle/tests/translations_test.py b/squirrelbattle/tests/translations_test.py index 0cb39c5..8176fd4 100644 --- a/squirrelbattle/tests/translations_test.py +++ b/squirrelbattle/tests/translations_test.py @@ -50,6 +50,8 @@ class TestTranslations(unittest.TestCase): "Touche pour équiper un objet de l'inventaire") self.assertEqual(_("Key used to drop an item in the inventory"), "Touche pour jeter un objet de l'inventaire") + self.assertEqual(_("Key used to talk to a friendly entity"), + "Touche pour parler à une entité pacifique") self.assertEqual(_("Texture pack"), "Pack de textures") self.assertEqual(_("Language"), "Langue") @@ -64,3 +66,4 @@ class TestTranslations(unittest.TestCase): self.assertEqual(_("body snatch potion"), "potion d'arrachage de corps") self.assertEqual(_("bomb"), "bombe") self.assertEqual(_("heart"), "cœur") + self.assertEqual(_("sword"), "épée")