Merge branch 'moreitems' into 'master'
Moreitems Closes #64 and #60 See merge request ynerant/squirrel-battle!60
This commit was merged in pull request #141.
	This commit is contained in:
		| @@ -10,7 +10,8 @@ from squirrelbattle.display.mapdisplay import MapDisplay | |||||||
| from squirrelbattle.display.messagedisplay import MessageDisplay | from squirrelbattle.display.messagedisplay import MessageDisplay | ||||||
| from squirrelbattle.display.statsdisplay import StatsDisplay | from squirrelbattle.display.statsdisplay import StatsDisplay | ||||||
| from squirrelbattle.display.menudisplay import MainMenuDisplay, \ | from squirrelbattle.display.menudisplay import MainMenuDisplay, \ | ||||||
|     PlayerInventoryDisplay, StoreInventoryDisplay, SettingsMenuDisplay |     PlayerInventoryDisplay, StoreInventoryDisplay, SettingsMenuDisplay, \ | ||||||
|  |     ChestInventoryDisplay | ||||||
| from squirrelbattle.display.logsdisplay import LogsDisplay | from squirrelbattle.display.logsdisplay import LogsDisplay | ||||||
| from squirrelbattle.display.texturepack import TexturePack | from squirrelbattle.display.texturepack import TexturePack | ||||||
| from typing import Any, List | from typing import Any, List | ||||||
| @@ -29,6 +30,7 @@ class DisplayManager: | |||||||
|         self.logsdisplay = LogsDisplay(screen, pack) |         self.logsdisplay = LogsDisplay(screen, pack) | ||||||
|         self.playerinventorydisplay = PlayerInventoryDisplay(screen, pack) |         self.playerinventorydisplay = PlayerInventoryDisplay(screen, pack) | ||||||
|         self.storeinventorydisplay = StoreInventoryDisplay(screen, pack) |         self.storeinventorydisplay = StoreInventoryDisplay(screen, pack) | ||||||
|  |         self.chestinventorydisplay = ChestInventoryDisplay(screen, pack) | ||||||
|         self.mainmenudisplay = MainMenuDisplay(self.game.main_menu, |         self.mainmenudisplay = MainMenuDisplay(self.game.main_menu, | ||||||
|                                                screen, pack) |                                                screen, pack) | ||||||
|         self.settingsmenudisplay = SettingsMenuDisplay(screen, pack) |         self.settingsmenudisplay = SettingsMenuDisplay(screen, pack) | ||||||
| @@ -40,7 +42,8 @@ class DisplayManager: | |||||||
|                          self.mainmenudisplay, self.settingsmenudisplay, |                          self.mainmenudisplay, self.settingsmenudisplay, | ||||||
|                          self.logsdisplay, self.messagedisplay, |                          self.logsdisplay, self.messagedisplay, | ||||||
|                          self.playerinventorydisplay, |                          self.playerinventorydisplay, | ||||||
|                          self.storeinventorydisplay, self.creditsdisplay] |                          self.storeinventorydisplay, self.creditsdisplay, | ||||||
|  |                          self.chestinventorydisplay] | ||||||
|         self.update_game_components() |         self.update_game_components() | ||||||
|  |  | ||||||
|     def handle_display_action(self, action: DisplayActions, *params) -> None: |     def handle_display_action(self, action: DisplayActions, *params) -> None: | ||||||
| @@ -87,7 +90,8 @@ class DisplayManager: | |||||||
|  |  | ||||||
|         if self.game.state == GameMode.PLAY \ |         if self.game.state == GameMode.PLAY \ | ||||||
|                 or self.game.state == GameMode.INVENTORY \ |                 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 |             # The map pad has already the good size | ||||||
|             self.mapdisplay.refresh(0, 0, self.rows * 4 // 5, |             self.mapdisplay.refresh(0, 0, self.rows * 4 // 5, | ||||||
|                                     self.mapdisplay.pack.tile_width |                                     self.mapdisplay.pack.tile_width | ||||||
| @@ -124,6 +128,19 @@ class DisplayManager: | |||||||
|                     pack.tile_width * (2 * self.cols // (5 * pack.tile_width))) |                     pack.tile_width * (2 * self.cols // (5 * pack.tile_width))) | ||||||
|                 displays.append(self.storeinventorydisplay) |                 displays.append(self.storeinventorydisplay) | ||||||
|                 displays.append(self.playerinventorydisplay) |                 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: |         elif self.game.state == GameMode.MAINMENU: | ||||||
|             self.mainmenudisplay.refresh(0, 0, self.rows, self.cols) |             self.mainmenudisplay.refresh(0, 0, self.rows, self.cols) | ||||||
|             displays.append(self.mainmenudisplay) |             displays.append(self.mainmenudisplay) | ||||||
|   | |||||||
| @@ -5,7 +5,8 @@ import curses | |||||||
| from random import randint | from random import randint | ||||||
| from typing import List | 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 .display import Box, Display | ||||||
| from ..entities.player import Player | from ..entities.player import Player | ||||||
| from ..enums import KeyValues, GameMode | from ..enums import KeyValues, GameMode | ||||||
| @@ -156,13 +157,16 @@ class PlayerInventoryDisplay(MenuDisplay): | |||||||
|     player: Player = None |     player: Player = None | ||||||
|     selected: bool = True |     selected: bool = True | ||||||
|     store_mode: bool = False |     store_mode: bool = False | ||||||
|  |     chest_mode: bool = False | ||||||
|  |  | ||||||
|     def update(self, game: Game) -> None: |     def update(self, game: Game) -> None: | ||||||
|         self.player = game.player |         self.player = game.player | ||||||
|         self.update_menu(game.inventory_menu) |         self.update_menu(game.inventory_menu) | ||||||
|         self.store_mode = game.state == GameMode.STORE |         self.store_mode = game.state == GameMode.STORE | ||||||
|  |         self.chest_mode = game.state == GameMode.CHEST | ||||||
|         self.selected = game.state == GameMode.INVENTORY \ |         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: |     def update_pad(self) -> None: | ||||||
|         self.menubox.update_title(_("INVENTORY")) |         self.menubox.update_title(_("INVENTORY")) | ||||||
| @@ -241,3 +245,40 @@ class StoreInventoryDisplay(MenuDisplay): | |||||||
|         self.menu.position = max(0, min(len(self.menu.values) - 1, y - 2)) |         self.menu.position = max(0, min(len(self.menu.values) - 1, y - 2)) | ||||||
|         game.is_in_store_menu = True |         game.is_in_store_menu = True | ||||||
|         game.handle_key_pressed(KeyValues.ENTER) |         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, attr: 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) | ||||||
|   | |||||||
| @@ -21,9 +21,12 @@ class TexturePack: | |||||||
|  |  | ||||||
|     BODY_SNATCH_POTION: str |     BODY_SNATCH_POTION: str | ||||||
|     BOMB: str |     BOMB: str | ||||||
|  |     BOW: str | ||||||
|  |     CHEST: str | ||||||
|     CHESTPLATE: str |     CHESTPLATE: str | ||||||
|     EAGLE: str |     EAGLE: str | ||||||
|     EMPTY: str |     EMPTY: str | ||||||
|  |     FIRE_BALL_STAFF: str | ||||||
|     FLOOR: str |     FLOOR: str | ||||||
|     HAZELNUT: str |     HAZELNUT: str | ||||||
|     HEART: str |     HEART: str | ||||||
| @@ -34,6 +37,9 @@ class TexturePack: | |||||||
|     RABBIT: str |     RABBIT: str | ||||||
|     RING_OF_CRITICAL_DAMAGE: str |     RING_OF_CRITICAL_DAMAGE: str | ||||||
|     RING_OF_MORE_EXPERIENCE: str |     RING_OF_MORE_EXPERIENCE: str | ||||||
|  |     RULER: str | ||||||
|  |     SCROLL_OF_DAMAGE: str | ||||||
|  |     SCROLL_OF_WEAKENING: str | ||||||
|     SHIELD: str |     SHIELD: str | ||||||
|     SUNFLOWER: str |     SUNFLOWER: str | ||||||
|     SWORD: str |     SWORD: str | ||||||
| @@ -73,10 +79,13 @@ TexturePack.ASCII_PACK = TexturePack( | |||||||
|  |  | ||||||
|     BODY_SNATCH_POTION='S', |     BODY_SNATCH_POTION='S', | ||||||
|     BOMB='ç', |     BOMB='ç', | ||||||
|  |     BOW=')', | ||||||
|  |     CHEST='□', | ||||||
|     CHESTPLATE='(', |     CHESTPLATE='(', | ||||||
|     EAGLE='µ', |     EAGLE='µ', | ||||||
|     EMPTY=' ', |     EMPTY=' ', | ||||||
|     EXPLOSION='%', |     EXPLOSION='%', | ||||||
|  |     FIRE_BALL_STAFF=':', | ||||||
|     FLOOR='.', |     FLOOR='.', | ||||||
|     LADDER='H', |     LADDER='H', | ||||||
|     HAZELNUT='¤', |     HAZELNUT='¤', | ||||||
| @@ -89,6 +98,7 @@ TexturePack.ASCII_PACK = TexturePack( | |||||||
|     RABBIT='Y', |     RABBIT='Y', | ||||||
|     RING_OF_CRITICAL_DAMAGE='o', |     RING_OF_CRITICAL_DAMAGE='o', | ||||||
|     RING_OF_MORE_EXPERIENCE='o', |     RING_OF_MORE_EXPERIENCE='o', | ||||||
|  |     RULER='\\', | ||||||
|     SHIELD='D', |     SHIELD='D', | ||||||
|     SUNFLOWER='I', |     SUNFLOWER='I', | ||||||
|     SWORD='\u2020', |     SWORD='\u2020', | ||||||
| @@ -96,6 +106,8 @@ TexturePack.ASCII_PACK = TexturePack( | |||||||
|     TIGER='n', |     TIGER='n', | ||||||
|     TRUMPET='/', |     TRUMPET='/', | ||||||
|     WALL='#', |     WALL='#', | ||||||
|  |     SCROLL_OF_DAMAGE=']', | ||||||
|  |     SCROLL_OF_WEAKENING=']', | ||||||
| ) | ) | ||||||
|  |  | ||||||
| TexturePack.SQUIRREL_PACK = TexturePack( | TexturePack.SQUIRREL_PACK = TexturePack( | ||||||
| @@ -109,10 +121,13 @@ TexturePack.SQUIRREL_PACK = TexturePack( | |||||||
|  |  | ||||||
|     BODY_SNATCH_POTION='🔀', |     BODY_SNATCH_POTION='🔀', | ||||||
|     BOMB='💣', |     BOMB='💣', | ||||||
|  |     BOW='🏹', | ||||||
|  |     CHEST='🧰', | ||||||
|     CHESTPLATE='🦺', |     CHESTPLATE='🦺', | ||||||
|     EAGLE='🦅', |     EAGLE='🦅', | ||||||
|     EMPTY='  ', |     EMPTY='  ', | ||||||
|     EXPLOSION='💥', |     EXPLOSION='💥', | ||||||
|  |     FIRE_BALL_STAFF='🪄', | ||||||
|     FLOOR='██', |     FLOOR='██', | ||||||
|     LADDER=('🪜', curses.COLOR_WHITE, (1000, 1000, 1000), |     LADDER=('🪜', curses.COLOR_WHITE, (1000, 1000, 1000), | ||||||
|             curses.COLOR_WHITE, (1000, 1000, 1000)), |             curses.COLOR_WHITE, (1000, 1000, 1000)), | ||||||
| @@ -126,6 +141,7 @@ TexturePack.SQUIRREL_PACK = TexturePack( | |||||||
|     RABBIT='🐇', |     RABBIT='🐇', | ||||||
|     RING_OF_CRITICAL_DAMAGE='💍', |     RING_OF_CRITICAL_DAMAGE='💍', | ||||||
|     RING_OF_MORE_EXPERIENCE='💍', |     RING_OF_MORE_EXPERIENCE='💍', | ||||||
|  |     RULER='📏', | ||||||
|     SHIELD='🛡️ ', |     SHIELD='🛡️ ', | ||||||
|     SUNFLOWER='🌻', |     SUNFLOWER='🌻', | ||||||
|     SWORD='🗡️ ', |     SWORD='🗡️ ', | ||||||
| @@ -133,4 +149,6 @@ TexturePack.SQUIRREL_PACK = TexturePack( | |||||||
|     TIGER='🐅', |     TIGER='🐅', | ||||||
|     TRUMPET='🎺', |     TRUMPET='🎺', | ||||||
|     WALL='🧱', |     WALL='🧱', | ||||||
|  |     SCROLL_OF_DAMAGE='📜', | ||||||
|  |     SCROLL_OF_WEAKENING='📜', | ||||||
| ) | ) | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| from ..interfaces import FriendlyEntity, InventoryHolder, Map, FightingEntity | from ..interfaces import Entity, FriendlyEntity, InventoryHolder, \ | ||||||
|  |     Map, FightingEntity | ||||||
| from ..translations import gettext as _ | from ..translations import gettext as _ | ||||||
| from .player import Player | from .player import Player | ||||||
| from .monsters import Monster | from .monsters import Monster | ||||||
| @@ -17,8 +18,8 @@ class Merchant(InventoryHolder, FriendlyEntity): | |||||||
|         return super().keys() + ["inventory", "hazel"] |         return super().keys() + ["inventory", "hazel"] | ||||||
|  |  | ||||||
|     def __init__(self, name: str = "merchant", inventory: list = None, |     def __init__(self, name: str = "merchant", inventory: list = None, | ||||||
|                  hazel: int = 75, *args, **kwargs): |                  hazel: int = 75, maxhealth: int = 8, *args, **kwargs): | ||||||
|         super().__init__(name=name, *args, **kwargs) |         super().__init__(name=name, maxhealth=maxhealth, *args, **kwargs) | ||||||
|         self.inventory = self.translate_inventory(inventory or []) |         self.inventory = self.translate_inventory(inventory or []) | ||||||
|         self.hazel = hazel |         self.hazel = hazel | ||||||
|         if not self.inventory: |         if not self.inventory: | ||||||
| @@ -39,6 +40,40 @@ class Merchant(InventoryHolder, FriendlyEntity): | |||||||
|         self.hazel += hz |         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): | class Sunflower(FriendlyEntity): | ||||||
|     """ |     """ | ||||||
|     A friendly sunflower. |     A friendly sunflower. | ||||||
|   | |||||||
| @@ -2,7 +2,7 @@ | |||||||
| # SPDX-License-Identifier: GPL-3.0-or-later | # SPDX-License-Identifier: GPL-3.0-or-later | ||||||
|  |  | ||||||
| from random import choice, randint | from random import choice, randint | ||||||
| from typing import Optional | from typing import Optional, Any | ||||||
|  |  | ||||||
| from ..interfaces import Entity, FightingEntity, Map, InventoryHolder | from ..interfaces import Entity, FightingEntity, Map, InventoryHolder | ||||||
| from ..translations import gettext as _ | from ..translations import gettext as _ | ||||||
| @@ -47,6 +47,11 @@ class Item(Entity): | |||||||
|         Indicates what should be done when the item is used. |         Indicates what should be done when the item is used. | ||||||
|         """ |         """ | ||||||
|  |  | ||||||
|  |     def throw(self, direction: int) -> None: | ||||||
|  |         """ | ||||||
|  |         Indicates what should be done when the item is thrown. | ||||||
|  |         """ | ||||||
|  |  | ||||||
|     def equip(self) -> None: |     def equip(self) -> None: | ||||||
|         """ |         """ | ||||||
|         Indicates what should be done when the item is equipped. |         Indicates what should be done when the item is equipped. | ||||||
| @@ -86,16 +91,22 @@ class Item(Entity): | |||||||
|         """ |         """ | ||||||
|         Returns the list of all item classes. |         Returns the list of all item classes. | ||||||
|         """ |         """ | ||||||
|         return [BodySnatchPotion, Chestplate, Bomb, Heart, Helmet, Monocle, |         return [BodySnatchPotion, Bomb, Bow, Chestplate, FireBallStaff, | ||||||
|                 Shield, Sword, RingCritical, RingXP] |                 Heart, Helmet, Monocle, ScrollofDamage, ScrollofWeakening, | ||||||
|  |                 Shield, Sword, RingCritical, RingXP, Ruler] | ||||||
|  |  | ||||||
|     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. |         Does all necessary actions when an object is to be sold. | ||||||
|         Is overwritten by some classes that cannot exist in the player's |         Is overwritten by some classes that cannot exist in the player's | ||||||
|         inventory. |         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) |             self.hold(buyer) | ||||||
|             seller.remove_from_inventory(self) |             seller.remove_from_inventory(self) | ||||||
|             buyer.change_hazel_balance(-self.price) |             buyer.change_hazel_balance(-self.price) | ||||||
| @@ -266,6 +277,15 @@ class Sword(Weapon): | |||||||
|         super().__init__(name=name, price=price, *args, **kwargs) |         super().__init__(name=name, price=price, *args, **kwargs) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Ruler(Weapon): | ||||||
|  |     """ | ||||||
|  |     A basic weapon | ||||||
|  |     """ | ||||||
|  |     def __init__(self, name: str = "ruler", price: int = 2, | ||||||
|  |                  damage: int = 1, *args, **kwargs): | ||||||
|  |         super().__init__(name=name, price=price, damage=damage, *args, **kwargs) | ||||||
|  |  | ||||||
|  |  | ||||||
| class Armor(Item): | class Armor(Item): | ||||||
|     """ |     """ | ||||||
|     Class of items that increase the player's constitution. |     Class of items that increase the player's constitution. | ||||||
| @@ -455,6 +475,165 @@ class RingXP(Ring): | |||||||
|                          *args, **kwargs) |                          *args, **kwargs) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class ScrollofDamage(Item): | ||||||
|  |     """ | ||||||
|  |     A scroll that, when used, deals damage to all entities in a certain radius. | ||||||
|  |     """ | ||||||
|  |     def __init__(self, name: str = "scroll_of_damage", price: int = 18, | ||||||
|  |                  *args, **kwargs): | ||||||
|  |         super().__init__(name=name, price=price, *args, **kwargs) | ||||||
|  |  | ||||||
|  |     def use(self) -> None: | ||||||
|  |         """ | ||||||
|  |         Find all entities within a radius of 5, and deal damage based on the | ||||||
|  |         player's intelligence. | ||||||
|  |         """ | ||||||
|  |         for entity in self.held_by.map.entities: | ||||||
|  |             if entity.is_fighting_entity() and not entity == self.held_by: | ||||||
|  |                 if entity.distance(self.held_by) <= 5: | ||||||
|  |                     self.held_by.map.logs.add_message(entity.take_damage( | ||||||
|  |                         self.held_by, self.held_by.intelligence)) | ||||||
|  |         self.held_by.inventory.remove(self) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class ScrollofWeakening(Item): | ||||||
|  |     """ | ||||||
|  |     A scroll that, when used, reduces the damage of the ennemies for 3 turn. | ||||||
|  |     """ | ||||||
|  |     def __init__(self, name: str = "scroll_of_weakening", price: int = 13, | ||||||
|  |                  *args, **kwargs): | ||||||
|  |         super().__init__(name=name, price=price, *args, **kwargs) | ||||||
|  |  | ||||||
|  |     def use(self) -> None: | ||||||
|  |         """ | ||||||
|  |         Find all entities and reduce their damage. | ||||||
|  |         """ | ||||||
|  |         for entity in self.held_by.map.entities: | ||||||
|  |             if entity.is_fighting_entity() and not entity == self.held_by: | ||||||
|  |                 entity.strength = entity.strength - \ | ||||||
|  |                     max(1, self.held_by.intelligence // 2) | ||||||
|  |                 entity.effects.append(["strength", | ||||||
|  |                                        -max(1, self.held_by.intelligence // 2), | ||||||
|  |                                        3]) | ||||||
|  |         self.held_by.map.logs.add_message( | ||||||
|  |             _(f"The ennemies have -{max(1, self.held_by.intelligence // 2)}" | ||||||
|  |               + "strength for 3 turns")) | ||||||
|  |         self.held_by.inventory.remove(self) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class LongRangeWeapon(Item): | ||||||
|  |     def __init__(self, damage: int = 4, | ||||||
|  |                  rang: int = 3, *args, **kwargs): | ||||||
|  |         super().__init__(*args, **kwargs) | ||||||
|  |         self.damage = damage | ||||||
|  |         self.range = rang | ||||||
|  |  | ||||||
|  |     def throw(self, direction: int) -> Any: | ||||||
|  |         to_kill = None | ||||||
|  |         for entity in self.held_by.map.entities: | ||||||
|  |             if entity.is_fighting_entity(): | ||||||
|  |                 if direction == 0 and self.held_by.x == entity.x \ | ||||||
|  |                     and self.held_by.y - entity.y > 0 and \ | ||||||
|  |                         self.held_by.y - entity.y <= self.range: | ||||||
|  |                     to_kill = entity | ||||||
|  |                 elif direction == 2 and self.held_by.x == entity.x \ | ||||||
|  |                     and entity.y - self.held_by.y > 0 and \ | ||||||
|  |                         entity.y - self.held_by.y <= self.range: | ||||||
|  |                     to_kill = entity | ||||||
|  |                 elif direction == 1 and self.held_by.y == entity.y \ | ||||||
|  |                     and entity.x - self.held_by.x > 0 and \ | ||||||
|  |                         entity.x - self.held_by.x <= self.range: | ||||||
|  |                     to_kill = entity | ||||||
|  |                 elif direction == 3 and self.held_by.y == entity.y \ | ||||||
|  |                     and self.held_by.x - entity.x > 0 and \ | ||||||
|  |                         self.held_by.x - entity.x <= self.range: | ||||||
|  |                     to_kill = entity | ||||||
|  |         if to_kill: | ||||||
|  |             line = _("{name}").format(name=to_kill.translated_name.capitalize() | ||||||
|  |                                       ) + self.string + " "\ | ||||||
|  |                                         + to_kill.take_damage( | ||||||
|  |                                             self.held_by, self.damage | ||||||
|  |                                             + getattr(self.held_by, self.stat)) | ||||||
|  |             self.held_by.map.logs.add_message(line) | ||||||
|  |         return (to_kill.x, to_kill.y) if to_kill else None | ||||||
|  |  | ||||||
|  |     def equip(self) -> None: | ||||||
|  |         """ | ||||||
|  |         Equip the weapon. | ||||||
|  |         """ | ||||||
|  |         self.held_by.remove_from_inventory(self) | ||||||
|  |         self.held_by.equipped_main = self | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def stat(self) -> str: | ||||||
|  |         """ | ||||||
|  |         The stat that is used when using the object: dexterity for a bow | ||||||
|  |         or intelligence for a magic staff. | ||||||
|  |         """ | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def string(self) -> str: | ||||||
|  |         """ | ||||||
|  |         The string that is printed when we hit an ennemy. | ||||||
|  |         """ | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Bow(LongRangeWeapon): | ||||||
|  |     """ | ||||||
|  |     A type of long range weapon that deals damage | ||||||
|  |     based on the player's dexterity | ||||||
|  |     """ | ||||||
|  |     def __init__(self, name: str = "bow", price: int = 22, damage: int = 4, | ||||||
|  |                  rang: int = 3, *args, **kwargs): | ||||||
|  |         super().__init__(name=name, price=price, damage=damage, | ||||||
|  |                          rang=rang, *args, **kwargs) | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def stat(self) -> str: | ||||||
|  |         """ | ||||||
|  |         Here it is dexterity | ||||||
|  |         """ | ||||||
|  |         return "dexterity" | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def string(self) -> str: | ||||||
|  |         return " is shot by an arrow." | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class FireBallStaff(LongRangeWeapon): | ||||||
|  |     """ | ||||||
|  |     A type of powerful long range weapon that deals damage | ||||||
|  |     based on the player's intelligence | ||||||
|  |     """ | ||||||
|  |     def __init__(self, name: str = "fire_ball_staff", price: int = 36, | ||||||
|  |                  damage: int = 6, rang: int = 4, *args, **kwargs): | ||||||
|  |         super().__init__(name=name, price=price, damage=damage, | ||||||
|  |                          rang=rang, *args, **kwargs) | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def stat(self) -> str: | ||||||
|  |         """ | ||||||
|  |         Here it is dexterity | ||||||
|  |         """ | ||||||
|  |         return "intelligence" | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def string(self) -> str: | ||||||
|  |         return " is shot by a fire ball." | ||||||
|  |  | ||||||
|  |     def throw(self, direction: int) -> None: | ||||||
|  |         """ | ||||||
|  |         Adds an explosion animation when killing something. | ||||||
|  |         """ | ||||||
|  |         coord = super().throw(direction) | ||||||
|  |         if coord: | ||||||
|  |             x = coord[0] | ||||||
|  |             y = coord[1] | ||||||
|  |  | ||||||
|  |             explosion = Explosion(y=y, x=x) | ||||||
|  |             self.held_by.map.add_entity(explosion) | ||||||
|  |  | ||||||
|  |  | ||||||
| class Monocle(Item): | class Monocle(Item): | ||||||
|     def __init__(self, name: str = "monocle", price: int = 10, |     def __init__(self, name: str = "monocle", price: int = 10, | ||||||
|                  *args, **kwargs): |                  *args, **kwargs): | ||||||
|   | |||||||
| @@ -31,6 +31,7 @@ class Monster(FightingEntity): | |||||||
|         By default, a monster will move randomly where it is possible |         By default, a monster will move randomly where it is possible | ||||||
|         If the player is closeby, the monster runs to the player. |         If the player is closeby, the monster runs to the player. | ||||||
|         """ |         """ | ||||||
|  |         super().act(m) | ||||||
|         target = None |         target = None | ||||||
|         for entity in m.entities: |         for entity in m.entities: | ||||||
|             if self.distance_squared(entity) <= 25 and \ |             if self.distance_squared(entity) <= 25 and \ | ||||||
|   | |||||||
| @@ -28,6 +28,7 @@ class GameMode(Enum): | |||||||
|     SETTINGS = auto() |     SETTINGS = auto() | ||||||
|     INVENTORY = auto() |     INVENTORY = auto() | ||||||
|     STORE = auto() |     STORE = auto() | ||||||
|  |     CHEST = auto() | ||||||
|     CREDITS = auto() |     CREDITS = auto() | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -48,6 +49,7 @@ class KeyValues(Enum): | |||||||
|     CHAT = auto() |     CHAT = auto() | ||||||
|     WAIT = auto() |     WAIT = auto() | ||||||
|     LADDER = auto() |     LADDER = auto() | ||||||
|  |     LAUNCH = auto() | ||||||
|  |  | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     def translate_key(key: str, settings: Settings) -> Optional["KeyValues"]: |     def translate_key(key: str, settings: Settings) -> Optional["KeyValues"]: | ||||||
| @@ -84,4 +86,6 @@ class KeyValues(Enum): | |||||||
|             return KeyValues.WAIT |             return KeyValues.WAIT | ||||||
|         elif key == settings.KEY_LADDER: |         elif key == settings.KEY_LADDER: | ||||||
|             return KeyValues.LADDER |             return KeyValues.LADDER | ||||||
|  |         elif key == settings.KEY_LAUNCH: | ||||||
|  |             return KeyValues.LAUNCH | ||||||
|         return None |         return None | ||||||
|   | |||||||
| @@ -35,7 +35,9 @@ class Game: | |||||||
|         """ |         """ | ||||||
|         self.state = GameMode.MAINMENU |         self.state = GameMode.MAINMENU | ||||||
|         self.waiting_for_friendly_key = False |         self.waiting_for_friendly_key = False | ||||||
|  |         self.waiting_for_launch_key = False | ||||||
|         self.is_in_store_menu = True |         self.is_in_store_menu = True | ||||||
|  |         self.is_in_chest_menu = True | ||||||
|         self.settings = Settings() |         self.settings = Settings() | ||||||
|         self.settings.load_settings() |         self.settings.load_settings() | ||||||
|         self.settings.write_settings() |         self.settings.write_settings() | ||||||
| @@ -45,6 +47,7 @@ class Game: | |||||||
|         self.settings_menu.update_values(self.settings) |         self.settings_menu.update_values(self.settings) | ||||||
|         self.inventory_menu = menus.InventoryMenu() |         self.inventory_menu = menus.InventoryMenu() | ||||||
|         self.store_menu = menus.StoreMenu() |         self.store_menu = menus.StoreMenu() | ||||||
|  |         self.chest_menu = menus.ChestMenu() | ||||||
|         self.logs = Logs() |         self.logs = Logs() | ||||||
|         self.message = None |         self.message = None | ||||||
|  |  | ||||||
| @@ -121,6 +124,9 @@ class Game: | |||||||
|             if self.waiting_for_friendly_key: |             if self.waiting_for_friendly_key: | ||||||
|                 # The player requested to talk with a friendly entity |                 # The player requested to talk with a friendly entity | ||||||
|                 self.handle_friendly_entity_chat(key) |                 self.handle_friendly_entity_chat(key) | ||||||
|  |             elif self.waiting_for_launch_key: | ||||||
|  |                 # The player requested to launch | ||||||
|  |                 self.handle_launch(key) | ||||||
|             else: |             else: | ||||||
|                 self.handle_key_pressed_play(key) |                 self.handle_key_pressed_play(key) | ||||||
|         elif self.state == GameMode.INVENTORY: |         elif self.state == GameMode.INVENTORY: | ||||||
| @@ -131,6 +137,8 @@ class Game: | |||||||
|             self.settings_menu.handle_key_pressed(key, raw_key, self) |             self.settings_menu.handle_key_pressed(key, raw_key, self) | ||||||
|         elif self.state == GameMode.STORE: |         elif self.state == GameMode.STORE: | ||||||
|             self.handle_key_pressed_store(key) |             self.handle_key_pressed_store(key) | ||||||
|  |         elif self.state == GameMode.CHEST: | ||||||
|  |             self.handle_key_pressed_chest(key) | ||||||
|         elif self.state == GameMode.CREDITS: |         elif self.state == GameMode.CREDITS: | ||||||
|             self.state = GameMode.MAINMENU |             self.state = GameMode.MAINMENU | ||||||
|         self.display_actions(DisplayActions.REFRESH) |         self.display_actions(DisplayActions.REFRESH) | ||||||
| @@ -159,6 +167,9 @@ class Game: | |||||||
|                 self.player.equipped_main.use() |                 self.player.equipped_main.use() | ||||||
|             if self.player.equipped_secondary: |             if self.player.equipped_secondary: | ||||||
|                 self.player.equipped_secondary.use() |                 self.player.equipped_secondary.use() | ||||||
|  |         elif key == KeyValues.LAUNCH: | ||||||
|  |             # Wait for the direction to launch in | ||||||
|  |             self.waiting_for_launch_key = True | ||||||
|         elif key == KeyValues.SPACE: |         elif key == KeyValues.SPACE: | ||||||
|             self.state = GameMode.MAINMENU |             self.state = GameMode.MAINMENU | ||||||
|         elif key == KeyValues.CHAT: |         elif key == KeyValues.CHAT: | ||||||
| @@ -250,6 +261,35 @@ class Game: | |||||||
|                         self.is_in_store_menu = True |                         self.is_in_store_menu = True | ||||||
|                         self.store_menu.update_merchant(entity) |                         self.store_menu.update_merchant(entity) | ||||||
|                         self.display_actions(DisplayActions.UPDATE) |                         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: | ||||||
|  |         """ | ||||||
|  |         If the player tries to throw something in a direction, the game looks | ||||||
|  |         for entities in that direction and within the range of the player's | ||||||
|  |         weapon and adds damage | ||||||
|  |         """ | ||||||
|  |         if not self.waiting_for_launch_key: | ||||||
|  |             return | ||||||
|  |         self.waiting_for_launch_key = False | ||||||
|  |  | ||||||
|  |         if key == KeyValues.UP: | ||||||
|  |             direction = 0 | ||||||
|  |         elif key == KeyValues.DOWN: | ||||||
|  |             direction = 2 | ||||||
|  |         elif key == KeyValues.LEFT: | ||||||
|  |             direction = 3 | ||||||
|  |         elif key == KeyValues.RIGHT: | ||||||
|  |             direction = 1 | ||||||
|  |         else: | ||||||
|  |             return | ||||||
|  |  | ||||||
|  |         if self.player.equipped_main: | ||||||
|  |             self.player.equipped_main.throw(direction) | ||||||
|  |  | ||||||
|     def handle_key_pressed_inventory(self, key: KeyValues) -> None: |     def handle_key_pressed_inventory(self, key: KeyValues) -> None: | ||||||
|         """ |         """ | ||||||
| @@ -306,6 +346,36 @@ class Game: | |||||||
|             # Ensure that the cursor has a good position |             # Ensure that the cursor has a good position | ||||||
|             menu.position = min(menu.position, len(menu.values) - 1) |             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 | ||||||
|  |                 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: |     def handle_key_pressed_main_menu(self, key: KeyValues) -> None: | ||||||
|         """ |         """ | ||||||
|         In the main menu, we can navigate through different options. |         In the main menu, we can navigate through different options. | ||||||
|   | |||||||
| @@ -606,6 +606,13 @@ class Entity: | |||||||
|         from squirrelbattle.entities.friendly import Merchant |         from squirrelbattle.entities.friendly import Merchant | ||||||
|         return isinstance(self, 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 |     @property | ||||||
|     def translated_name(self) -> str: |     def translated_name(self) -> str: | ||||||
|         """ |         """ | ||||||
| @@ -622,9 +629,9 @@ class Entity: | |||||||
|         from squirrelbattle.entities.monsters import Tiger, Hedgehog, \ |         from squirrelbattle.entities.monsters import Tiger, Hedgehog, \ | ||||||
|             Rabbit, TeddyBear, GiantSeaEagle |             Rabbit, TeddyBear, GiantSeaEagle | ||||||
|         from squirrelbattle.entities.friendly import Merchant, Sunflower, \ |         from squirrelbattle.entities.friendly import Merchant, Sunflower, \ | ||||||
|             Trumpet |             Trumpet, Chest | ||||||
|         return [BodySnatchPotion, Bomb, Heart, Hedgehog, Rabbit, TeddyBear, |         return [BodySnatchPotion, Bomb, Heart, Hedgehog, Rabbit, TeddyBear, | ||||||
|                 Sunflower, Tiger, Merchant, GiantSeaEagle, Trumpet] |                 Sunflower, Tiger, Merchant, GiantSeaEagle, Trumpet, Chest] | ||||||
|  |  | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     def get_weights() -> list: |     def get_weights() -> list: | ||||||
| @@ -632,8 +639,7 @@ class Entity: | |||||||
|         Returns a weigth list associated to the above function, to |         Returns a weigth list associated to the above function, to | ||||||
|         be used to spawn random entities with a certain probability. |         be used to spawn random entities with a certain probability. | ||||||
|         """ |         """ | ||||||
|         return [3, 5, 6, 5, 5, 5, |         return [3, 5, 6, 5, 5, 5, 5, 4, 3, 1, 2, 4] | ||||||
|                 5, 4, 4, 1, 2] |  | ||||||
|  |  | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     def get_all_entity_classes_in_a_dict() -> dict: |     def get_all_entity_classes_in_a_dict() -> dict: | ||||||
| @@ -644,30 +650,37 @@ class Entity: | |||||||
|         from squirrelbattle.entities.monsters import Tiger, Hedgehog, Rabbit, \ |         from squirrelbattle.entities.monsters import Tiger, Hedgehog, Rabbit, \ | ||||||
|             TeddyBear, GiantSeaEagle |             TeddyBear, GiantSeaEagle | ||||||
|         from squirrelbattle.entities.friendly import Merchant, Sunflower, \ |         from squirrelbattle.entities.friendly import Merchant, Sunflower, \ | ||||||
|             Trumpet |             Trumpet, Chest | ||||||
|         from squirrelbattle.entities.items import BodySnatchPotion, Bomb, \ |         from squirrelbattle.entities.items import BodySnatchPotion, Bomb, \ | ||||||
|             Heart, Monocle, Sword, Shield, Chestplate, Helmet, \ |             Heart, Sword, Shield, Chestplate, Helmet, RingCritical, RingXP, \ | ||||||
|             RingCritical, RingXP |             ScrollofDamage, ScrollofWeakening, Ruler, Bow, FireBallStaff, \ | ||||||
|  |             Monocle | ||||||
|         return { |         return { | ||||||
|             "Bomb": Bomb, |  | ||||||
|             "Chestplate": Chestplate, |  | ||||||
|             "Heart": Heart, |  | ||||||
|             "BodySnatchPotion": BodySnatchPotion, |             "BodySnatchPotion": BodySnatchPotion, | ||||||
|  |             "Bomb": Bomb, | ||||||
|  |             "Bow": Bow, | ||||||
|  |             "Chest": Chest, | ||||||
|  |             "Chestplate": Chestplate, | ||||||
|             "Eagle": GiantSeaEagle, |             "Eagle": GiantSeaEagle, | ||||||
|  |             "FireBallStaff": FireBallStaff, | ||||||
|  |             "Heart": Heart, | ||||||
|             "Hedgehog": Hedgehog, |             "Hedgehog": Hedgehog, | ||||||
|             "Helmet": Helmet, |             "Helmet": Helmet, | ||||||
|             "Player": Player, |  | ||||||
|             "Merchant": Merchant, |             "Merchant": Merchant, | ||||||
|             "Monocle": Monocle, |             "Monocle": Monocle, | ||||||
|             "Sunflower": Sunflower, |             "Player": Player, | ||||||
|             "Sword": Sword, |  | ||||||
|             "Trumpet": Trumpet, |  | ||||||
|             "Shield": Shield, |  | ||||||
|             "TeddyBear": TeddyBear, |  | ||||||
|             "Tiger": Tiger, |  | ||||||
|             "Rabbit": Rabbit, |             "Rabbit": Rabbit, | ||||||
|             "RingCritical": RingCritical, |             "RingCritical": RingCritical, | ||||||
|             "RingXP": RingXP, |             "RingXP": RingXP, | ||||||
|  |             "Ruler": Ruler, | ||||||
|  |             "ScrollofDamage": ScrollofDamage, | ||||||
|  |             "ScrollofWeakening": ScrollofWeakening, | ||||||
|  |             "Shield": Shield, | ||||||
|  |             "Sunflower": Sunflower, | ||||||
|  |             "Sword": Sword, | ||||||
|  |             "Trumpet": Trumpet, | ||||||
|  |             "TeddyBear": TeddyBear, | ||||||
|  |             "Tiger": Tiger, | ||||||
|         } |         } | ||||||
|  |  | ||||||
|     def save_state(self) -> dict: |     def save_state(self) -> dict: | ||||||
| @@ -710,6 +723,7 @@ class FightingEntity(Entity): | |||||||
|         self.constitution = constitution |         self.constitution = constitution | ||||||
|         self.level = level |         self.level = level | ||||||
|         self.critical = critical |         self.critical = critical | ||||||
|  |         self.effects = []  # effects = temporary buff or weakening of the stats. | ||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def dead(self) -> bool: |     def dead(self) -> bool: | ||||||
| @@ -718,13 +732,27 @@ class FightingEntity(Entity): | |||||||
|         """ |         """ | ||||||
|         return self.health <= 0 |         return self.health <= 0 | ||||||
|  |  | ||||||
|  |     def act(self, m: Map) -> None: | ||||||
|  |         """ | ||||||
|  |         Refreshes all current effects. | ||||||
|  |         """ | ||||||
|  |         for i in range(len(self.effects)): | ||||||
|  |             self.effects[i][2] -= 1 | ||||||
|  |  | ||||||
|  |         copy = self.effects[:] | ||||||
|  |         for i in range(len(copy)): | ||||||
|  |             if copy[i][2] <= 0: | ||||||
|  |                 setattr(self, copy[i][0], | ||||||
|  |                         getattr(self, copy[i][0]) - copy[i][1]) | ||||||
|  |                 self.effects.remove(copy[i]) | ||||||
|  |  | ||||||
|     def hit(self, opponent: "FightingEntity") -> str: |     def hit(self, opponent: "FightingEntity") -> str: | ||||||
|         """ |         """ | ||||||
|         The entity deals damage to the opponent |         The entity deals damage to the opponent | ||||||
|         based on their respective stats. |         based on their respective stats. | ||||||
|         """ |         """ | ||||||
|         diceroll = randint(1, 100) |         diceroll = randint(1, 100) | ||||||
|         damage = self.strength |         damage = max(0, self.strength) | ||||||
|         string = " " |         string = " " | ||||||
|         if diceroll <= self.critical:  # It is a critical hit |         if diceroll <= self.critical:  # It is a critical hit | ||||||
|             damage *= 4 |             damage *= 4 | ||||||
|   | |||||||
| @@ -6,7 +6,7 @@ from typing import Any, Optional | |||||||
|  |  | ||||||
| from .display.texturepack import TexturePack | from .display.texturepack import TexturePack | ||||||
| from .entities.player import Player | from .entities.player import Player | ||||||
| from .entities.friendly import Merchant | from .entities.friendly import Merchant, Chest | ||||||
| from .enums import GameMode, KeyValues, DisplayActions | from .enums import GameMode, KeyValues, DisplayActions | ||||||
| from .settings import Settings | from .settings import Settings | ||||||
| from .translations import gettext as _, Translator | from .translations import gettext as _, Translator | ||||||
| @@ -158,3 +158,23 @@ class StoreMenu(Menu): | |||||||
|         Returns the values of the menu. |         Returns the values of the menu. | ||||||
|         """ |         """ | ||||||
|         return self.merchant.inventory if self.merchant else [] |         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 [] | ||||||
|   | |||||||
| @@ -35,6 +35,7 @@ class Settings: | |||||||
|         self.KEY_CHAT = ['t', 'Key used to talk to a friendly entity'] |         self.KEY_CHAT = ['t', 'Key used to talk to a friendly entity'] | ||||||
|         self.KEY_WAIT = ['w', 'Key used to wait'] |         self.KEY_WAIT = ['w', 'Key used to wait'] | ||||||
|         self.KEY_LADDER = ['<', 'Key used to use ladders'] |         self.KEY_LADDER = ['<', 'Key used to use ladders'] | ||||||
|  |         self.KEY_LAUNCH = ['l', 'Key used to use a bow'] | ||||||
|         self.TEXTURE_PACK = ['ascii', 'Texture pack'] |         self.TEXTURE_PACK = ['ascii', 'Texture pack'] | ||||||
|         self.LOCALE = [locale.getlocale()[0][:2], 'Language'] |         self.LOCALE = [locale.getlocale()[0][:2], 'Language'] | ||||||
|  |  | ||||||
|   | |||||||
| @@ -8,13 +8,14 @@ import unittest | |||||||
| from ..bootstrap import Bootstrap | from ..bootstrap import Bootstrap | ||||||
| from ..display.display import Display | from ..display.display import Display | ||||||
| from ..display.display_manager import DisplayManager | from ..display.display_manager import DisplayManager | ||||||
| from ..entities.friendly import Merchant, Sunflower | from ..entities.friendly import Merchant, Sunflower, Chest | ||||||
| from ..entities.items import Bomb, Heart, Sword, Explosion, Shield, Helmet, \ | from ..entities.items import Bomb, Heart, Sword, Explosion, Shield, Helmet, \ | ||||||
|     Chestplate, RingCritical, Monocle |     Chestplate, RingCritical, Bow, FireBallStaff, ScrollofDamage,\ | ||||||
| from ..entities.monsters import GiantSeaEagle |     ScrollofWeakening, Monocle | ||||||
|  | from ..entities.monsters import Rabbit, GiantSeaEagle | ||||||
| from ..entities.player import Player | from ..entities.player import Player | ||||||
| from ..enums import DisplayActions | from ..enums import DisplayActions, KeyValues, GameMode | ||||||
| from ..game import Game, KeyValues, GameMode | from ..game import Game | ||||||
| from ..interfaces import Map | from ..interfaces import Map | ||||||
| from ..menus import MainMenuValues | from ..menus import MainMenuValues | ||||||
| from ..resources import ResourceManager | from ..resources import ResourceManager | ||||||
| @@ -349,7 +350,7 @@ class TestGame(unittest.TestCase): | |||||||
|         self.assertEqual(self.game.settings.KEY_LEFT_PRIMARY, 'a') |         self.assertEqual(self.game.settings.KEY_LEFT_PRIMARY, 'a') | ||||||
|  |  | ||||||
|         # Navigate to "texture pack" |         # Navigate to "texture pack" | ||||||
|         for ignored in range(12): |         for ignored in range(13): | ||||||
|             self.game.handle_key_pressed(KeyValues.DOWN) |             self.game.handle_key_pressed(KeyValues.DOWN) | ||||||
|  |  | ||||||
|         # Change texture pack |         # Change texture pack | ||||||
| @@ -767,3 +768,157 @@ class TestGame(unittest.TestCase): | |||||||
|         self.game.handle_key_pressed(KeyValues.ENTER) |         self.game.handle_key_pressed(KeyValues.ENTER) | ||||||
|  |  | ||||||
|         self.assertEqual(self.game.state, GameMode.MAINMENU) |         self.assertEqual(self.game.state, GameMode.MAINMENU) | ||||||
|  |  | ||||||
|  |     def test_launch(self) -> None: | ||||||
|  |         """ | ||||||
|  |         Use the long range weapons to kill some entities. | ||||||
|  |         """ | ||||||
|  |         self.game.state = GameMode.PLAY | ||||||
|  |         self.game.player.move(2, 6) | ||||||
|  |  | ||||||
|  |         b = Bow() | ||||||
|  |         b.held_by = self.game.player | ||||||
|  |         self.game.player.equipped_main = b | ||||||
|  |         self.assertTrue(self.game.player.equipped_main) | ||||||
|  |  | ||||||
|  |         entity = Rabbit() | ||||||
|  |         entity.health = 1 | ||||||
|  |         self.game.map.add_entity(entity) | ||||||
|  |         entity.move(3, 6) | ||||||
|  |  | ||||||
|  |         self.game.handle_launch(KeyValues.UP) | ||||||
|  |  | ||||||
|  |         self.game.waiting_for_launch_key = True | ||||||
|  |         self.game.handle_key_pressed(KeyValues.CHAT) | ||||||
|  |  | ||||||
|  |         entity = Rabbit() | ||||||
|  |         entity.health = 1 | ||||||
|  |         self.game.map.add_entity(entity) | ||||||
|  |         entity.move(2, 8) | ||||||
|  |         self.game.waiting_for_launch_key = True | ||||||
|  |         self.game.handle_key_pressed(KeyValues.RIGHT) | ||||||
|  |  | ||||||
|  |         entity = Rabbit() | ||||||
|  |         entity.health = 1 | ||||||
|  |         self.game.map.add_entity(entity) | ||||||
|  |         entity.move(2, 5) | ||||||
|  |         self.game.waiting_for_launch_key = True | ||||||
|  |         self.game.handle_key_pressed(KeyValues.LEFT) | ||||||
|  |  | ||||||
|  |         key = "l" | ||||||
|  |         KeyValues.translate_key(key, self.game.settings) | ||||||
|  |  | ||||||
|  |         self.game.handle_key_pressed(KeyValues.LAUNCH) | ||||||
|  |         self.assertTrue(self.game.waiting_for_launch_key) | ||||||
|  |         self.game.handle_key_pressed(KeyValues.DOWN) | ||||||
|  |  | ||||||
|  |         self.assertTrue(entity.dead) | ||||||
|  |  | ||||||
|  |         entity2 = Rabbit() | ||||||
|  |         entity2.health = 1 | ||||||
|  |         self.game.map.add_entity(entity2) | ||||||
|  |         entity2.move(1, 6) | ||||||
|  |  | ||||||
|  |         b = FireBallStaff() | ||||||
|  |         self.game.player.inventory.append(b) | ||||||
|  |         b.held_by = self.game.player | ||||||
|  |         b.equip() | ||||||
|  |  | ||||||
|  |         self.game.handle_key_pressed(KeyValues.LAUNCH) | ||||||
|  |         self.assertTrue(self.game.waiting_for_launch_key) | ||||||
|  |         self.game.handle_key_pressed(KeyValues.UP) | ||||||
|  |  | ||||||
|  |         self.assertTrue(entity2.dead) | ||||||
|  |  | ||||||
|  |     def test_scrolls(self) -> None: | ||||||
|  |         """ | ||||||
|  |         Use the scrolls. | ||||||
|  |         """ | ||||||
|  |         self.game.state = GameMode.PLAY | ||||||
|  |         self.game.player.move(2, 6) | ||||||
|  |  | ||||||
|  |         entity = Rabbit() | ||||||
|  |         self.game.map.add_entity(entity) | ||||||
|  |         entity.move(3, 6) | ||||||
|  |  | ||||||
|  |         entity2 = GiantSeaEagle() | ||||||
|  |         self.game.map.add_entity(entity2) | ||||||
|  |         entity2.move(3, 8) | ||||||
|  |  | ||||||
|  |         scroll1 = ScrollofDamage() | ||||||
|  |         scroll2 = ScrollofWeakening() | ||||||
|  |         self.game.player.inventory.append(scroll1) | ||||||
|  |         self.game.player.inventory.append(scroll2) | ||||||
|  |         scroll1.held_by = self.game.player | ||||||
|  |         scroll2.held_by = self.game.player | ||||||
|  |  | ||||||
|  |         scroll1.use() | ||||||
|  |         self.assertTrue(entity.health != entity.maxhealth) | ||||||
|  |         self.assertTrue(entity2.health != entity2.maxhealth) | ||||||
|  |  | ||||||
|  |         scroll2.use() | ||||||
|  |         self.assertEqual(entity.strength, 0) | ||||||
|  |         self.assertEqual(entity2.strength, 999) | ||||||
|  |  | ||||||
|  |         self.game.map.tick(self.game.player) | ||||||
|  |         self.game.map.tick(self.game.player) | ||||||
|  |         self.game.map.tick(self.game.player) | ||||||
|  |  | ||||||
|  |         self.assertEqual(entity2.effects, []) | ||||||
|  |  | ||||||
|  |     def test_chests(self) -> None: | ||||||
|  |         """ | ||||||
|  |         Interacts with chests. | ||||||
|  |         """ | ||||||
|  |         self.game.state = GameMode.PLAY | ||||||
|  |  | ||||||
|  |         chest = Chest() | ||||||
|  |         chest.move(2, 6) | ||||||
|  |         self.game.map.add_entity(chest) | ||||||
|  |         chest.inventory.append(FireBallStaff()) | ||||||
|  |  | ||||||
|  |         # 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.CHEST) | ||||||
|  |         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.LEFT) | ||||||
|  |         self.assertFalse(self.game.is_in_chest_menu) | ||||||
|  |         self.game.handle_key_pressed(KeyValues.RIGHT) | ||||||
|  |         self.assertTrue(self.game.is_in_chest_menu) | ||||||
|  |         self.game.handle_key_pressed(KeyValues.UP) | ||||||
|  |         self.assertEqual(self.game.chest_menu.position, 1) | ||||||
|  |  | ||||||
|  |         # The second item is not a heart | ||||||
|  |         chest.inventory[1] = sword = Sword() | ||||||
|  |         # Take the second item | ||||||
|  |         item = self.game.chest_menu.validate() | ||||||
|  |         self.assertIn(item, chest.inventory) | ||||||
|  |         self.game.display_actions(DisplayActions.MOUSE, 7, 25, | ||||||
|  |                                   curses.BUTTON1_CLICKED) | ||||||
|  |         self.assertIn(item, self.game.player.inventory) | ||||||
|  |         self.assertNotIn(item, chest.inventory) | ||||||
|  |  | ||||||
|  |         # Give an item back | ||||||
|  |         self.game.inventory_menu.position = len(self.game.player.inventory) - 1 | ||||||
|  |         self.game.handle_key_pressed(KeyValues.LEFT) | ||||||
|  |         self.assertFalse(self.game.is_in_chest_menu) | ||||||
|  |         self.assertIn(sword, self.game.player.inventory) | ||||||
|  |         self.assertEqual(self.game.inventory_menu.validate(), sword) | ||||||
|  |         self.game.handle_key_pressed(KeyValues.ENTER) | ||||||
|  |         self.assertNotIn(sword, self.game.player.inventory) | ||||||
|  |         self.assertIn(sword, chest.inventory) | ||||||
|  |  | ||||||
|  |         # Test immortality | ||||||
|  |         self.game.player.hit(chest) | ||||||
|  |         self.assertTrue(not chest.dead) | ||||||
|  |  | ||||||
|  |         # Exit the menu | ||||||
|  |         self.game.handle_key_pressed(KeyValues.SPACE) | ||||||
|  |         self.assertEqual(self.game.state, GameMode.PLAY) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user