Merge branch 'master' into map_generation

# Conflicts:
#	squirrelbattle/interfaces.py
This commit is contained in:
Yohann D'ANELLO 2020-12-11 19:02:45 +01:00
commit e91deccb6f
19 changed files with 973 additions and 129 deletions

View File

@ -5,6 +5,7 @@ import curses
from typing import Any, Optional, Union from typing import Any, Optional, Union
from squirrelbattle.display.texturepack import TexturePack from squirrelbattle.display.texturepack import TexturePack
from squirrelbattle.game import Game
from squirrelbattle.tests.screen import FakePad from squirrelbattle.tests.screen import FakePad
@ -86,6 +87,13 @@ class Display:
def display(self) -> None: def display(self) -> None:
raise NotImplementedError raise NotImplementedError
def handle_click(self, y: int, x: int, game: Game) -> None:
"""
A mouse click was performed on the coordinates (y, x) of the pad.
Maybe it can do something.
"""
pass
@property @property
def rows(self) -> int: def rows(self) -> int:
return curses.LINES if self.screen else 42 return curses.LINES if self.screen else 42

View File

@ -2,15 +2,16 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
import curses import curses
from squirrelbattle.display.display import VerticalSplit, HorizontalSplit from squirrelbattle.display.display import VerticalSplit, HorizontalSplit, \
Display
from squirrelbattle.display.mapdisplay import MapDisplay 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, \
InventoryDisplay, SettingsMenuDisplay PlayerInventoryDisplay, StoreInventoryDisplay, SettingsMenuDisplay
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 from typing import Any, List
from squirrelbattle.game import Game, GameMode from squirrelbattle.game import Game, GameMode
from squirrelbattle.enums import DisplayActions from squirrelbattle.enums import DisplayActions
@ -24,7 +25,8 @@ class DisplayManager:
self.mapdisplay = MapDisplay(screen, pack) self.mapdisplay = MapDisplay(screen, pack)
self.statsdisplay = StatsDisplay(screen, pack) self.statsdisplay = StatsDisplay(screen, pack)
self.logsdisplay = LogsDisplay(screen, pack) self.logsdisplay = LogsDisplay(screen, pack)
self.inventorydisplay = InventoryDisplay(screen, pack) self.playerinventorydisplay = PlayerInventoryDisplay(screen, pack)
self.storeinventorydisplay = StoreInventoryDisplay(screen, pack)
self.mainmenudisplay = MainMenuDisplay(self.game.main_menu, self.mainmenudisplay = MainMenuDisplay(self.game.main_menu,
screen, pack) screen, pack)
self.settingsmenudisplay = SettingsMenuDisplay(screen, pack) self.settingsmenudisplay = SettingsMenuDisplay(screen, pack)
@ -33,28 +35,50 @@ class DisplayManager:
self.vbar = VerticalSplit(screen, pack) self.vbar = VerticalSplit(screen, pack)
self.displays = [self.statsdisplay, self.mapdisplay, self.displays = [self.statsdisplay, self.mapdisplay,
self.mainmenudisplay, self.settingsmenudisplay, self.mainmenudisplay, self.settingsmenudisplay,
self.logsdisplay, self.messagedisplay] self.logsdisplay, self.messagedisplay,
self.playerinventorydisplay,
self.storeinventorydisplay]
self.update_game_components() self.update_game_components()
def handle_display_action(self, action: DisplayActions) -> None: def handle_display_action(self, action: DisplayActions, *params) -> None:
if action == DisplayActions.REFRESH: if action == DisplayActions.REFRESH:
self.refresh() self.refresh()
elif action == DisplayActions.UPDATE: elif action == DisplayActions.UPDATE:
self.update_game_components() self.update_game_components()
elif action == DisplayActions.MOUSE:
self.handle_mouse_click(*params)
def update_game_components(self) -> None: def update_game_components(self) -> None:
for d in self.displays: for d in self.displays:
d.pack = TexturePack.get_pack(self.game.settings.TEXTURE_PACK) d.pack = TexturePack.get_pack(self.game.settings.TEXTURE_PACK)
self.mapdisplay.update_map(self.game.map) self.mapdisplay.update_map(self.game.map)
self.statsdisplay.update_player(self.game.player) self.statsdisplay.update_player(self.game.player)
self.inventorydisplay.update_menu(self.game.inventory_menu) self.game.inventory_menu.update_player(self.game.player)
self.game.store_menu.update_merchant(self.game.player)
self.playerinventorydisplay.update_menu(self.game.inventory_menu)
self.storeinventorydisplay.update_menu(self.game.store_menu)
self.settingsmenudisplay.update_menu(self.game.settings_menu) self.settingsmenudisplay.update_menu(self.game.settings_menu)
self.logsdisplay.update_logs(self.game.logs) self.logsdisplay.update_logs(self.game.logs)
self.messagedisplay.update_message(self.game.message) self.messagedisplay.update_message(self.game.message)
def refresh(self) -> None: def handle_mouse_click(self, y: int, x: int) -> None:
displays = self.refresh()
display = None
for d in displays:
top_y, top_x, height, width = d.y, d.x, d.height, d.width
if top_y <= y < top_y + height and top_x <= x < top_x + width:
# The click coordinates correspond to the coordinates
# of that display
display = d
if display:
display.handle_click(y - display.y, x - display.x, self.game)
def refresh(self) -> List[Display]:
displays = []
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:
# 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
@ -67,15 +91,26 @@ class DisplayManager:
self.rows // 5 - 1, self.cols * 4 // 5) self.rows // 5 - 1, self.cols * 4 // 5)
self.hbar.refresh(self.rows * 4 // 5, 0, 1, self.cols * 4 // 5) self.hbar.refresh(self.rows * 4 // 5, 0, 1, self.cols * 4 // 5)
self.vbar.refresh(0, self.cols * 4 // 5, self.rows, 1) self.vbar.refresh(0, self.cols * 4 // 5, self.rows, 1)
displays += [self.mapdisplay, self.statsdisplay, self.logsdisplay,
self.hbar, self.vbar]
if self.game.state == GameMode.INVENTORY: if self.game.state == GameMode.INVENTORY:
self.inventorydisplay.refresh(self.rows // 10, self.playerinventorydisplay.refresh(
self.cols // 2, self.rows // 10, self.cols // 2,
8 * self.rows // 10, 8 * self.rows // 10, 2 * self.cols // 5)
2 * self.cols // 5) displays.append(self.playerinventorydisplay)
elif self.game.state == GameMode.STORE:
self.storeinventorydisplay.refresh(
self.rows // 10, self.cols // 2,
8 * self.rows // 10, 2 * self.cols // 5)
displays.append(self.storeinventorydisplay)
elif self.game.state == GameMode.MAINMENU: 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)
elif self.game.state == GameMode.SETTINGS: elif self.game.state == GameMode.SETTINGS:
self.settingsmenudisplay.refresh(0, 0, self.rows, self.cols) self.settingsmenudisplay.refresh(0, 0, self.rows, self.cols)
displays.append(self.settingsmenudisplay)
if self.game.message: if self.game.message:
height, width = 0, 0 height, width = 0, 0
@ -84,9 +119,12 @@ class DisplayManager:
width = max(width, len(line)) width = max(width, len(line))
y, x = (self.rows - height) // 2, (self.cols - width) // 2 y, x = (self.rows - height) // 2, (self.cols - width) // 2
self.messagedisplay.refresh(y, x, height, width) self.messagedisplay.refresh(y, x, height, width)
displays.append(self.messagedisplay)
self.resize_window() self.resize_window()
return displays
def resize_window(self) -> bool: def resize_window(self) -> bool:
""" """
If the window got resized, ensure that the screen size got updated. If the window got resized, ensure that the screen size got updated.

View File

@ -1,15 +1,21 @@
# Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse # Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
import curses import curses
from typing import List from typing import List
from squirrelbattle.menus import Menu, MainMenu from squirrelbattle.menus import Menu, MainMenu
from .display import Display, Box from .display import Display, Box
from ..enums import KeyValues
from ..game import Game
from ..resources import ResourceManager from ..resources import ResourceManager
from ..translations import gettext as _ from ..translations import gettext as _
class MenuDisplay(Display): class MenuDisplay(Display):
"""
A class to display the menu objects
"""
position: int position: int
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -41,6 +47,13 @@ class MenuDisplay(Display):
self.height - 2 + self.y, self.height - 2 + self.y,
self.width - 2 + self.x) self.width - 2 + self.x)
def handle_click(self, y: int, x: int, game: Game) -> None:
"""
We can select a menu item with the mouse.
"""
self.menu.position = max(0, min(len(self.menu.values) - 1, y - 1))
game.handle_key_pressed(KeyValues.ENTER)
@property @property
def truewidth(self) -> int: def truewidth(self) -> int:
return max([len(str(a)) for a in self.values]) return max([len(str(a)) for a in self.values])
@ -63,6 +76,9 @@ class MenuDisplay(Display):
class SettingsMenuDisplay(MenuDisplay): class SettingsMenuDisplay(MenuDisplay):
"""
A class to display specifically a settingsmenu object
"""
@property @property
def values(self) -> List[str]: def values(self) -> List[str]:
return [_(a[1][1]) + (" : " return [_(a[1][1]) + (" : "
@ -73,6 +89,9 @@ class SettingsMenuDisplay(MenuDisplay):
class MainMenuDisplay(Display): class MainMenuDisplay(Display):
"""
A class to display specifically a mainmenu object
"""
def __init__(self, menu: MainMenu, *args): def __init__(self, menu: MainMenu, *args):
super().__init__(*args) super().__init__(*args)
self.menu = menu self.menu = menu
@ -99,12 +118,21 @@ class MainMenuDisplay(Display):
menuy, menux, min(self.menudisplay.preferred_height, menuy, menux, min(self.menudisplay.preferred_height,
self.height - menuy), menuwidth) self.height - menuy), menuwidth)
def handle_click(self, y: int, x: int, game: Game) -> None:
menuwidth = min(self.menudisplay.preferred_width, self.width)
menuy, menux = len(self.title) + 8, self.width // 2 - menuwidth // 2 - 1
menuheight = min(self.menudisplay.preferred_height, self.height - menuy)
class InventoryDisplay(MenuDisplay): if menuy <= y < menuy + menuheight and menux <= x < menux + menuwidth:
def update_pad(self) -> None: self.menudisplay.handle_click(y - menuy, x - menux, game)
class PlayerInventoryDisplay(MenuDisplay):
message = _("== INVENTORY ==") message = _("== INVENTORY ==")
self.addstr(self.pad, 0, (self.width - len(message)) // 2, message,
curses.A_BOLD | curses.A_ITALIC) def update_pad(self) -> None:
self.addstr(self.pad, 0, (self.width - len(self.message)) // 2,
self.message, curses.A_BOLD | curses.A_ITALIC)
for i, item in enumerate(self.menu.values): for i, item in enumerate(self.menu.values):
rep = self.pack[item.name.upper()] rep = self.pack[item.name.upper()]
selection = f"[{rep}]" if i == self.menu.position else f" {rep} " selection = f"[{rep}]" if i == self.menu.position else f" {rep} "
@ -118,3 +146,39 @@ class InventoryDisplay(MenuDisplay):
@property @property
def trueheight(self) -> int: def trueheight(self) -> int:
return 2 + super().trueheight return 2 + super().trueheight
def handle_click(self, y: int, x: int, game: Game) -> None:
"""
We can select a menu item with the mouse.
"""
self.menu.position = max(0, min(len(self.menu.values) - 1, y - 3))
game.handle_key_pressed(KeyValues.ENTER)
class StoreInventoryDisplay(MenuDisplay):
message = _("== STALL ==")
def update_pad(self) -> None:
self.addstr(self.pad, 0, (self.width - len(self.message)) // 2,
self.message, curses.A_BOLD | curses.A_ITALIC)
for i, item in enumerate(self.menu.values):
rep = self.pack[item.name.upper()]
selection = f"[{rep}]" if i == self.menu.position else f" {rep} "
self.addstr(self.pad, 2 + i, 0, selection
+ " " + item.translated_name.capitalize()
+ ": " + str(item.price) + " Hazels")
@property
def truewidth(self) -> int:
return max(1, self.height if hasattr(self, "height") else 10)
@property
def trueheight(self) -> int:
return 2 + super().trueheight
def handle_click(self, y: int, x: int, game: Game) -> None:
"""
We can select a menu item with the mouse.
"""
self.menu.position = max(0, min(len(self.menu.values) - 1, y - 3))
game.handle_key_pressed(KeyValues.ENTER)

View File

@ -46,8 +46,11 @@ class StatsDisplay(Display):
printed_items.append(item) printed_items.append(item)
self.addstr(self.pad, 8, 0, inventory_str) self.addstr(self.pad, 8, 0, inventory_str)
self.addstr(self.pad, 9, 0, f"{self.pack.HAZELNUT} "
f"x{self.player.hazel}")
if self.player.dead: if self.player.dead:
self.addstr(self.pad, 10, 0, _("YOU ARE DEAD"), self.addstr(self.pad, 11, 0, _("YOU ARE DEAD"),
curses.A_BOLD | curses.A_BLINK | curses.A_STANDOUT curses.A_BOLD | curses.A_BLINK | curses.A_STANDOUT
| self.color_pair(3)) | self.color_pair(3))

View File

@ -14,10 +14,22 @@ class TexturePack:
tile_bg_color: int tile_bg_color: int
entity_fg_color: int entity_fg_color: int
entity_bg_color: int entity_bg_color: int
BODY_SNATCH_POTION: str
BOMB: str
HEART: str
HEDGEHOG: str
EMPTY: str EMPTY: str
WALL: str
FLOOR: str FLOOR: str
HAZELNUT: str
MERCHANT: str
PLAYER: str PLAYER: str
RABBIT: str
SUNFLOWER: str
SWORD: str
TEDDY_BEAR: str
TIGER: str
WALL: str
ASCII_PACK: "TexturePack" ASCII_PACK: "TexturePack"
SQUIRREL_PACK: "TexturePack" SQUIRREL_PACK: "TexturePack"
@ -46,17 +58,22 @@ TexturePack.ASCII_PACK = TexturePack(
tile_bg_color=curses.COLOR_BLACK, tile_bg_color=curses.COLOR_BLACK,
entity_fg_color=curses.COLOR_WHITE, entity_fg_color=curses.COLOR_WHITE,
entity_bg_color=curses.COLOR_BLACK, entity_bg_color=curses.COLOR_BLACK,
EMPTY=' ',
WALL='#',
FLOOR='.',
PLAYER='@',
HEDGEHOG='*',
HEART='',
BOMB='o',
RABBIT='Y',
TIGER='n',
TEDDY_BEAR='8',
BODY_SNATCH_POTION='S', BODY_SNATCH_POTION='S',
BOMB='o',
EMPTY=' ',
FLOOR='.',
HAZELNUT='¤',
HEART='',
HEDGEHOG='*',
MERCHANT='M',
PLAYER='@',
RABBIT='Y',
SUNFLOWER='I',
SWORD='\u2020',
TEDDY_BEAR='8',
TIGER='n',
WALL='#',
) )
TexturePack.SQUIRREL_PACK = TexturePack( TexturePack.SQUIRREL_PACK = TexturePack(
@ -66,15 +83,20 @@ TexturePack.SQUIRREL_PACK = TexturePack(
tile_bg_color=curses.COLOR_BLACK, tile_bg_color=curses.COLOR_BLACK,
entity_fg_color=curses.COLOR_WHITE, entity_fg_color=curses.COLOR_WHITE,
entity_bg_color=curses.COLOR_WHITE, entity_bg_color=curses.COLOR_WHITE,
EMPTY=' ',
WALL='🧱',
FLOOR='██',
PLAYER='🐿️ ',
HEDGEHOG='🦔',
HEART='💜',
BOMB='💣',
RABBIT='🐇',
TIGER='🐅',
TEDDY_BEAR='🧸',
BODY_SNATCH_POTION='🔀', BODY_SNATCH_POTION='🔀',
BOMB='💣',
EMPTY=' ',
FLOOR='██',
HAZELNUT='🌰',
HEART='💜',
HEDGEHOG='🦔',
PLAYER='🐿️ ',
MERCHANT='🦜',
RABBIT='🐇',
SUNFLOWER='🌻',
SWORD='🗡️',
TEDDY_BEAR='🧸',
TIGER='🐅',
WALL='🧱',
) )

View File

@ -0,0 +1,50 @@
from ..interfaces import FriendlyEntity, InventoryHolder
from ..translations import gettext as _
from .player import Player
from .items import Item
from random import choice
class Merchant(InventoryHolder, FriendlyEntity):
"""
The class for merchants in the dungeon
"""
def keys(self) -> list:
"""
Returns a friendly entitie's specific attributes
"""
return super().keys() + ["inventory", "hazel"]
def __init__(self, name: str = "merchant", inventory: list = None,
hazel: int = 75, *args, **kwargs):
super().__init__(name=name, *args, **kwargs)
self.inventory = self.translate_inventory(inventory or [])
self.hazel = hazel
if not self.inventory:
for i in range(5):
self.inventory.append(choice(Item.get_all_items())())
def talk_to(self, player: Player) -> str:
"""
This function is used to open the merchant's inventory in a menu,
and allow the player to buy/sell objects
"""
return _("I don't sell any squirrel")
def change_hazel_balance(self, hz: int) -> None:
"""
Change the number of hazel the merchant has by hz.
"""
self.hazel += hz
class Sunflower(FriendlyEntity):
"""
A friendly sunflower
"""
dialogue_option = [_("Flower power!!"), _("The sun is warm today")]
def __init__(self, maxhealth: int = 15,
*args, **kwargs) -> None:
super().__init__(name="sunflower", maxhealth=maxhealth, *args, **kwargs)

View File

@ -5,7 +5,7 @@ from random import choice, randint
from typing import Optional from typing import Optional
from .player import Player from .player import Player
from ..interfaces import Entity, FightingEntity, Map from ..interfaces import Entity, FightingEntity, Map, InventoryHolder
from ..translations import gettext as _ from ..translations import gettext as _
@ -14,13 +14,16 @@ class Item(Entity):
A class for items A class for items
""" """
held: bool held: bool
held_by: Optional[Player] held_by: Optional[InventoryHolder]
price: int
def __init__(self, held: bool = False, held_by: Optional[Player] = None, def __init__(self, held: bool = False,
*args, **kwargs): held_by: Optional[InventoryHolder] = None,
price: int = 2, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.held = held self.held = held
self.held_by = held_by self.held_by = held_by
self.price = price
def drop(self) -> None: def drop(self) -> None:
""" """
@ -28,7 +31,7 @@ class Item(Entity):
""" """
if self.held: if self.held:
self.held_by.inventory.remove(self) self.held_by.inventory.remove(self)
self.map.add_entity(self) self.held_by.map.add_entity(self)
self.move(self.held_by.y, self.held_by.x) self.move(self.held_by.y, self.held_by.x)
self.held = False self.held = False
self.held_by = None self.held_by = None
@ -43,14 +46,14 @@ class Item(Entity):
Indicates what should be done when the item is equipped. Indicates what should be done when the item is equipped.
""" """
def hold(self, player: "Player") -> None: def hold(self, player: InventoryHolder) -> None:
""" """
The item is taken from the floor and put into the inventory The item is taken from the floor and put into the inventory
""" """
self.held = True self.held = True
self.held_by = player self.held_by = player
self.map.remove_entity(self) self.held_by.map.remove_entity(self)
player.inventory.append(self) player.add_to_inventory(self)
def save_state(self) -> dict: def save_state(self) -> dict:
""" """
@ -60,6 +63,25 @@ class Item(Entity):
d["held"] = self.held d["held"] = self.held
return d return d
@staticmethod
def get_all_items() -> list:
return [BodySnatchPotion, Bomb, Heart, Sword]
def be_sold(self, buyer: InventoryHolder, seller: InventoryHolder) -> bool:
"""
Does all necessary actions when an object is to be sold.
Is overwritten by some classes that cannot exist in the player's
inventory
"""
if buyer.hazel >= self.price:
self.hold(buyer)
seller.remove_from_inventory(self)
buyer.change_hazel_balance(-self.price)
seller.change_hazel_balance(self.price)
return True
else:
return False
class Heart(Item): class Heart(Item):
""" """
@ -67,16 +89,17 @@ class Heart(Item):
""" """
healing: int healing: int
def __init__(self, name: str = "heart", healing: int = 5, *args, **kwargs): def __init__(self, name: str = "heart", healing: int = 5, price: int = 3,
super().__init__(name=name, *args, **kwargs) *args, **kwargs):
super().__init__(name=name, price=price, *args, **kwargs)
self.healing = healing self.healing = healing
def hold(self, player: "Player") -> None: def hold(self, entity: InventoryHolder) -> None:
""" """
When holding a heart, heal the player and don't put item in inventory. When holding a heart, heal the player and don't put item in inventory.
""" """
player.health = min(player.maxhealth, player.health + self.healing) entity.health = min(entity.maxhealth, entity.health + self.healing)
self.map.remove_entity(self) entity.map.remove_entity(self)
def save_state(self) -> dict: def save_state(self) -> dict:
""" """
@ -97,8 +120,8 @@ class Bomb(Item):
tick: int tick: int
def __init__(self, name: str = "bomb", damage: int = 5, def __init__(self, name: str = "bomb", damage: int = 5,
exploding: bool = False, *args, **kwargs): exploding: bool = False, price: int = 4, *args, **kwargs):
super().__init__(name=name, *args, **kwargs) super().__init__(name=name, price=price, *args, **kwargs)
self.damage = damage self.damage = damage
self.exploding = exploding self.exploding = exploding
self.tick = 4 self.tick = 4
@ -145,14 +168,43 @@ class Bomb(Item):
return d return d
class Weapon(Item):
"""
Non-throwable items that improve player damage
"""
damage: int
def __init__(self, damage: int = 3, *args, **kwargs):
super().__init__(*args, **kwargs)
self.damage = damage
def save_state(self) -> dict:
"""
Saves the state of the weapon into a dictionary
"""
d = super().save_state()
d["damage"] = self.damage
return d
class Sword(Weapon):
"""
A basic weapon
"""
def __init__(self, name: str = "sword", price: int = 20, *args, **kwargs):
super().__init__(name=name, price=price, *args, **kwargs)
self.name = name
class BodySnatchPotion(Item): class BodySnatchPotion(Item):
""" """
The body-snatch potion allows to exchange all characteristics with a random The body-snatch potion allows to exchange all characteristics with a random
other entity. other entity.
""" """
def __init__(self, name: str = "body_snatch_potion", *args, **kwargs): def __init__(self, name: str = "body_snatch_potion", price: int = 14,
super().__init__(name=name, *args, **kwargs) *args, **kwargs):
super().__init__(name=name, price=price, *args, **kwargs)
def use(self) -> None: def use(self) -> None:
""" """

View File

@ -6,23 +6,22 @@ from queue import PriorityQueue
from random import randint from random import randint
from typing import Dict, Tuple from typing import Dict, Tuple
from ..interfaces import FightingEntity from ..interfaces import FightingEntity, InventoryHolder
class Player(FightingEntity): class Player(InventoryHolder, FightingEntity):
""" """
The class of the player The class of the player
""" """
current_xp: int = 0 current_xp: int = 0
max_xp: int = 10 max_xp: int = 10
inventory: list
paths: Dict[Tuple[int, int], Tuple[int, int]] paths: Dict[Tuple[int, int], Tuple[int, int]]
def __init__(self, name: str = "player", maxhealth: int = 20, def __init__(self, name: str = "player", maxhealth: int = 20,
strength: int = 5, intelligence: int = 1, charisma: int = 1, strength: int = 5, intelligence: int = 1, charisma: int = 1,
dexterity: int = 1, constitution: int = 1, level: int = 1, dexterity: int = 1, constitution: int = 1, level: int = 1,
current_xp: int = 0, max_xp: int = 10, inventory: list = None, current_xp: int = 0, max_xp: int = 10, inventory: list = None,
*args, **kwargs) \ hazel: int = 42, *args, **kwargs) \
-> None: -> None:
super().__init__(name=name, maxhealth=maxhealth, strength=strength, super().__init__(name=name, maxhealth=maxhealth, strength=strength,
intelligence=intelligence, charisma=charisma, intelligence=intelligence, charisma=charisma,
@ -30,13 +29,9 @@ class Player(FightingEntity):
level=level, *args, **kwargs) level=level, *args, **kwargs)
self.current_xp = current_xp self.current_xp = current_xp
self.max_xp = max_xp self.max_xp = max_xp
self.inventory = inventory if inventory else list() self.inventory = self.translate_inventory(inventory or [])
for i in range(len(self.inventory)):
if isinstance(self.inventory[i], dict):
entity_classes = self.get_all_entity_classes_in_a_dict()
item_class = entity_classes[self.inventory[i]["type"]]
self.inventory[i] = item_class(**self.inventory[i])
self.paths = dict() self.paths = dict()
self.hazel = hazel
def move(self, y: int, x: int) -> None: def move(self, y: int, x: int) -> None:
""" """
@ -149,5 +144,4 @@ class Player(FightingEntity):
d = super().save_state() d = super().save_state()
d["current_xp"] = self.current_xp d["current_xp"] = self.current_xp
d["max_xp"] = self.max_xp d["max_xp"] = self.max_xp
d["inventory"] = [item.save_state() for item in self.inventory]
return d return d

View File

@ -16,6 +16,7 @@ class DisplayActions(Enum):
""" """
REFRESH = auto() REFRESH = auto()
UPDATE = auto() UPDATE = auto()
MOUSE = auto()
class GameMode(Enum): class GameMode(Enum):
@ -26,12 +27,14 @@ class GameMode(Enum):
PLAY = auto() PLAY = auto()
SETTINGS = auto() SETTINGS = auto()
INVENTORY = auto() INVENTORY = auto()
STORE = auto()
class KeyValues(Enum): class KeyValues(Enum):
""" """
Key values options used in the game Key values options used in the game
""" """
MOUSE = auto()
UP = auto() UP = auto()
DOWN = auto() DOWN = auto()
LEFT = auto() LEFT = auto()
@ -42,6 +45,7 @@ class KeyValues(Enum):
EQUIP = auto() EQUIP = auto()
DROP = auto() DROP = auto()
SPACE = auto() SPACE = auto()
CHAT = auto()
@staticmethod @staticmethod
def translate_key(key: str, settings: Settings) -> Optional["KeyValues"]: def translate_key(key: str, settings: Settings) -> Optional["KeyValues"]:
@ -72,4 +76,6 @@ class KeyValues(Enum):
return KeyValues.DROP return KeyValues.DROP
elif key == ' ': elif key == ' ':
return KeyValues.SPACE return KeyValues.SPACE
elif key == settings.KEY_CHAT:
return KeyValues.CHAT
return None return None

View File

@ -4,6 +4,7 @@
from json import JSONDecodeError from json import JSONDecodeError
from random import randint from random import randint
from typing import Any, Optional from typing import Any, Optional
import curses
import json import json
import os import os
import sys import sys
@ -16,7 +17,6 @@ from .resources import ResourceManager
from .settings import Settings from .settings import Settings
from . import menus from . import menus
from .translations import gettext as _, Translator from .translations import gettext as _, Translator
from typing import Callable
class Game: class Game:
@ -25,14 +25,16 @@ class Game:
""" """
map: Map map: Map
player: Player player: Player
screen: Any
# display_actions is a display interface set by the bootstrapper # display_actions is a display interface set by the bootstrapper
display_actions: Callable[[DisplayActions], None] display_actions: callable
def __init__(self) -> None: def __init__(self) -> None:
""" """
Init the game. Init the game.
""" """
self.state = GameMode.MAINMENU self.state = GameMode.MAINMENU
self.waiting_for_friendly_key = False
self.settings = Settings() self.settings = Settings()
self.settings.load_settings() self.settings.load_settings()
self.settings.write_settings() self.settings.write_settings()
@ -41,6 +43,7 @@ class Game:
self.settings_menu = menus.SettingsMenu() self.settings_menu = menus.SettingsMenu()
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.logs = Logs() self.logs = Logs()
self.message = None self.message = None
@ -68,6 +71,10 @@ class Game:
screen.refresh() screen.refresh()
self.display_actions(DisplayActions.REFRESH) self.display_actions(DisplayActions.REFRESH)
key = screen.getkey() key = screen.getkey()
if key == "KEY_MOUSE":
_ignored1, x, y, _ignored2, _ignored3 = curses.getmouse()
self.display_actions(DisplayActions.MOUSE, y, x)
else:
self.handle_key_pressed( self.handle_key_pressed(
KeyValues.translate_key(key, self.settings), key) KeyValues.translate_key(key, self.settings), key)
@ -83,6 +90,10 @@ class Game:
return return
if self.state == GameMode.PLAY: if self.state == GameMode.PLAY:
if self.waiting_for_friendly_key:
# The player requested to talk with a friendly entity
self.handle_friendly_entity_chat(key)
else:
self.handle_key_pressed_play(key) self.handle_key_pressed_play(key)
elif self.state == GameMode.INVENTORY: elif self.state == GameMode.INVENTORY:
self.handle_key_pressed_inventory(key) self.handle_key_pressed_inventory(key)
@ -90,6 +101,8 @@ class Game:
self.handle_key_pressed_main_menu(key) self.handle_key_pressed_main_menu(key)
elif self.state == GameMode.SETTINGS: elif self.state == GameMode.SETTINGS:
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:
self.handle_key_pressed_store(key)
self.display_actions(DisplayActions.REFRESH) self.display_actions(DisplayActions.REFRESH)
def handle_key_pressed_play(self, key: KeyValues) -> None: def handle_key_pressed_play(self, key: KeyValues) -> None:
@ -112,6 +125,42 @@ class Game:
self.state = GameMode.INVENTORY self.state = GameMode.INVENTORY
elif key == KeyValues.SPACE: elif key == KeyValues.SPACE:
self.state = GameMode.MAINMENU self.state = GameMode.MAINMENU
elif key == KeyValues.CHAT:
# Wait for the direction of the friendly entity
self.waiting_for_friendly_key = True
def handle_friendly_entity_chat(self, key: KeyValues) -> None:
"""
If the player is talking to a friendly entity, we get the direction
where the entity is, then we interact with it.
"""
if not self.waiting_for_friendly_key:
return
self.waiting_for_friendly_key = False
if key == KeyValues.UP:
xp = self.player.x
yp = self.player.y - 1
elif key == KeyValues.DOWN:
xp = self.player.x
yp = self.player.y + 1
elif key == KeyValues.LEFT:
xp = self.player.x - 1
yp = self.player.y
elif key == KeyValues.RIGHT:
xp = self.player.x + 1
yp = self.player.y
else:
return
if self.map.entity_is_present(yp, xp):
for entity in self.map.entities:
if entity.is_friendly() and entity.x == xp and \
entity.y == yp:
msg = entity.talk_to(self.player)
self.logs.add_message(msg)
if entity.is_merchant():
self.state = GameMode.STORE
self.store_menu.update_merchant(entity)
def handle_key_pressed_inventory(self, key: KeyValues) -> None: def handle_key_pressed_inventory(self, key: KeyValues) -> None:
""" """
@ -136,6 +185,27 @@ class Game:
len(self.inventory_menu.values) len(self.inventory_menu.values)
- 1) - 1)
def handle_key_pressed_store(self, key: KeyValues) -> None:
"""
In a store menu, we can buy items or close the menu.
"""
if key == KeyValues.SPACE:
self.state = GameMode.PLAY
elif key == KeyValues.UP:
self.store_menu.go_up()
elif key == KeyValues.DOWN:
self.store_menu.go_down()
if self.store_menu.values and not self.player.dead:
if key == KeyValues.ENTER:
item = self.store_menu.validate()
flag = item.be_sold(self.player, self.store_menu.merchant)
if not flag:
self.message = _("You do not have enough money")
self.display_actions(DisplayActions.UPDATE)
# Ensure that the cursor has a good position
self.store_menu.position = min(self.store_menu.position,
len(self.store_menu.values) - 1)
def handle_key_pressed_main_menu(self, key: KeyValues) -> None: def handle_key_pressed_main_menu(self, key: KeyValues) -> None:
""" """
In the main menu, we can navigate through options. In the main menu, we can navigate through options.

View File

@ -4,7 +4,7 @@
from enum import Enum, auto from enum import Enum, auto
from math import sqrt from math import sqrt
from random import choice, randint from random import choice, randint
from typing import List, Optional from typing import List, Optional, Any
from itertools import product from itertools import product
from .display.texturepack import TexturePack from .display.texturepack import TexturePack
@ -69,6 +69,7 @@ class Map:
""" """
Unregister an entity from the map. Unregister an entity from the map.
""" """
if entity in self.entities:
self.entities.remove(entity) self.entities.remove(entity)
def find_entities(self, entity_class: type) -> list: def find_entities(self, entity_class: type) -> list:
@ -77,12 +78,21 @@ class Map:
def is_free(self, y: int, x: int) -> bool: def is_free(self, y: int, x: int) -> bool:
""" """
Indicates that the case at the coordinates (y, x) is empty. Indicates that the tile at the coordinates (y, x) is empty.
""" """
return 0 <= y < self.height and 0 <= x < self.width and \ return 0 <= y < self.height and 0 <= x < self.width and \
self.tiles[y][x].can_walk() and \ self.tiles[y][x].can_walk() and \
not any(entity.x == x and entity.y == y for entity in self.entities) not any(entity.x == x and entity.y == y for entity in self.entities)
def entity_is_present(self, y: int, x: int) -> bool:
"""
Indicates that the tile at the coordinates (y, x) contains a killable
entity
"""
return 0 <= y < self.height and 0 <= x < self.width and \
any(entity.x == x and entity.y == y and entity.is_friendly()
for entity in self.entities)
@staticmethod @staticmethod
def load(filename: str) -> "Map": def load(filename: str) -> "Map":
""" """
@ -128,7 +138,7 @@ class Map:
def spawn_random_entities(self, count: int) -> None: def spawn_random_entities(self, count: int) -> None:
""" """
Put randomly {count} hedgehogs on the map, where it is available. Put randomly {count} entities on the map, where it is available.
""" """
for ignored in range(count): for ignored in range(count):
y, x = 0, 0 y, x = 0, 0
@ -328,20 +338,34 @@ class Entity:
from squirrelbattle.entities.items import Item from squirrelbattle.entities.items import Item
return isinstance(self, Item) return isinstance(self, Item)
def is_friendly(self) -> bool:
"""
Is this entity a friendly entity?
"""
return isinstance(self, FriendlyEntity)
def is_merchant(self) -> bool:
"""
Is this entity a merchant?
"""
from squirrelbattle.entities.friendly import Merchant
return isinstance(self, Merchant)
@property @property
def translated_name(self) -> str: def translated_name(self) -> str:
return _(self.name.replace("_", " ")) return _(self.name.replace("_", " "))
@staticmethod @staticmethod
def get_all_entity_classes(): def get_all_entity_classes() -> list:
""" """
Returns all entities subclasses Returns all entities subclasses
""" """
from squirrelbattle.entities.items import BodySnatchPotion, Bomb, Heart from squirrelbattle.entities.items import BodySnatchPotion, Bomb, Heart
from squirrelbattle.entities.monsters import Tiger, Hedgehog, \ from squirrelbattle.entities.monsters import Tiger, Hedgehog, \
Rabbit, TeddyBear Rabbit, TeddyBear
return [BodySnatchPotion, Bomb, Heart, Hedgehog, from squirrelbattle.entities.friendly import Merchant, Sunflower
Rabbit, TeddyBear, Tiger] return [BodySnatchPotion, Bomb, Heart, Hedgehog, Rabbit, TeddyBear,
Sunflower, Tiger, Merchant]
@staticmethod @staticmethod
def get_all_entity_classes_in_a_dict() -> dict: def get_all_entity_classes_in_a_dict() -> dict:
@ -351,7 +375,9 @@ class Entity:
from squirrelbattle.entities.player import Player from squirrelbattle.entities.player import Player
from squirrelbattle.entities.monsters import Tiger, Hedgehog, Rabbit, \ from squirrelbattle.entities.monsters import Tiger, Hedgehog, Rabbit, \
TeddyBear TeddyBear
from squirrelbattle.entities.items import BodySnatchPotion, Bomb, Heart from squirrelbattle.entities.friendly import Merchant, Sunflower
from squirrelbattle.entities.items import BodySnatchPotion, Bomb, \
Heart, Sword
return { return {
"Tiger": Tiger, "Tiger": Tiger,
"Bomb": Bomb, "Bomb": Bomb,
@ -361,6 +387,9 @@ class Entity:
"Rabbit": Rabbit, "Rabbit": Rabbit,
"TeddyBear": TeddyBear, "TeddyBear": TeddyBear,
"Player": Player, "Player": Player,
"Merchant": Merchant,
"Sunflower": Sunflower,
"Sword": Sword,
} }
def save_state(self) -> dict: def save_state(self) -> dict:
@ -436,7 +465,7 @@ class FightingEntity(Entity):
def keys(self) -> list: def keys(self) -> list:
""" """
Returns a fighting entities specific attributes Returns a fighting entity's specific attributes
""" """
return ["name", "maxhealth", "health", "level", "strength", return ["name", "maxhealth", "health", "level", "strength",
"intelligence", "charisma", "dexterity", "constitution"] "intelligence", "charisma", "dexterity", "constitution"]
@ -449,3 +478,74 @@ class FightingEntity(Entity):
for name in self.keys(): for name in self.keys():
d[name] = getattr(self, name) d[name] = getattr(self, name)
return d return d
class FriendlyEntity(FightingEntity):
"""
Friendly entities are living entities which do not attack the player
"""
dialogue_option: list
def talk_to(self, player: Any) -> str:
a = randint(0, len(self.dialogue_option) - 1)
return "The " + self.translated_name \
+ " said : " + self.dialogue_option[a]
def keys(self) -> list:
"""
Returns a friendly entity's specific attributes
"""
return ["maxhealth", "health"]
class InventoryHolder(Entity):
hazel: int # Currency of the game
inventory: list
def translate_inventory(self, inventory: list) -> list:
"""
Translate the JSON-state of the inventory into a list of the items in
the inventory.
"""
for i in range(len(inventory)):
if isinstance(inventory[i], dict):
inventory[i] = self.dict_to_inventory(inventory[i])
return inventory
def dict_to_inventory(self, item_dict: dict) -> Entity:
"""
Translate a dict object that contains the state of an item
into an item object.
"""
entity_classes = self.get_all_entity_classes_in_a_dict()
item_class = entity_classes[item_dict["type"]]
return item_class(**item_dict)
def save_state(self) -> dict:
"""
We save the inventory of the merchant formatted as JSON
"""
d = super().save_state()
d["hazel"] = self.hazel
d["inventory"] = [item.save_state() for item in self.inventory]
return d
def add_to_inventory(self, obj: Any) -> None:
"""
Adds an object to inventory
"""
self.inventory.append(obj)
def remove_from_inventory(self, obj: Any) -> None:
"""
Removes an object from the inventory
"""
self.inventory.remove(obj)
def change_hazel_balance(self, hz: int) -> None:
"""
Change the number of hazel the entity has by hz. hz is negative
when the player loses money and positive when he gains money
"""
self.hazel += hz

View File

@ -6,7 +6,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: squirrelbattle 3.14.1\n" "Project-Id-Version: squirrelbattle 3.14.1\n"
"Report-Msgid-Bugs-To: squirrel-battle@crans.org\n" "Report-Msgid-Bugs-To: squirrel-battle@crans.org\n"
"POT-Creation-Date: 2020-12-05 14:46+0100\n" "POT-Creation-Date: 2020-12-11 18:06+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -15,31 +15,52 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
#: squirrelbattle/display/menudisplay.py:105 #: squirrelbattle/display/menudisplay.py:113
msgid "== INVENTORY ==" msgid "== INVENTORY =="
msgstr "== BESTAND ==" msgstr "== BESTAND =="
#: squirrelbattle/display/menudisplay.py:134
msgid "== STALL =="
msgstr "== STAND =="
#: squirrelbattle/display/statsdisplay.py:34 #: squirrelbattle/display/statsdisplay.py:34
msgid "Inventory:" msgid "Inventory:"
msgstr "Bestand:" msgstr "Bestand:"
#: squirrelbattle/display/statsdisplay.py:50 #: squirrelbattle/display/statsdisplay.py:53
msgid "YOU ARE DEAD" msgid "YOU ARE DEAD"
msgstr "SIE WURDEN GESTORBEN" msgstr "SIE WURDEN GESTORBEN"
#. TODO
#: squirrelbattle/entities/friendly.py:33
msgid "I don't sell any squirrel"
msgstr "Ich verkaufe keinen Eichhörnchen."
#: squirrelbattle/entities/friendly.py:46
msgid "Flower power!!"
msgstr "Blumenmacht!!"
#: squirrelbattle/entities/friendly.py:46
msgid "The sun is warm today"
msgstr "Die Sonne ist warm heute"
#. The bomb is exploding. #. The bomb is exploding.
#. Each entity that is close to the bomb takes damages. #. Each entity that is close to the bomb takes damages.
#. The player earn XP if the entity was killed. #. The player earn XP if the entity was killed.
#: squirrelbattle/entities/items.py:128 #: squirrelbattle/entities/items.py:151
msgid "Bomb is exploding." msgid "Bomb is exploding."
msgstr "Die Bombe explodiert." msgstr "Die Bombe explodiert."
#: squirrelbattle/entities/items.py:172 #: squirrelbattle/entities/items.py:224
#, python-brace-format #, python-brace-format
msgid "{player} exchanged its body with {entity}." msgid "{player} exchanged its body with {entity}."
msgstr "{player} täuscht seinem Körper mit {entity} aus." msgstr "{player} täuscht seinem Körper mit {entity} aus."
#: squirrelbattle/game.py:177 #: squirrelbattle/game.py:199 squirrelbattle/tests/game_test.py:537
msgid "You do not have enough money"
msgstr ""
#: squirrelbattle/game.py:243
msgid "" msgid ""
"Some keys are missing in your save file.\n" "Some keys are missing in your save file.\n"
"Your save seems to be corrupt. It got deleted." "Your save seems to be corrupt. It got deleted."
@ -47,7 +68,7 @@ msgstr ""
"In Ihrer Speicherdatei fehlen einige Schlüssel.\n" "In Ihrer Speicherdatei fehlen einige Schlüssel.\n"
"Ihre Speicherung scheint korrupt zu sein. Es wird gelöscht." "Ihre Speicherung scheint korrupt zu sein. Es wird gelöscht."
#: squirrelbattle/game.py:185 #: squirrelbattle/game.py:251
msgid "" msgid ""
"No player was found on this map!\n" "No player was found on this map!\n"
"Maybe you died?" "Maybe you died?"
@ -55,7 +76,7 @@ msgstr ""
"Auf dieser Karte wurde kein Spieler gefunden!\n" "Auf dieser Karte wurde kein Spieler gefunden!\n"
"Vielleicht sind Sie gestorben?" "Vielleicht sind Sie gestorben?"
#: squirrelbattle/game.py:205 #: squirrelbattle/game.py:271
msgid "" msgid ""
"The JSON file is not correct.\n" "The JSON file is not correct.\n"
"Your save seems corrupted. It got deleted." "Your save seems corrupted. It got deleted."
@ -63,27 +84,27 @@ msgstr ""
"Die JSON-Datei ist nicht korrekt.\n" "Die JSON-Datei ist nicht korrekt.\n"
"Ihre Speicherung scheint korrumpiert. Sie wurde gelöscht." "Ihre Speicherung scheint korrumpiert. Sie wurde gelöscht."
#: squirrelbattle/interfaces.py:400 #: squirrelbattle/interfaces.py:429
#, python-brace-format #, python-brace-format
msgid "{name} hits {opponent}." msgid "{name} hits {opponent}."
msgstr "{name} schlägt {opponent}." msgstr "{name} schlägt {opponent}."
#: squirrelbattle/interfaces.py:412 #: squirrelbattle/interfaces.py:441
#, python-brace-format #, python-brace-format
msgid "{name} takes {amount} damage." msgid "{name} takes {amount} damage."
msgstr "{name} nimmt {amount} Schadenspunkte." msgstr "{name} nimmt {amount} Schadenspunkte."
#: squirrelbattle/interfaces.py:414 #: squirrelbattle/interfaces.py:443
#, python-brace-format #, python-brace-format
msgid "{name} dies." msgid "{name} dies."
msgstr "{name} stirbt." msgstr "{name} stirbt."
#: squirrelbattle/menus.py:72 #: squirrelbattle/menus.py:73
msgid "Back" msgid "Back"
msgstr "Zurück" msgstr "Zurück"
#: squirrelbattle/tests/game_test.py:300 squirrelbattle/tests/game_test.py:303 #: squirrelbattle/tests/game_test.py:314 squirrelbattle/tests/game_test.py:317
#: squirrelbattle/tests/game_test.py:306 #: squirrelbattle/tests/game_test.py:320
#: squirrelbattle/tests/translations_test.py:16 #: squirrelbattle/tests/translations_test.py:16
msgid "New game" msgid "New game"
msgstr "Neu Spiel" msgstr "Neu Spiel"
@ -161,41 +182,49 @@ msgid "Key used to drop an item in the inventory"
msgstr "Taste um eines Objekts im Bestand zu werfen" msgstr "Taste um eines Objekts im Bestand zu werfen"
#: squirrelbattle/tests/translations_test.py:53 #: squirrelbattle/tests/translations_test.py:53
msgid "Key used to talk to a friendly entity"
msgstr "Taste um mit einer friedlicher Entität zu sprechen"
#: squirrelbattle/tests/translations_test.py:55
msgid "Texture pack" msgid "Texture pack"
msgstr "Textur-Packung" msgstr "Textur-Packung"
#: squirrelbattle/tests/translations_test.py:54 #: squirrelbattle/tests/translations_test.py:56
msgid "Language" msgid "Language"
msgstr "Sprache" msgstr "Sprache"
#: squirrelbattle/tests/translations_test.py:57 #: squirrelbattle/tests/translations_test.py:59
msgid "player" msgid "player"
msgstr "Spieler" msgstr "Spieler"
#: squirrelbattle/tests/translations_test.py:59 #: squirrelbattle/tests/translations_test.py:61
msgid "tiger" msgid "tiger"
msgstr "Tiger" msgstr "Tiger"
#: squirrelbattle/tests/translations_test.py:60 #: squirrelbattle/tests/translations_test.py:62
msgid "hedgehog" msgid "hedgehog"
msgstr "Igel" msgstr "Igel"
#: squirrelbattle/tests/translations_test.py:61 #: squirrelbattle/tests/translations_test.py:63
msgid "rabbit" msgid "rabbit"
msgstr "Kanninchen" msgstr "Kanninchen"
#: squirrelbattle/tests/translations_test.py:62 #: squirrelbattle/tests/translations_test.py:64
msgid "teddy bear" msgid "teddy bear"
msgstr "Teddybär" msgstr "Teddybär"
#: squirrelbattle/tests/translations_test.py:64 #: squirrelbattle/tests/translations_test.py:66
msgid "body snatch potion" msgid "body snatch potion"
msgstr "Leichenfleddererzaubertrank" msgstr "Leichenfleddererzaubertrank"
#: squirrelbattle/tests/translations_test.py:65 #: squirrelbattle/tests/translations_test.py:67
msgid "bomb" msgid "bomb"
msgstr "Bombe" msgstr "Bombe"
#: squirrelbattle/tests/translations_test.py:66 #: squirrelbattle/tests/translations_test.py:68
msgid "heart" msgid "heart"
msgstr "Herz" msgstr "Herz"
#: squirrelbattle/tests/translations_test.py:69
msgid "sword"
msgstr "schwert"

View File

@ -0,0 +1,207 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR ÿnérant, eichhornchen, nicomarg, charlse
# This file is distributed under the same license as the squirrelbattle package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: squirrelbattle 3.14.1\n"
"Report-Msgid-Bugs-To: squirrel-battle@crans.org\n"
"POT-Creation-Date: 2020-12-01 17:10+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: squirrelbattle/display/statsdisplay.py:34
msgid "Inventory:"
msgstr ""
#: squirrelbattle/display/statsdisplay.py:39
msgid "YOU ARE DEAD"
msgstr ""
#: squirrelbattle/interfaces.py:394 squirrelbattle/interfaces.py:398
#: squirrelbattle/interfaces.py:408
#, python-brace-format
msgid "{name} hits {opponent}."
msgstr ""
#: squirrelbattle/interfaces.py:405 squirrelbattle/interfaces.py:410
#: squirrelbattle/interfaces.py:420
#, python-brace-format
msgid "{name} takes {amount} damage."
msgstr ""
#: squirrelbattle/menus.py:45 squirrelbattle/tests/translations_test.py:14
#: squirrelbattle/tests/game_test.py:284 squirrelbattle/tests/game_test.py:287
#: squirrelbattle/tests/translations_test.py:16
#: squirrelbattle/tests/game_test.py:290
msgid "New game"
msgstr ""
#: squirrelbattle/menus.py:46 squirrelbattle/tests/translations_test.py:15
#: squirrelbattle/tests/translations_test.py:17
msgid "Resume"
msgstr ""
#: squirrelbattle/menus.py:47 squirrelbattle/tests/translations_test.py:17
#: squirrelbattle/tests/translations_test.py:19
msgid "Save"
msgstr ""
#: squirrelbattle/menus.py:48 squirrelbattle/tests/translations_test.py:16
#: squirrelbattle/tests/translations_test.py:18
msgid "Load"
msgstr ""
#: squirrelbattle/menus.py:49 squirrelbattle/tests/translations_test.py:18
#: squirrelbattle/tests/translations_test.py:20
msgid "Settings"
msgstr ""
#: squirrelbattle/menus.py:50 squirrelbattle/tests/translations_test.py:19
#: squirrelbattle/tests/translations_test.py:21
msgid "Exit"
msgstr ""
#: squirrelbattle/menus.py:71
msgid "Back"
msgstr ""
#: squirrelbattle/game.py:147 squirrelbattle/game.py:148
msgid ""
"Some keys are missing in your save file.\n"
"Your save seems to be corrupt. It got deleted."
msgstr ""
#: squirrelbattle/game.py:155 squirrelbattle/game.py:156
msgid ""
"No player was found on this map!\n"
"Maybe you died?"
msgstr ""
#: squirrelbattle/game.py:175 squirrelbattle/game.py:176
msgid ""
"The JSON file is not correct.\n"
"Your save seems corrupted. It got deleted."
msgstr ""
#: squirrelbattle/settings.py:21 squirrelbattle/tests/translations_test.py:21
#: squirrelbattle/tests/translations_test.py:25
#: squirrelbattle/tests/translations_test.py:27
msgid "Main key to move up"
msgstr ""
#: squirrelbattle/settings.py:22 squirrelbattle/tests/translations_test.py:23
#: squirrelbattle/tests/translations_test.py:27
#: squirrelbattle/tests/translations_test.py:29
msgid "Secondary key to move up"
msgstr ""
#: squirrelbattle/settings.py:23 squirrelbattle/tests/translations_test.py:25
#: squirrelbattle/tests/translations_test.py:29
#: squirrelbattle/tests/translations_test.py:31
msgid "Main key to move down"
msgstr ""
#: squirrelbattle/settings.py:24 squirrelbattle/tests/translations_test.py:27
#: squirrelbattle/tests/translations_test.py:31
#: squirrelbattle/tests/translations_test.py:33
msgid "Secondary key to move down"
msgstr ""
#: squirrelbattle/settings.py:25 squirrelbattle/tests/translations_test.py:29
#: squirrelbattle/tests/translations_test.py:33
#: squirrelbattle/tests/translations_test.py:35
msgid "Main key to move left"
msgstr ""
#: squirrelbattle/settings.py:26 squirrelbattle/tests/translations_test.py:31
#: squirrelbattle/tests/translations_test.py:35
#: squirrelbattle/tests/translations_test.py:37
msgid "Secondary key to move left"
msgstr ""
#: squirrelbattle/settings.py:27 squirrelbattle/tests/translations_test.py:33
#: squirrelbattle/tests/translations_test.py:37
#: squirrelbattle/tests/translations_test.py:39
msgid "Main key to move right"
msgstr ""
#: squirrelbattle/settings.py:29 squirrelbattle/tests/translations_test.py:35
#: squirrelbattle/tests/translations_test.py:39
#: squirrelbattle/tests/translations_test.py:41
msgid "Secondary key to move right"
msgstr ""
#: squirrelbattle/settings.py:30 squirrelbattle/tests/translations_test.py:37
#: squirrelbattle/tests/translations_test.py:41
#: squirrelbattle/tests/translations_test.py:43
msgid "Key to validate a menu"
msgstr ""
#: squirrelbattle/settings.py:31 squirrelbattle/tests/translations_test.py:39
#: squirrelbattle/tests/translations_test.py:43
#: squirrelbattle/tests/translations_test.py:45
msgid "Texture pack"
msgstr ""
#: squirrelbattle/settings.py:32 squirrelbattle/tests/translations_test.py:40
#: squirrelbattle/tests/translations_test.py:44
#: squirrelbattle/tests/translations_test.py:46
msgid "Language"
msgstr ""
#: squirrelbattle/interfaces.py:407 squirrelbattle/interfaces.py:412
#: squirrelbattle/interfaces.py:422
#, python-brace-format
msgid "{name} dies."
msgstr ""
#: squirrelbattle/tests/translations_test.py:47
#: squirrelbattle/tests/translations_test.py:49
msgid "player"
msgstr ""
#: squirrelbattle/tests/translations_test.py:49
#: squirrelbattle/tests/translations_test.py:51
msgid "tiger"
msgstr ""
#: squirrelbattle/tests/translations_test.py:50
#: squirrelbattle/tests/translations_test.py:52
msgid "hedgehog"
msgstr ""
#: squirrelbattle/tests/translations_test.py:51
#: squirrelbattle/tests/translations_test.py:53
msgid "rabbit"
msgstr ""
#: squirrelbattle/tests/translations_test.py:52
#: squirrelbattle/tests/translations_test.py:54
msgid "teddy bear"
msgstr ""
#: squirrelbattle/tests/translations_test.py:54
#: squirrelbattle/tests/translations_test.py:56
msgid "bomb"
msgstr ""
#: squirrelbattle/tests/translations_test.py:55
#: squirrelbattle/tests/translations_test.py:57
msgid "heart"
msgstr ""
#: squirrelbattle/entities/friendly.py:31
msgid "Flower power!!"
msgstr ""
#: squirrelbattle/entities/friendly.py:31
msgid "The sun is warm today"
msgstr ""

View File

@ -7,7 +7,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: squirrelbattle 3.14.1\n" "Project-Id-Version: squirrelbattle 3.14.1\n"
"Report-Msgid-Bugs-To: squirrel-battle@crans.org\n" "Report-Msgid-Bugs-To: squirrel-battle@crans.org\n"
"POT-Creation-Date: 2020-12-05 14:46+0100\n" "POT-Creation-Date: 2020-12-11 18:06+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -16,31 +16,52 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
#: squirrelbattle/display/menudisplay.py:105 #: squirrelbattle/display/menudisplay.py:113
msgid "== INVENTORY ==" msgid "== INVENTORY =="
msgstr "== INVENTAIRE ==" msgstr "== INVENTAIRE =="
#: squirrelbattle/display/menudisplay.py:134
msgid "== STALL =="
msgstr "== STAND =="
#: squirrelbattle/display/statsdisplay.py:34 #: squirrelbattle/display/statsdisplay.py:34
msgid "Inventory:" msgid "Inventory:"
msgstr "Inventaire :" msgstr "Inventaire :"
#: squirrelbattle/display/statsdisplay.py:50 #: squirrelbattle/display/statsdisplay.py:53
msgid "YOU ARE DEAD" msgid "YOU ARE DEAD"
msgstr "VOUS ÊTES MORT" msgstr "VOUS ÊTES MORT"
#. TODO
#: squirrelbattle/entities/friendly.py:33
msgid "I don't sell any squirrel"
msgstr "Je ne vends pas d'écureuil"
#: squirrelbattle/entities/friendly.py:46
msgid "Flower power!!"
msgstr "Pouvoir des fleurs !"
#: squirrelbattle/entities/friendly.py:46
msgid "The sun is warm today"
msgstr "Le soleil est chaud aujourd'hui"
#. The bomb is exploding. #. The bomb is exploding.
#. Each entity that is close to the bomb takes damages. #. Each entity that is close to the bomb takes damages.
#. The player earn XP if the entity was killed. #. The player earn XP if the entity was killed.
#: squirrelbattle/entities/items.py:128 #: squirrelbattle/entities/items.py:151
msgid "Bomb is exploding." msgid "Bomb is exploding."
msgstr "La bombe explose." msgstr "La bombe explose."
#: squirrelbattle/entities/items.py:172 #: squirrelbattle/entities/items.py:224
#, python-brace-format #, python-brace-format
msgid "{player} exchanged its body with {entity}." msgid "{player} exchanged its body with {entity}."
msgstr "{player} a échangé son corps avec {entity}." msgstr "{player} a échangé son corps avec {entity}."
#: squirrelbattle/game.py:177 #: squirrelbattle/game.py:199 squirrelbattle/tests/game_test.py:537
msgid "You do not have enough money"
msgstr ""
#: squirrelbattle/game.py:243
msgid "" msgid ""
"Some keys are missing in your save file.\n" "Some keys are missing in your save file.\n"
"Your save seems to be corrupt. It got deleted." "Your save seems to be corrupt. It got deleted."
@ -48,7 +69,7 @@ msgstr ""
"Certaines clés de votre ficher de sauvegarde sont manquantes.\n" "Certaines clés de votre ficher de sauvegarde sont manquantes.\n"
"Votre sauvegarde semble corrompue. Elle a été supprimée." "Votre sauvegarde semble corrompue. Elle a été supprimée."
#: squirrelbattle/game.py:185 #: squirrelbattle/game.py:251
msgid "" msgid ""
"No player was found on this map!\n" "No player was found on this map!\n"
"Maybe you died?" "Maybe you died?"
@ -56,7 +77,7 @@ msgstr ""
"Aucun joueur n'a été trouvé sur la carte !\n" "Aucun joueur n'a été trouvé sur la carte !\n"
"Peut-être êtes-vous mort ?" "Peut-être êtes-vous mort ?"
#: squirrelbattle/game.py:205 #: squirrelbattle/game.py:271
msgid "" msgid ""
"The JSON file is not correct.\n" "The JSON file is not correct.\n"
"Your save seems corrupted. It got deleted." "Your save seems corrupted. It got deleted."
@ -64,27 +85,27 @@ msgstr ""
"Le fichier JSON de sauvegarde est incorrect.\n" "Le fichier JSON de sauvegarde est incorrect.\n"
"Votre sauvegarde semble corrompue. Elle a été supprimée." "Votre sauvegarde semble corrompue. Elle a été supprimée."
#: squirrelbattle/interfaces.py:400 #: squirrelbattle/interfaces.py:429
#, python-brace-format #, python-brace-format
msgid "{name} hits {opponent}." msgid "{name} hits {opponent}."
msgstr "{name} frappe {opponent}." msgstr "{name} frappe {opponent}."
#: squirrelbattle/interfaces.py:412 #: squirrelbattle/interfaces.py:441
#, python-brace-format #, python-brace-format
msgid "{name} takes {amount} damage." msgid "{name} takes {amount} damage."
msgstr "{name} prend {amount} points de dégât." msgstr "{name} prend {amount} points de dégât."
#: squirrelbattle/interfaces.py:414 #: squirrelbattle/interfaces.py:443
#, python-brace-format #, python-brace-format
msgid "{name} dies." msgid "{name} dies."
msgstr "{name} meurt." msgstr "{name} meurt."
#: squirrelbattle/menus.py:72 #: squirrelbattle/menus.py:73
msgid "Back" msgid "Back"
msgstr "Retour" msgstr "Retour"
#: squirrelbattle/tests/game_test.py:300 squirrelbattle/tests/game_test.py:303 #: squirrelbattle/tests/game_test.py:314 squirrelbattle/tests/game_test.py:317
#: squirrelbattle/tests/game_test.py:306 #: squirrelbattle/tests/game_test.py:320
#: squirrelbattle/tests/translations_test.py:16 #: squirrelbattle/tests/translations_test.py:16
msgid "New game" msgid "New game"
msgstr "Nouvelle partie" msgstr "Nouvelle partie"
@ -162,41 +183,49 @@ msgid "Key used to drop an item in the inventory"
msgstr "Touche pour jeter un objet de l'inventaire" msgstr "Touche pour jeter un objet de l'inventaire"
#: squirrelbattle/tests/translations_test.py:53 #: squirrelbattle/tests/translations_test.py:53
msgid "Key used to talk to a friendly entity"
msgstr "Touche pour parler à une entité pacifique"
#: squirrelbattle/tests/translations_test.py:55
msgid "Texture pack" msgid "Texture pack"
msgstr "Pack de textures" msgstr "Pack de textures"
#: squirrelbattle/tests/translations_test.py:54 #: squirrelbattle/tests/translations_test.py:56
msgid "Language" msgid "Language"
msgstr "Langue" msgstr "Langue"
#: squirrelbattle/tests/translations_test.py:57 #: squirrelbattle/tests/translations_test.py:59
msgid "player" msgid "player"
msgstr "joueur" msgstr "joueur"
#: squirrelbattle/tests/translations_test.py:59 #: squirrelbattle/tests/translations_test.py:61
msgid "tiger" msgid "tiger"
msgstr "tigre" msgstr "tigre"
#: squirrelbattle/tests/translations_test.py:60 #: squirrelbattle/tests/translations_test.py:62
msgid "hedgehog" msgid "hedgehog"
msgstr "hérisson" msgstr "hérisson"
#: squirrelbattle/tests/translations_test.py:61 #: squirrelbattle/tests/translations_test.py:63
msgid "rabbit" msgid "rabbit"
msgstr "lapin" msgstr "lapin"
#: squirrelbattle/tests/translations_test.py:62 #: squirrelbattle/tests/translations_test.py:64
msgid "teddy bear" msgid "teddy bear"
msgstr "nounours" msgstr "nounours"
#: squirrelbattle/tests/translations_test.py:64 #: squirrelbattle/tests/translations_test.py:66
msgid "body snatch potion" msgid "body snatch potion"
msgstr "potion d'arrachage de corps" msgstr "potion d'arrachage de corps"
#: squirrelbattle/tests/translations_test.py:65 #: squirrelbattle/tests/translations_test.py:67
msgid "bomb" msgid "bomb"
msgstr "bombe" msgstr "bombe"
#: squirrelbattle/tests/translations_test.py:66 #: squirrelbattle/tests/translations_test.py:68
msgid "heart" msgid "heart"
msgstr "cœur" msgstr "cœur"
#: squirrelbattle/tests/translations_test.py:69
msgid "sword"
msgstr "épée"

View File

@ -6,6 +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 .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
@ -128,3 +129,14 @@ class InventoryMenu(Menu):
@property @property
def values(self) -> list: def values(self) -> list:
return self.player.inventory return self.player.inventory
class StoreMenu(Menu):
merchant: Merchant
def update_merchant(self, merchant: Merchant) -> None:
self.merchant = merchant
@property
def values(self) -> list:
return self.merchant.inventory

View File

@ -31,6 +31,7 @@ class Settings:
self.KEY_USE = ['u', 'Key used to use an item in the inventory'] self.KEY_USE = ['u', 'Key used to use an item in the inventory']
self.KEY_EQUIP = ['e', 'Key used to equip an item in the inventory'] self.KEY_EQUIP = ['e', 'Key used to equip an item in the inventory']
self.KEY_DROP = ['r', 'Key used to drop an item in the inventory'] self.KEY_DROP = ['r', 'Key used to drop an item in the inventory']
self.KEY_CHAT = ['t', 'Key used to talk to a friendly entity']
self.TEXTURE_PACK = ['ascii', 'Texture pack'] self.TEXTURE_PACK = ['ascii', 'Texture pack']
self.LOCALE = [locale.getlocale()[0][:2], 'Language'] self.LOCALE = [locale.getlocale()[0][:2], 'Language']

View File

@ -20,6 +20,8 @@ class TermManager: # pragma: no cover
curses.cbreak() curses.cbreak()
# make cursor invisible # make cursor invisible
curses.curs_set(False) curses.curs_set(False)
# Catch mouse events
curses.mousemask(True)
# Enable colors # Enable colors
curses.start_color() curses.start_color()

View File

@ -7,7 +7,8 @@ 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.items import Bomb from ..entities.friendly import Merchant, Sunflower
from ..entities.items import Bomb, Heart, Sword
from ..entities.player import Player from ..entities.player import Player
from ..enums import DisplayActions from ..enums import DisplayActions
from ..game import Game, KeyValues, GameMode from ..game import Game, KeyValues, GameMode
@ -34,7 +35,17 @@ class TestGame(unittest.TestCase):
""" """
bomb = Bomb() bomb = Bomb()
self.game.map.add_entity(bomb) self.game.map.add_entity(bomb)
sword = Sword()
self.game.map.add_entity(sword)
# Add items in the inventory to check that it is well loaded
bomb.hold(self.game.player) bomb.hold(self.game.player)
sword.hold(self.game.player)
# Ensure that merchants can be saved
merchant = Merchant()
merchant.move(3, 6)
self.game.map.add_entity(merchant)
old_state = self.game.save_state() old_state = self.game.save_state()
self.game.handle_key_pressed(KeyValues.DOWN) self.game.handle_key_pressed(KeyValues.DOWN)
@ -117,6 +128,9 @@ class TestGame(unittest.TestCase):
self.assertEqual(KeyValues.translate_key( self.assertEqual(KeyValues.translate_key(
self.game.settings.KEY_INVENTORY, self.game.settings), self.game.settings.KEY_INVENTORY, self.game.settings),
KeyValues.INVENTORY) KeyValues.INVENTORY)
self.assertEqual(KeyValues.translate_key(
self.game.settings.KEY_CHAT, self.game.settings),
KeyValues.CHAT)
self.assertEqual(KeyValues.translate_key( self.assertEqual(KeyValues.translate_key(
self.game.settings.KEY_USE, self.game.settings), self.game.settings.KEY_USE, self.game.settings),
KeyValues.USE) KeyValues.USE)
@ -216,6 +230,33 @@ class TestGame(unittest.TestCase):
self.game.handle_key_pressed(KeyValues.SPACE) self.game.handle_key_pressed(KeyValues.SPACE)
self.assertEqual(self.game.state, GameMode.MAINMENU) self.assertEqual(self.game.state, GameMode.MAINMENU)
def test_mouse_click(self) -> None:
"""
Simulate mouse clicks.
"""
self.game.state = GameMode.MAINMENU
# Settings menu
self.game.display_actions(DisplayActions.MOUSE, 25, 21)
self.assertEqual(self.game.main_menu.position, 4)
self.assertEqual(self.game.state, GameMode.SETTINGS)
bomb = Bomb()
bomb.hold(self.game.player)
bomb2 = Bomb()
bomb2.hold(self.game.player)
self.game.state = GameMode.INVENTORY
# Click nowhere
self.game.display_actions(DisplayActions.MOUSE, 0, 0)
self.assertEqual(self.game.state, GameMode.INVENTORY)
# Click on the second item
self.game.display_actions(DisplayActions.MOUSE, 8, 25)
self.assertEqual(self.game.state, GameMode.INVENTORY)
self.assertEqual(self.game.inventory_menu.position, 1)
def test_new_game(self) -> None: def test_new_game(self) -> None:
""" """
Ensure that the start button starts a new game. Ensure that the start button starts a new game.
@ -280,7 +321,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(9): for ignored in range(10):
self.game.handle_key_pressed(KeyValues.DOWN) self.game.handle_key_pressed(KeyValues.DOWN)
# Change texture pack # Change texture pack
@ -417,3 +458,116 @@ class TestGame(unittest.TestCase):
self.assertTrue(bomb.exploding) self.assertTrue(bomb.exploding)
self.assertEqual(bomb.y, self.game.player.y) self.assertEqual(bomb.y, self.game.player.y)
self.assertEqual(bomb.x, self.game.player.x) self.assertEqual(bomb.x, self.game.player.x)
def test_talk_to_sunflowers(self) -> None:
"""
Interact with sunflowers
"""
self.game.state = GameMode.PLAY
sunflower = Sunflower()
sunflower.move(2, 6)
self.game.map.add_entity(sunflower)
# Does nothing
self.assertIsNone(self.game.handle_friendly_entity_chat(KeyValues.UP))
# Talk to sunflower... or not
self.game.handle_key_pressed(KeyValues.CHAT)
self.assertTrue(self.game.waiting_for_friendly_key)
# Wrong key
self.game.handle_key_pressed(KeyValues.EQUIP)
self.assertFalse(self.game.waiting_for_friendly_key)
self.game.handle_key_pressed(KeyValues.CHAT)
self.assertTrue(self.game.waiting_for_friendly_key)
self.game.handle_key_pressed(KeyValues.UP)
self.assertFalse(self.game.waiting_for_friendly_key)
self.assertEqual(self.game.state, GameMode.PLAY)
self.assertFalse(len(self.game.logs.messages) > 1)
# Talk to sunflower
self.game.handle_key_pressed(KeyValues.CHAT)
self.assertTrue(self.game.waiting_for_friendly_key)
self.game.handle_key_pressed(KeyValues.DOWN)
self.assertFalse(self.game.waiting_for_friendly_key)
self.assertEqual(self.game.state, GameMode.PLAY)
self.assertTrue(self.game.logs.messages)
# Ensure that the message is a good message
self.assertIn(self.game.logs.messages[1][21:],
Sunflower.dialogue_option)
# Test all directions to detect the friendly entity
self.game.player.move(3, 6)
self.game.handle_key_pressed(KeyValues.CHAT)
self.game.handle_key_pressed(KeyValues.UP)
self.assertEqual(len(self.game.logs.messages), 3)
self.game.player.move(2, 7)
self.game.handle_key_pressed(KeyValues.CHAT)
self.game.handle_key_pressed(KeyValues.LEFT)
self.assertEqual(len(self.game.logs.messages), 4)
self.game.player.move(2, 5)
self.game.handle_key_pressed(KeyValues.CHAT)
self.game.handle_key_pressed(KeyValues.RIGHT)
self.assertEqual(len(self.game.logs.messages), 5)
def test_talk_to_merchant(self) -> None:
"""
Interact with merchants
"""
self.game.state = GameMode.PLAY
merchant = Merchant()
merchant.move(2, 6)
self.game.map.add_entity(merchant)
# Does nothing
self.assertIsNone(self.game.handle_friendly_entity_chat(KeyValues.UP))
# Talk to merchant
self.game.handle_key_pressed(KeyValues.CHAT)
self.assertTrue(self.game.waiting_for_friendly_key)
self.game.handle_key_pressed(KeyValues.DOWN)
self.assertFalse(self.game.waiting_for_friendly_key)
self.assertEqual(self.game.state, GameMode.STORE)
self.assertTrue(self.game.logs.messages)
# Navigate in the menu
self.game.handle_key_pressed(KeyValues.DOWN)
self.game.handle_key_pressed(KeyValues.DOWN)
self.game.handle_key_pressed(KeyValues.UP)
self.assertEqual(self.game.store_menu.position, 1)
# The second item is not a heart
merchant.inventory[1] = Sword()
# Buy the second item by clicking on it
item = self.game.store_menu.validate()
self.assertIn(item, merchant.inventory)
self.game.display_actions(DisplayActions.MOUSE, 8, 25)
self.game.handle_key_pressed(KeyValues.ENTER)
self.assertIn(item, self.game.player.inventory)
self.assertNotIn(item, merchant.inventory)
# Buy a heart
merchant.inventory[1] = Heart()
item = self.game.store_menu.validate()
self.assertIn(item, merchant.inventory)
self.assertEqual(item, merchant.inventory[1])
self.game.player.health = self.game.player.maxhealth - 1 - item.healing
self.game.handle_key_pressed(KeyValues.ENTER)
self.assertNotIn(item, self.game.player.inventory)
self.assertNotIn(item, merchant.inventory)
self.assertEqual(self.game.player.health,
self.game.player.maxhealth - 1)
# We don't have enough of money
self.game.player.hazel = 0
item = self.game.store_menu.validate()
self.game.handle_key_pressed(KeyValues.ENTER)
self.assertNotIn(item, self.game.player.inventory)
self.assertIn(item, merchant.inventory)
self.assertEqual(self.game.message, _("You do not have enough money"))
self.game.handle_key_pressed(KeyValues.ENTER)
# Exit the menu
self.game.handle_key_pressed(KeyValues.SPACE)
self.assertEqual(self.game.state, GameMode.PLAY)

View File

@ -50,6 +50,8 @@ class TestTranslations(unittest.TestCase):
"Touche pour équiper un objet de l'inventaire") "Touche pour équiper un objet de l'inventaire")
self.assertEqual(_("Key used to drop an item in the inventory"), self.assertEqual(_("Key used to drop an item in the inventory"),
"Touche pour jeter un objet de l'inventaire") "Touche pour jeter un objet de l'inventaire")
self.assertEqual(_("Key used to talk to a friendly entity"),
"Touche pour parler à une entité pacifique")
self.assertEqual(_("Texture pack"), "Pack de textures") self.assertEqual(_("Texture pack"), "Pack de textures")
self.assertEqual(_("Language"), "Langue") self.assertEqual(_("Language"), "Langue")
@ -64,3 +66,4 @@ class TestTranslations(unittest.TestCase):
self.assertEqual(_("body snatch potion"), "potion d'arrachage de corps") self.assertEqual(_("body snatch potion"), "potion d'arrachage de corps")
self.assertEqual(_("bomb"), "bombe") self.assertEqual(_("bomb"), "bombe")
self.assertEqual(_("heart"), "cœur") self.assertEqual(_("heart"), "cœur")
self.assertEqual(_("sword"), "épée")