Merge branch 'moreitems' into 'master'
Moreitems Closes #64 and #60 See merge request ynerant/squirrel-battle!60
This commit is contained in:
commit
93a9e5e4c4
|
@ -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)
|
||||
|
|
|
@ -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"))
|
||||
|
@ -241,3 +245,40 @@ 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, 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
|
||||
BOMB: str
|
||||
BOW: str
|
||||
CHEST: str
|
||||
CHESTPLATE: str
|
||||
EAGLE: str
|
||||
EMPTY: str
|
||||
FIRE_BALL_STAFF: str
|
||||
FLOOR: str
|
||||
HAZELNUT: str
|
||||
HEART: str
|
||||
|
@ -34,6 +37,9 @@ class TexturePack:
|
|||
RABBIT: str
|
||||
RING_OF_CRITICAL_DAMAGE: str
|
||||
RING_OF_MORE_EXPERIENCE: str
|
||||
RULER: str
|
||||
SCROLL_OF_DAMAGE: str
|
||||
SCROLL_OF_WEAKENING: str
|
||||
SHIELD: str
|
||||
SUNFLOWER: str
|
||||
SWORD: str
|
||||
|
@ -73,10 +79,13 @@ TexturePack.ASCII_PACK = TexturePack(
|
|||
|
||||
BODY_SNATCH_POTION='S',
|
||||
BOMB='ç',
|
||||
BOW=')',
|
||||
CHEST='□',
|
||||
CHESTPLATE='(',
|
||||
EAGLE='µ',
|
||||
EMPTY=' ',
|
||||
EXPLOSION='%',
|
||||
FIRE_BALL_STAFF=':',
|
||||
FLOOR='.',
|
||||
LADDER='H',
|
||||
HAZELNUT='¤',
|
||||
|
@ -89,6 +98,7 @@ TexturePack.ASCII_PACK = TexturePack(
|
|||
RABBIT='Y',
|
||||
RING_OF_CRITICAL_DAMAGE='o',
|
||||
RING_OF_MORE_EXPERIENCE='o',
|
||||
RULER='\\',
|
||||
SHIELD='D',
|
||||
SUNFLOWER='I',
|
||||
SWORD='\u2020',
|
||||
|
@ -96,6 +106,8 @@ TexturePack.ASCII_PACK = TexturePack(
|
|||
TIGER='n',
|
||||
TRUMPET='/',
|
||||
WALL='#',
|
||||
SCROLL_OF_DAMAGE=']',
|
||||
SCROLL_OF_WEAKENING=']',
|
||||
)
|
||||
|
||||
TexturePack.SQUIRREL_PACK = TexturePack(
|
||||
|
@ -109,10 +121,13 @@ TexturePack.SQUIRREL_PACK = TexturePack(
|
|||
|
||||
BODY_SNATCH_POTION='🔀',
|
||||
BOMB='💣',
|
||||
BOW='🏹',
|
||||
CHEST='🧰',
|
||||
CHESTPLATE='🦺',
|
||||
EAGLE='🦅',
|
||||
EMPTY=' ',
|
||||
EXPLOSION='💥',
|
||||
FIRE_BALL_STAFF='🪄',
|
||||
FLOOR='██',
|
||||
LADDER=('🪜', curses.COLOR_WHITE, (1000, 1000, 1000),
|
||||
curses.COLOR_WHITE, (1000, 1000, 1000)),
|
||||
|
@ -126,6 +141,7 @@ TexturePack.SQUIRREL_PACK = TexturePack(
|
|||
RABBIT='🐇',
|
||||
RING_OF_CRITICAL_DAMAGE='💍',
|
||||
RING_OF_MORE_EXPERIENCE='💍',
|
||||
RULER='📏',
|
||||
SHIELD='🛡️ ',
|
||||
SUNFLOWER='🌻',
|
||||
SWORD='🗡️ ',
|
||||
|
@ -133,4 +149,6 @@ TexturePack.SQUIRREL_PACK = TexturePack(
|
|||
TIGER='🐅',
|
||||
TRUMPET='🎺',
|
||||
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 .player import Player
|
||||
from .monsters import Monster
|
||||
|
@ -17,8 +18,8 @@ class Merchant(InventoryHolder, FriendlyEntity):
|
|||
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)
|
||||
hazel: int = 75, maxhealth: int = 8, *args, **kwargs):
|
||||
super().__init__(name=name, maxhealth=maxhealth, *args, **kwargs)
|
||||
self.inventory = self.translate_inventory(inventory or [])
|
||||
self.hazel = hazel
|
||||
if not self.inventory:
|
||||
|
@ -39,6 +40,40 @@ 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):
|
||||
"""
|
||||
A friendly sunflower.
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from random import choice, randint
|
||||
from typing import Optional
|
||||
from typing import Optional, Any
|
||||
|
||||
from ..interfaces import Entity, FightingEntity, Map, InventoryHolder
|
||||
from ..translations import gettext as _
|
||||
|
@ -47,6 +47,11 @@ class Item(Entity):
|
|||
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:
|
||||
"""
|
||||
Indicates what should be done when the item is equipped.
|
||||
|
@ -86,16 +91,22 @@ class Item(Entity):
|
|||
"""
|
||||
Returns the list of all item classes.
|
||||
"""
|
||||
return [BodySnatchPotion, Chestplate, Bomb, Heart, Helmet, Monocle,
|
||||
Shield, Sword, RingCritical, RingXP]
|
||||
return [BodySnatchPotion, Bomb, Bow, Chestplate, FireBallStaff,
|
||||
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.
|
||||
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)
|
||||
|
@ -266,6 +277,15 @@ class Sword(Weapon):
|
|||
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 of items that increase the player's constitution.
|
||||
|
@ -455,6 +475,165 @@ class RingXP(Ring):
|
|||
*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):
|
||||
def __init__(self, name: str = "monocle", price: int = 10,
|
||||
*args, **kwargs):
|
||||
|
|
|
@ -31,6 +31,7 @@ class Monster(FightingEntity):
|
|||
By default, a monster will move randomly where it is possible
|
||||
If the player is closeby, the monster runs to the player.
|
||||
"""
|
||||
super().act(m)
|
||||
target = None
|
||||
for entity in m.entities:
|
||||
if self.distance_squared(entity) <= 25 and \
|
||||
|
|
|
@ -28,6 +28,7 @@ class GameMode(Enum):
|
|||
SETTINGS = auto()
|
||||
INVENTORY = auto()
|
||||
STORE = auto()
|
||||
CHEST = auto()
|
||||
CREDITS = auto()
|
||||
|
||||
|
||||
|
@ -48,6 +49,7 @@ class KeyValues(Enum):
|
|||
CHAT = auto()
|
||||
WAIT = auto()
|
||||
LADDER = auto()
|
||||
LAUNCH = auto()
|
||||
|
||||
@staticmethod
|
||||
def translate_key(key: str, settings: Settings) -> Optional["KeyValues"]:
|
||||
|
@ -84,4 +86,6 @@ class KeyValues(Enum):
|
|||
return KeyValues.WAIT
|
||||
elif key == settings.KEY_LADDER:
|
||||
return KeyValues.LADDER
|
||||
elif key == settings.KEY_LAUNCH:
|
||||
return KeyValues.LAUNCH
|
||||
return None
|
||||
|
|
|
@ -35,7 +35,9 @@ class Game:
|
|||
"""
|
||||
self.state = GameMode.MAINMENU
|
||||
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()
|
||||
|
@ -45,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
|
||||
|
||||
|
@ -121,6 +124,9 @@ class Game:
|
|||
if self.waiting_for_friendly_key:
|
||||
# The player requested to talk with a friendly entity
|
||||
self.handle_friendly_entity_chat(key)
|
||||
elif self.waiting_for_launch_key:
|
||||
# The player requested to launch
|
||||
self.handle_launch(key)
|
||||
else:
|
||||
self.handle_key_pressed_play(key)
|
||||
elif self.state == GameMode.INVENTORY:
|
||||
|
@ -131,6 +137,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)
|
||||
|
@ -159,6 +167,9 @@ class Game:
|
|||
self.player.equipped_main.use()
|
||||
if self.player.equipped_secondary:
|
||||
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:
|
||||
self.state = GameMode.MAINMENU
|
||||
elif key == KeyValues.CHAT:
|
||||
|
@ -250,6 +261,35 @@ 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:
|
||||
"""
|
||||
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:
|
||||
"""
|
||||
|
@ -306,6 +346,36 @@ class Game:
|
|||
# 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
|
||||
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:
|
||||
"""
|
||||
In the main menu, we can navigate through different options.
|
||||
|
|
|
@ -606,6 +606,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:
|
||||
"""
|
||||
|
@ -622,9 +629,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:
|
||||
|
@ -632,8 +639,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:
|
||||
|
@ -644,30 +650,37 @@ 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, Monocle, Sword, Shield, Chestplate, Helmet, \
|
||||
RingCritical, RingXP
|
||||
Heart, Sword, Shield, Chestplate, Helmet, RingCritical, RingXP, \
|
||||
ScrollofDamage, ScrollofWeakening, Ruler, Bow, FireBallStaff, \
|
||||
Monocle
|
||||
return {
|
||||
"Bomb": Bomb,
|
||||
"Chestplate": Chestplate,
|
||||
"Heart": Heart,
|
||||
"BodySnatchPotion": BodySnatchPotion,
|
||||
"Bomb": Bomb,
|
||||
"Bow": Bow,
|
||||
"Chest": Chest,
|
||||
"Chestplate": Chestplate,
|
||||
"Eagle": GiantSeaEagle,
|
||||
"FireBallStaff": FireBallStaff,
|
||||
"Heart": Heart,
|
||||
"Hedgehog": Hedgehog,
|
||||
"Helmet": Helmet,
|
||||
"Player": Player,
|
||||
"Merchant": Merchant,
|
||||
"Monocle": Monocle,
|
||||
"Sunflower": Sunflower,
|
||||
"Sword": Sword,
|
||||
"Trumpet": Trumpet,
|
||||
"Shield": Shield,
|
||||
"TeddyBear": TeddyBear,
|
||||
"Tiger": Tiger,
|
||||
"Player": Player,
|
||||
"Rabbit": Rabbit,
|
||||
"RingCritical": RingCritical,
|
||||
"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:
|
||||
|
@ -710,6 +723,7 @@ class FightingEntity(Entity):
|
|||
self.constitution = constitution
|
||||
self.level = level
|
||||
self.critical = critical
|
||||
self.effects = [] # effects = temporary buff or weakening of the stats.
|
||||
|
||||
@property
|
||||
def dead(self) -> bool:
|
||||
|
@ -718,13 +732,27 @@ class FightingEntity(Entity):
|
|||
"""
|
||||
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:
|
||||
"""
|
||||
The entity deals damage to the opponent
|
||||
based on their respective stats.
|
||||
"""
|
||||
diceroll = randint(1, 100)
|
||||
damage = self.strength
|
||||
damage = max(0, self.strength)
|
||||
string = " "
|
||||
if diceroll <= self.critical: # It is a critical hit
|
||||
damage *= 4
|
||||
|
|
|
@ -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 []
|
||||
|
|
|
@ -35,6 +35,7 @@ class Settings:
|
|||
self.KEY_CHAT = ['t', 'Key used to talk to a friendly entity']
|
||||
self.KEY_WAIT = ['w', 'Key used to wait']
|
||||
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.LOCALE = [locale.getlocale()[0][:2], 'Language']
|
||||
|
||||
|
|
|
@ -8,13 +8,14 @@ import unittest
|
|||
from ..bootstrap import Bootstrap
|
||||
from ..display.display import Display
|
||||
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, \
|
||||
Chestplate, RingCritical, Monocle
|
||||
from ..entities.monsters import GiantSeaEagle
|
||||
Chestplate, RingCritical, Bow, FireBallStaff, ScrollofDamage,\
|
||||
ScrollofWeakening, Monocle
|
||||
from ..entities.monsters import Rabbit, GiantSeaEagle
|
||||
from ..entities.player import Player
|
||||
from ..enums import DisplayActions
|
||||
from ..game import Game, KeyValues, GameMode
|
||||
from ..enums import DisplayActions, KeyValues, GameMode
|
||||
from ..game import Game
|
||||
from ..interfaces import Map
|
||||
from ..menus import MainMenuValues
|
||||
from ..resources import ResourceManager
|
||||
|
@ -349,7 +350,7 @@ class TestGame(unittest.TestCase):
|
|||
self.assertEqual(self.game.settings.KEY_LEFT_PRIMARY, 'a')
|
||||
|
||||
# Navigate to "texture pack"
|
||||
for ignored in range(12):
|
||||
for ignored in range(13):
|
||||
self.game.handle_key_pressed(KeyValues.DOWN)
|
||||
|
||||
# Change texture pack
|
||||
|
@ -767,3 +768,157 @@ class TestGame(unittest.TestCase):
|
|||
self.game.handle_key_pressed(KeyValues.ENTER)
|
||||
|
||||
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)
|
||||
|
|
Loading…
Reference in New Issue