From bdbf214d8d02d8ecc33d54ae95e2d8188d2d22db Mon Sep 17 00:00:00 2001 From: eichhornchen Date: Fri, 8 Jan 2021 23:15:48 +0100 Subject: [PATCH] Added chests, they are immortal and contain objects the player can take for free. --- squirrelbattle/display/display_manager.py | 23 ++++++++++-- squirrelbattle/display/menudisplay.py | 44 +++++++++++++++++++++-- squirrelbattle/display/texturepack.py | 3 ++ squirrelbattle/entities/friendly.py | 33 +++++++++++++++++ squirrelbattle/entities/items.py | 9 +++-- squirrelbattle/enums.py | 1 + squirrelbattle/game.py | 39 ++++++++++++++++++++ squirrelbattle/interfaces.py | 17 ++++++--- squirrelbattle/menus.py | 22 +++++++++++- 9 files changed, 178 insertions(+), 13 deletions(-) diff --git a/squirrelbattle/display/display_manager.py b/squirrelbattle/display/display_manager.py index 0042615..0eceb62 100644 --- a/squirrelbattle/display/display_manager.py +++ b/squirrelbattle/display/display_manager.py @@ -10,7 +10,8 @@ from squirrelbattle.display.mapdisplay import MapDisplay from squirrelbattle.display.messagedisplay import MessageDisplay from squirrelbattle.display.statsdisplay import StatsDisplay from squirrelbattle.display.menudisplay import MainMenuDisplay, \ - PlayerInventoryDisplay, StoreInventoryDisplay, SettingsMenuDisplay + PlayerInventoryDisplay, StoreInventoryDisplay, SettingsMenuDisplay, \ + ChestInventoryDisplay from squirrelbattle.display.logsdisplay import LogsDisplay from squirrelbattle.display.texturepack import TexturePack from typing import Any, List @@ -29,6 +30,7 @@ class DisplayManager: self.logsdisplay = LogsDisplay(screen, pack) self.playerinventorydisplay = PlayerInventoryDisplay(screen, pack) self.storeinventorydisplay = StoreInventoryDisplay(screen, pack) + self.chestinventorydisplay = ChestInventoryDisplay(screen, pack) self.mainmenudisplay = MainMenuDisplay(self.game.main_menu, screen, pack) self.settingsmenudisplay = SettingsMenuDisplay(screen, pack) @@ -40,7 +42,8 @@ class DisplayManager: self.mainmenudisplay, self.settingsmenudisplay, self.logsdisplay, self.messagedisplay, self.playerinventorydisplay, - self.storeinventorydisplay, self.creditsdisplay] + self.storeinventorydisplay, self.creditsdisplay, + self.chestinventorydisplay] self.update_game_components() def handle_display_action(self, action: DisplayActions, *params) -> None: @@ -87,7 +90,8 @@ class DisplayManager: if self.game.state == GameMode.PLAY \ or self.game.state == GameMode.INVENTORY \ - or self.game.state == GameMode.STORE: + or self.game.state == GameMode.STORE\ + or self.game.state == GameMode.CHEST: # The map pad has already the good size self.mapdisplay.refresh(0, 0, self.rows * 4 // 5, self.mapdisplay.pack.tile_width @@ -124,6 +128,19 @@ class DisplayManager: pack.tile_width * (2 * self.cols // (5 * pack.tile_width))) displays.append(self.storeinventorydisplay) displays.append(self.playerinventorydisplay) + elif self.game.state == GameMode.CHEST: + self.chestinventorydisplay.refresh( + self.rows // 10, + pack.tile_width * (self.cols // (2 * pack.tile_width)), + 8 * self.rows // 10, + pack.tile_width * (2 * self.cols // (5 * pack.tile_width))) + self.playerinventorydisplay.refresh( + self.rows // 10, + pack.tile_width * (self.cols // (10 * pack.tile_width)), + 8 * self.rows // 10, + pack.tile_width * (2 * self.cols // (5 * pack.tile_width))) + displays.append(self.chestinventorydisplay) + displays.append(self.playerinventorydisplay) 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 cc73010..2e870e2 100644 --- a/squirrelbattle/display/menudisplay.py +++ b/squirrelbattle/display/menudisplay.py @@ -5,7 +5,8 @@ import curses from random import randint from typing import List -from squirrelbattle.menus import Menu, MainMenu, SettingsMenu, StoreMenu +from squirrelbattle.menus import Menu, MainMenu, SettingsMenu, StoreMenu,\ + ChestMenu from .display import Box, Display from ..entities.player import Player from ..enums import KeyValues, GameMode @@ -156,13 +157,16 @@ class PlayerInventoryDisplay(MenuDisplay): player: Player = None selected: bool = True store_mode: bool = False + chest_mode: bool = False def update(self, game: Game) -> None: self.player = game.player self.update_menu(game.inventory_menu) self.store_mode = game.state == GameMode.STORE + self.chest_mode = game.state == GameMode.CHEST self.selected = game.state == GameMode.INVENTORY \ - or (self.store_mode and not game.is_in_store_menu) + or (self.store_mode and not game.is_in_store_menu)\ + or (self.chest_mode and not game.is_in_chest_menu) def update_pad(self) -> None: self.menubox.update_title(_("INVENTORY")) @@ -239,3 +243,39 @@ class StoreInventoryDisplay(MenuDisplay): self.menu.position = max(0, min(len(self.menu.values) - 1, y - 2)) game.is_in_store_menu = True game.handle_key_pressed(KeyValues.ENTER) + +class ChestInventoryDisplay(MenuDisplay): + """ + A class to handle the display of a merchant's inventory. + """ + menu: ChestMenu + selected: bool = False + + def update(self, game: Game) -> None: + self.update_menu(game.chest_menu) + self.selected = game.is_in_chest_menu + + def update_pad(self) -> None: + self.menubox.update_title(_("CHEST")) + for i, item in enumerate(self.menu.values): + rep = self.pack[item.name.upper()] + selection = f"[{rep}]" if i == self.menu.position \ + and self.selected else f" {rep} " + self.addstr(self.pad, i + 1, 0, selection + + " " + item.translated_name.capitalize()) + + @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 - 2)) + game.is_in_chest_menu = True + game.handle_key_pressed(KeyValues.ENTER) diff --git a/squirrelbattle/display/texturepack.py b/squirrelbattle/display/texturepack.py index 3ba64f5..328f05e 100644 --- a/squirrelbattle/display/texturepack.py +++ b/squirrelbattle/display/texturepack.py @@ -22,6 +22,7 @@ class TexturePack: BODY_SNATCH_POTION: str BOMB: str BOW: str + CHEST: str CHESTPLATE: str EAGLE: str EMPTY: str @@ -79,6 +80,7 @@ TexturePack.ASCII_PACK = TexturePack( BODY_SNATCH_POTION='S', BOMB='ç', BOW=')', + CHEST='□', CHESTPLATE='(', EAGLE='µ', EMPTY=' ', @@ -119,6 +121,7 @@ TexturePack.SQUIRREL_PACK = TexturePack( BODY_SNATCH_POTION='🔀', BOMB='💣', BOW='🏹', + CHEST='🧰', CHESTPLATE='🦺', EAGLE='🦅', EMPTY=' ', diff --git a/squirrelbattle/entities/friendly.py b/squirrelbattle/entities/friendly.py index 76a2071..5ce9de9 100644 --- a/squirrelbattle/entities/friendly.py +++ b/squirrelbattle/entities/friendly.py @@ -38,6 +38,39 @@ class Merchant(InventoryHolder, FriendlyEntity): """ self.hazel += hz +class Chest(InventoryHolder, FriendlyEntity): + """ + A class of chest inanimate entities which contain objects. + """ + def __init__(self, name: str = "chest", inventory: list = None, + hazel: int = 0, *args, **kwargs): + super().__init__(name=name, *args, **kwargs) + self.hazel = hazel + self.inventory = self.translate_inventory(inventory or []) + if not self.inventory: + for i in range(3): + self.inventory.append(choice(Item.get_all_items())()) + + def talk_to(self, player: Player) -> str: + """ + This function is used to open the chest's inventory in a menu, + and allows the player to take objects. + """ + return _("You have opened the chest") + + def take_damage(self, attacker: "Entity", amount: int) -> str: + """ + A chest is not living, it can not take damage + """ + return _("It's not really effective") + + @property + def dead(self) -> bool: + """ + Chest can not die + """ + return False + class Sunflower(FriendlyEntity): """ diff --git a/squirrelbattle/entities/items.py b/squirrelbattle/entities/items.py index 04db192..f1c8002 100644 --- a/squirrelbattle/entities/items.py +++ b/squirrelbattle/entities/items.py @@ -88,13 +88,18 @@ class Item(Entity): Chestplate, Helmet, RingCritical, RingXP, ScrollofDamage, ScrollofWeakening, Ruler, Bow, FireBallStaff] - def be_sold(self, buyer: InventoryHolder, seller: InventoryHolder) -> bool: + def be_sold(self, buyer: InventoryHolder, seller: InventoryHolder,\ + for_free: bool = False) -> 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: + if for_free: + self.hold(buyer) + seller.remove_from_inventory(self) + return True + elif buyer.hazel >= self.price: self.hold(buyer) seller.remove_from_inventory(self) buyer.change_hazel_balance(-self.price) diff --git a/squirrelbattle/enums.py b/squirrelbattle/enums.py index cec8a4c..f39a57f 100644 --- a/squirrelbattle/enums.py +++ b/squirrelbattle/enums.py @@ -28,6 +28,7 @@ class GameMode(Enum): SETTINGS = auto() INVENTORY = auto() STORE = auto() + CHEST = auto() CREDITS = auto() diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index dc866f2..65c18c1 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -37,6 +37,7 @@ class Game: self.waiting_for_friendly_key = False self.waiting_for_launch_key = False self.is_in_store_menu = True + self.is_in_chest_menu = True self.settings = Settings() self.settings.load_settings() self.settings.write_settings() @@ -46,6 +47,7 @@ class Game: self.settings_menu.update_values(self.settings) self.inventory_menu = menus.InventoryMenu() self.store_menu = menus.StoreMenu() + self.chest_menu = menus.ChestMenu() self.logs = Logs() self.message = None @@ -131,6 +133,8 @@ class Game: self.settings_menu.handle_key_pressed(key, raw_key, self) elif self.state == GameMode.STORE: self.handle_key_pressed_store(key) + elif self.state == GameMode.CHEST: + self.handle_key_pressed_chest(key) elif self.state == GameMode.CREDITS: self.state = GameMode.MAINMENU self.display_actions(DisplayActions.REFRESH) @@ -253,6 +257,11 @@ class Game: self.is_in_store_menu = True self.store_menu.update_merchant(entity) self.display_actions(DisplayActions.UPDATE) + elif entity.is_chest(): + self.state = GameMode.CHEST + self.is_in_chest_menu = True + self.chest_menu.update_chest(entity) + self.display_actions(DisplayActions.UPDATE) def handle_launch(self, key: KeyValues) -> None: """ @@ -332,6 +341,36 @@ class Game: self.display_actions(DisplayActions.UPDATE) # Ensure that the cursor has a good position menu.position = min(menu.position, len(menu.values) - 1) + + def handle_key_pressed_chest(self, key: KeyValues) -> None: + """ + In a chest menu, we can take or put items or close the menu. + """ + menu = self.chest_menu if self.is_in_chest_menu else self.inventory_menu + + if key == KeyValues.SPACE or key == KeyValues.INVENTORY: + self.state = GameMode.PLAY + elif key == KeyValues.UP: + menu.go_up() + elif key == KeyValues.DOWN: + menu.go_down() + elif key == KeyValues.LEFT: + self.is_in_chest_menu = False + self.display_actions(DisplayActions.UPDATE) + elif key == KeyValues.RIGHT: + self.is_in_chest_menu = True + self.display_actions(DisplayActions.UPDATE) + if menu.values and not self.player.dead: + if key == KeyValues.ENTER: + item = menu.validate() + owner = self.chest_menu.chest if self.is_in_chest_menu \ + else self.player + buyer = self.player if self.is_in_chest_menu \ + else self.chest_menu.chest + flag = item.be_sold(buyer, owner, for_free = True) + self.display_actions(DisplayActions.UPDATE) + # Ensure that the cursor has a good position + menu.position = min(menu.position, len(menu.values) - 1) def handle_key_pressed_main_menu(self, key: KeyValues) -> None: """ diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index 34a4c90..0a43b9a 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -589,6 +589,13 @@ class Entity: from squirrelbattle.entities.friendly import Merchant return isinstance(self, Merchant) + def is_chest(self) -> bool: + """ + Is this entity a chest? + """ + from squirrelbattle.entities.friendly import Chest + return isinstance(self, Chest) + @property def translated_name(self) -> str: """ @@ -605,9 +612,9 @@ class Entity: from squirrelbattle.entities.monsters import Tiger, Hedgehog, \ Rabbit, TeddyBear, GiantSeaEagle from squirrelbattle.entities.friendly import Merchant, Sunflower, \ - Trumpet + Trumpet, Chest return [BodySnatchPotion, Bomb, Heart, Hedgehog, Rabbit, TeddyBear, - Sunflower, Tiger, Merchant, GiantSeaEagle, Trumpet] + Sunflower, Tiger, Merchant, GiantSeaEagle, Trumpet, Chest] @staticmethod def get_weights() -> list: @@ -615,8 +622,7 @@ class Entity: Returns a weigth list associated to the above function, to be used to spawn random entities with a certain probability. """ - return [3, 5, 6, 5, 5, 5, - 5, 4, 4, 1, 2] + return [3, 5, 6, 5, 5, 5, 5, 4, 3, 1, 2, 4] @staticmethod def get_all_entity_classes_in_a_dict() -> dict: @@ -627,7 +633,7 @@ class Entity: from squirrelbattle.entities.monsters import Tiger, Hedgehog, Rabbit, \ TeddyBear, GiantSeaEagle from squirrelbattle.entities.friendly import Merchant, Sunflower, \ - Trumpet + Trumpet, Chest from squirrelbattle.entities.items import BodySnatchPotion, Bomb, \ Heart, Sword, Shield, Chestplate, Helmet, RingCritical, RingXP, \ ScrollofDamage, ScrollofWeakening, Ruler, Bow, FireBallStaff @@ -655,6 +661,7 @@ class Entity: "ScrollofWeakening": ScrollofWeakening, "Bow": Bow, "FireBallStaff": FireBallStaff, + "Chest": Chest, } def save_state(self) -> dict: diff --git a/squirrelbattle/menus.py b/squirrelbattle/menus.py index 7732642..92ead1c 100644 --- a/squirrelbattle/menus.py +++ b/squirrelbattle/menus.py @@ -6,7 +6,7 @@ from typing import Any, Optional from .display.texturepack import TexturePack from .entities.player import Player -from .entities.friendly import Merchant +from .entities.friendly import Merchant, Chest from .enums import GameMode, KeyValues, DisplayActions from .settings import Settings from .translations import gettext as _, Translator @@ -158,3 +158,23 @@ class StoreMenu(Menu): Returns the values of the menu. """ return self.merchant.inventory if self.merchant else [] + + +class ChestMenu(Menu): + """ + A special instance of a menu : the menu for the inventory of a chest. + """ + chest: Chest = None + + def update_chest(self, chest: Chest) -> None: + """ + Updates the player. + """ + self.chest = chest + + @property + def values(self) -> list: + """ + Returns the values of the menu. + """ + return self.chest.inventory if self.chest else []