Merge branch 'master' into 'doc'

# Conflicts:
#   squirrelbattle/entities/items.py
#   squirrelbattle/interfaces.py
This commit is contained in:
eichhornchen 2021-01-08 11:10:09 +01:00
commit 196e3708d2
21 changed files with 802 additions and 232 deletions

1
.gitignore vendored
View File

@ -3,6 +3,7 @@
env/ env/
venv/ venv/
local/
.coverage .coverage
.pytest_cache/ .pytest_cache/

View File

@ -1,8 +1,8 @@
1 6 1 6
####### ############# ####### #############
#.....# #...........# #.H...# #...........#
#.....# #####...........# #.....# #####...........#
#.....# #...............# #.....# #............H..#
#.##### #.###...........# #.##### #.###...........#
#.# #.# #...........# #.# #.# #...........#
#.# #.# ############# #.# #.# #############

View File

@ -1,6 +1,6 @@
1 17 1 17
########### ######### ########### #########
#.........# #.......# #....H....# #.......#
#.........# ############.......# #.........# ############.......#
#.........###############..........#.......############## #.........###############..........#.......##############
#.........#........................#....................# #.........#........................#....................#
@ -13,7 +13,7 @@
########.##########......# #.........# #.........# ########.##########......# #.........# #.........#
#...........##......# #.........# #.........# #...........##......# #.........# #.........#
#...........##......# #.........# #.........# #...........##......# #.........# #.........#
#...........##......# #.........# ################.###### #...........##..H...# #.........# ################.######
#...........##......# #.........# #.................############ #...........##......# #.........# #.................############
#...........##......# ########.########.......#.........#..........# #...........##......# ########.########.......#.........#..........#
#...........##......# #...............#.......#.........#..........# #...........##......# #...............#.......#.........#..........#

View File

@ -0,0 +1,41 @@
1 6
################################################################################
#..............................................................................#
#..#...........................................................................#
#...........#..................................................................#
#..............................................................................#
#..............................................................................#
#..............................................................................#
#..............................................................................#
#..............................................................................#
#..............................................................................#
#..............................................................................#
#..............................................................................#
#..............................................................................#
#..............................................................................#
#..............................................................................#
#..............................................................................#
#..............................................................................#
#..............................................................................#
#..............................................................................#
#..............................................................................#
#..............................................................................#
#..............................................................................#
#..............................................................................#
#..............................................................................#
#..............................................................................#
#..............................................................................#
#..............................................................................#
#..............................................................................#
#..............................................................................#
#..............................................................................#
#..............................................................................#
#..............................................................................#
#..............................................................................#
#..............................................................................#
#..............................................................................#
#..............................................................................#
#..............................................................................#
#..............................................................................#
#..............................................................................#
################################################################################

View File

@ -22,13 +22,21 @@ class MapDisplay(Display):
self.pack.tile_width * self.map.width + 1) self.pack.tile_width * self.map.width + 1)
def update_pad(self) -> None: def update_pad(self) -> None:
self.pad.resize(500, 500) for j in range(len(self.map.tiles)):
self.addstr(self.pad, 0, 0, self.map.draw_string(self.pack), for i in range(len(self.map.tiles[j])):
self.pack.tile_fg_color, self.pack.tile_bg_color) if not self.map.seen_tiles[j][i]:
continue
fg, bg = self.map.tiles[j][i].visible_color(self.pack) if \
self.map.visibility[j][i] else \
self.map.tiles[j][i].hidden_color(self.pack)
self.addstr(self.pad, j, self.pack.tile_width * i,
self.map.tiles[j][i].char(self.pack), fg, bg)
for e in self.map.entities: for e in self.map.entities:
self.addstr(self.pad, e.y, self.pack.tile_width * e.x, if self.map.visibility[e.y][e.x]:
self.pack[e.name.upper()], self.addstr(self.pad, e.y, self.pack.tile_width * e.x,
self.pack.entity_fg_color, self.pack.entity_bg_color) self.pack[e.name.upper()],
self.pack.entity_fg_color,
self.pack.entity_bg_color)
# Display Path map for debug purposes # Display Path map for debug purposes
# from squirrelbattle.entities.player import Player # from squirrelbattle.entities.player import Player

View File

@ -23,16 +23,18 @@ class StatsDisplay(Display):
self.player = game.player self.player = game.player
def update_pad(self) -> None: def update_pad(self) -> None:
string2 = _("player").capitalize() + " -- LVL {}\nEXP {}/{}\nHP {}/{}"\ string2 = f"{_(self.player.name).capitalize()} " \
.format(self.player.level, self.player.current_xp, f"-- LVL {self.player.level} -- " \
self.player.max_xp, self.player.health, f"FLOOR {-self.player.map.floor}\n" \
self.player.maxhealth) f"EXP {self.player.current_xp}/{self.player.max_xp}\n" \
f"HP {self.player.health}/{self.player.maxhealth}"
self.addstr(self.pad, 0, 0, string2) self.addstr(self.pad, 0, 0, string2)
string3 = "STR {}\nINT {}\nCHR {}\nDEX {}\nCON {}\nCRI {}%"\ string3 = f"STR {self.player.strength}\n" \
.format(self.player.strength, f"INT {self.player.intelligence}\n" \
self.player.intelligence, self.player.charisma, f"CHR {self.player.charisma}\n" \
self.player.dexterity, self.player.constitution,\ f"DEX {self.player.dexterity}\n" \
self.player.critical) f"CON {self.player.constitution}\n" \
f"CRI {self.player.critical}%"
self.addstr(self.pad, 3, 0, string3) self.addstr(self.pad, 3, 0, string3)
inventory_str = _("Inventory:") + " " inventory_str = _("Inventory:") + " "
@ -57,15 +59,16 @@ class StatsDisplay(Display):
if self.player.equipped_secondary: if self.player.equipped_secondary:
self.addstr(self.pad, 11, 0, self.addstr(self.pad, 11, 0,
_("Equipped secondary:") + " " _("Equipped secondary:") + " "
f"{self.pack[self.player.equipped_secondary.name.upper()]}") + self.pack[self.player.equipped_secondary
.name.upper()])
if self.player.equipped_armor: if self.player.equipped_armor:
self.addstr(self.pad, 12, 0, self.addstr(self.pad, 12, 0,
_("Equipped chestplate:") + " " _("Equipped chestplate:") + " "
f"{self.pack[self.player.equipped_armor.name.upper()]}") + self.pack[self.player.equipped_armor.name.upper()])
if self.player.equipped_helmet: if self.player.equipped_helmet:
self.addstr(self.pad, 13, 0, self.addstr(self.pad, 13, 0,
_("Equipped helmet:") + " " _("Equipped helmet:") + " "
f"{self.pack[self.player.equipped_helmet.name.upper()]}") + self.pack[self.player.equipped_helmet.name.upper()])
self.addstr(self.pad, 14, 0, f"{self.pack.HAZELNUT} " self.addstr(self.pad, 14, 0, f"{self.pack.HAZELNUT} "
f"x{self.player.hazel}") f"x{self.player.hazel}")

View File

@ -2,7 +2,7 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
import curses import curses
from typing import Any from typing import Any, Union, Tuple
class TexturePack: class TexturePack:
@ -13,10 +13,11 @@ class TexturePack:
name: str name: str
tile_width: int tile_width: int
tile_fg_color: int tile_fg_color: Union[int, Tuple[int, int, int]]
tile_bg_color: int tile_fg_visible_color: Union[int, Tuple[int, int, int]]
entity_fg_color: int tile_bg_color: Union[int, Tuple[int, int, int]]
entity_bg_color: int entity_fg_color: Union[int, Tuple[int, int, int]]
entity_bg_color: Union[int, Tuple[int, int, int]]
BODY_SNATCH_POTION: str BODY_SNATCH_POTION: str
BOMB: str BOMB: str
@ -64,9 +65,10 @@ class TexturePack:
TexturePack.ASCII_PACK = TexturePack( TexturePack.ASCII_PACK = TexturePack(
name="ascii", name="ascii",
tile_width=1, tile_width=1,
tile_fg_visible_color=(1000, 1000, 1000),
tile_fg_color=curses.COLOR_WHITE, tile_fg_color=curses.COLOR_WHITE,
tile_bg_color=curses.COLOR_BLACK, tile_bg_color=curses.COLOR_BLACK,
entity_fg_color=curses.COLOR_WHITE, entity_fg_color=(1000, 1000, 1000),
entity_bg_color=curses.COLOR_BLACK, entity_bg_color=curses.COLOR_BLACK,
BODY_SNATCH_POTION='S', BODY_SNATCH_POTION='S',
@ -76,6 +78,7 @@ TexturePack.ASCII_PACK = TexturePack(
EMPTY=' ', EMPTY=' ',
EXPLOSION='%', EXPLOSION='%',
FLOOR='.', FLOOR='.',
LADDER='H',
HAZELNUT='¤', HAZELNUT='¤',
HEART='', HEART='',
HEDGEHOG='*', HEDGEHOG='*',
@ -97,10 +100,11 @@ TexturePack.ASCII_PACK = TexturePack(
TexturePack.SQUIRREL_PACK = TexturePack( TexturePack.SQUIRREL_PACK = TexturePack(
name="squirrel", name="squirrel",
tile_width=2, tile_width=2,
tile_fg_visible_color=(1000, 1000, 1000),
tile_fg_color=curses.COLOR_WHITE, tile_fg_color=curses.COLOR_WHITE,
tile_bg_color=curses.COLOR_BLACK, tile_bg_color=curses.COLOR_BLACK,
entity_fg_color=curses.COLOR_WHITE, entity_fg_color=(1000, 1000, 1000),
entity_bg_color=curses.COLOR_WHITE, entity_bg_color=(1000, 1000, 1000),
BODY_SNATCH_POTION='🔀', BODY_SNATCH_POTION='🔀',
BOMB='💣', BOMB='💣',
@ -109,6 +113,8 @@ TexturePack.SQUIRREL_PACK = TexturePack(
EMPTY=' ', EMPTY=' ',
EXPLOSION='💥', EXPLOSION='💥',
FLOOR='██', FLOOR='██',
LADDER=('🪜', curses.COLOR_WHITE, (1000, 1000, 1000),
curses.COLOR_WHITE, (1000, 1000, 1000)),
HAZELNUT='🌰', HAZELNUT='🌰',
HEART='💜', HEART='💜',
HEDGEHOG='🦔', HEDGEHOG='🦔',

View File

@ -44,48 +44,17 @@ class Item(Entity):
""" """
Indicates what should be done when the item is equipped. Indicates what should be done when the item is equipped.
""" """
if isinstance(self, Chestplate): # Other objects are only equipped as secondary.
if self.held_by.equipped_armor: if self.held_by.equipped_secondary:
self.held_by.equipped_armor.unequip() self.held_by.equipped_secondary.unequip()
self.held_by.remove_from_inventory(self) self.held_by.remove_from_inventory(self)
self.held_by.equipped_armor = self self.held_by.equipped_secondary = self
elif isinstance(self, Helmet):
if self.held_by.equipped_helmet:
self.held_by.equipped_helmet.unequip()
self.held_by.remove_from_inventory(self)
self.held_by.equipped_helmet = self
elif isinstance(self, Weapon):
if self.held_by.equipped_main:
if self.held_by.equipped_secondary:
self.held_by.equipped_secondary.unequip()
self.held_by.remove_from_inventory(self)
self.held_by.equipped_secondary = self
# For weapons, they are equipped as main only if main is empty.
else:
self.held_by.remove_from_inventory(self)
self.held_by.equipped_main = self
else:
# Other objects are only equipped as secondary.
if self.held_by.equipped_secondary:
self.held_by.equipped_secondary.unequip()
self.held_by.remove_from_inventory(self)
self.held_by.equipped_secondary = self
def unequip(self) -> None: def unequip(self) -> None:
""" """
Indicates what should be done when the item is unequipped. Indicates what should be done when the item is unequipped.
""" """
if isinstance(self, Chestplate): self.held_by.remove_from_inventory(self)
self.held_by.equipped_armor = None
elif isinstance(self, Helmet):
self.held_by.equipped_helmet = None
elif isinstance(self, Weapon):
if self.held_by.equipped_main == self:
self.held_by.equipped_main = None
else:
self.held_by.equipped_secondary = None
else:
self.held_by.equipped_secondary = None
self.held_by.add_to_inventory(self) self.held_by.add_to_inventory(self)
def hold(self, holder: InventoryHolder) -> None: def hold(self, holder: InventoryHolder) -> None:
@ -236,7 +205,6 @@ class Explosion(Item):
""" """
The player can't hold an explosion. The player can't hold an explosion.
""" """
pass
class Weapon(Item): class Weapon(Item):
@ -261,7 +229,8 @@ class Weapon(Item):
""" """
When a weapon is equipped, the player gains strength. When a weapon is equipped, the player gains strength.
""" """
super().equip() self.held_by.remove_from_inventory(self)
self.held_by.equipped_main = self
self.held_by.strength += self.damage self.held_by.strength += self.damage
def unequip(self) -> None: def unequip(self) -> None:
@ -287,7 +256,7 @@ class Armor(Item):
Class of items that increase the player's constitution. Class of items that increase the player's constitution.
""" """
constitution: int constitution: int
def __init__(self, constitution: int, *args, **kwargs): def __init__(self, constitution: int, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.constitution = constitution self.constitution = constitution
@ -305,33 +274,45 @@ class Armor(Item):
d["constitution"] = self.constitution d["constitution"] = self.constitution
return d return d
class Shield(Armor): class Shield(Armor):
""" """
Class of shield items, they can be equipped in the other hand. Class of shield items, they can be equipped in the other hand.
""" """
def __init__(self, name: str = "shield", constitution: int = 2,
def __init__(self, name: str = "shield", constitution: int = 2,\
price: int = 16, *args, **kwargs): price: int = 16, *args, **kwargs):
super().__init__(name=name, constitution=constitution, *args, **kwargs) super().__init__(name=name, constitution=constitution, price=price,
*args, **kwargs)
class Helmet(Armor): class Helmet(Armor):
""" """
Class of helmet items, they can be equipped on the head. Class of helmet items, they can be equipped on the head.
""" """
def __init__(self, name: str = "helmet", constitution: int = 2,
def __init__(self, name: str = "helmet", constitution: int = 2, \
price: int = 18, *args, **kwargs): price: int = 18, *args, **kwargs):
super().__init__(name=name, constitution=constitution, *args, **kwargs) super().__init__(name=name, constitution=constitution, price=price,
*args, **kwargs)
def equip(self) -> None:
if self.held_by.equipped_helmet:
self.held_by.equipped_helmet.unequip()
self.held_by.remove_from_inventory(self)
self.held_by.equipped_helmet = self
class Chestplate(Armor): class Chestplate(Armor):
""" """
Class of chestplate items, they can be equipped on the body. Class of chestplate items, they can be equipped on the body.
""" """
def __init__(self, name: str = "chestplate", constitution: int = 4,
def __init__(self, name: str = "chestplate", constitution: int = 4,\
price: int = 30, *args, **kwargs): price: int = 30, *args, **kwargs):
super().__init__(name=name, constitution=constitution, *args, **kwargs) super().__init__(name=name, constitution=constitution, price=price,
*args, **kwargs)
def equip(self) -> None:
if self.held_by.equipped_armor:
self.held_by.equipped_armor.unequip()
self.held_by.remove_from_inventory(self)
self.held_by.equipped_armor = self
class BodySnatchPotion(Item): class BodySnatchPotion(Item):
""" """
@ -366,6 +347,7 @@ class BodySnatchPotion(Item):
self.held_by.inventory.remove(self) self.held_by.inventory.remove(self)
class Ring(Item): class Ring(Item):
""" """
A class of rings that boost the player's statistics. A class of rings that boost the player's statistics.
@ -379,9 +361,9 @@ class Ring(Item):
critical: int critical: int
experience: float experience: float
def __init__(self, maxhealth: int = 0, strength: int = 0,\ def __init__(self, maxhealth: int = 0, strength: int = 0,
intelligence: int = 0, charisma: int = 0,\ intelligence: int = 0, charisma: int = 0,
dexterity: int = 0, constitution: int = 0,\ dexterity: int = 0, constitution: int = 0,
critical: int = 0, experience: float = 0, *args, **kwargs): critical: int = 0, experience: float = 0, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.maxhealth = maxhealth self.maxhealth = maxhealth
@ -417,17 +399,26 @@ class Ring(Item):
def save_state(self) -> dict: def save_state(self) -> dict:
d = super().save_state() d = super().save_state()
d["maxhealth"] = self.maxhealth
d["strength"] = self.strength
d["intelligence"] = self.intelligence
d["charisma"] = self.charisma
d["dexterity"] = self.dexterity
d["constitution"] = self.constitution d["constitution"] = self.constitution
d["critical"] = self.critical
d["experience"] = self.experience
return d return d
class RingCritical(Ring): class RingCritical(Ring):
def __init__(self, name: str = "ring_of_critical_damage", price: int = 15, def __init__(self, name: str = "ring_of_critical_damage", price: int = 15,
critical: int = 20, *args, **kwargs): critical: int = 20, *args, **kwargs):
super().__init__(name=name, price=price, critical=critical, \ super().__init__(name=name, price=price, critical=critical,
*args, **kwargs) *args, **kwargs)
class RingXP(Ring): class RingXP(Ring):
def __init__(self, name: str = "ring_of_more_experience", price: int = 25, def __init__(self, name: str = "ring_of_more_experience", price: int = 25,
experience: float = 2, *args, **kwargs): experience: float = 2, *args, **kwargs):
super().__init__(name=name, price=price, experience=experience, \ super().__init__(name=name, price=price, experience=experience,
*args, **kwargs) *args, **kwargs)

View File

@ -94,9 +94,11 @@ class Rabbit(Monster):
A rabbit monster. A rabbit monster.
""" """
def __init__(self, name: str = "rabbit", strength: int = 1, def __init__(self, name: str = "rabbit", strength: int = 1,
maxhealth: int = 15, critical: int = 30, *args, **kwargs) -> None: maxhealth: int = 15, critical: int = 30,
*args, **kwargs) -> None:
super().__init__(name=name, strength=strength, super().__init__(name=name, strength=strength,
maxhealth=maxhealth, critical=critical, *args, **kwargs) maxhealth=maxhealth, critical=critical,
*args, **kwargs)
class TeddyBear(Monster): class TeddyBear(Monster):
@ -108,6 +110,7 @@ class TeddyBear(Monster):
super().__init__(name=name, strength=strength, super().__init__(name=name, strength=strength,
maxhealth=maxhealth, *args, **kwargs) maxhealth=maxhealth, *args, **kwargs)
class GiantSeaEagle(Monster): class GiantSeaEagle(Monster):
""" """
An eagle boss An eagle boss

View File

@ -26,10 +26,10 @@ class Player(InventoryHolder, FightingEntity):
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,
hazel: int = 42, equipped_main: Optional[Item] = None, hazel: int = 42, equipped_main: Optional[Item] = None,
equipped_armor: Optional[Item] = None, critical: int = 5,\ equipped_armor: Optional[Item] = None, critical: int = 5,
equipped_secondary: Optional[Item] = None, \ equipped_secondary: Optional[Item] = None,
equipped_helmet: Optional[Item] = None, xp_buff: float = 1,\ equipped_helmet: Optional[Item] = None, xp_buff: float = 1,
*args, **kwargs) -> None: vision: int = 5, *args, **kwargs) -> 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,
dexterity=dexterity, constitution=constitution, dexterity=dexterity, constitution=constitution,
@ -40,18 +40,15 @@ class Player(InventoryHolder, FightingEntity):
self.inventory = self.translate_inventory(inventory or []) self.inventory = self.translate_inventory(inventory or [])
self.paths = dict() self.paths = dict()
self.hazel = hazel self.hazel = hazel
if isinstance(equipped_main, dict): self.equipped_main = self.dict_to_item(equipped_main) \
equipped_main = self.dict_to_item(equipped_main) if isinstance(equipped_main, dict) else equipped_main
if isinstance(equipped_armor, dict): self.equipped_armor = self.dict_to_item(equipped_armor) \
equipped_armor = self.dict_to_item(equipped_armor) if isinstance(equipped_armor, dict) else equipped_armor
if isinstance(equipped_secondary, dict): self.equipped_secondary = self.dict_to_item(equipped_secondary) \
equipped_secondary = self.dict_to_item(equipped_secondary) if isinstance(equipped_secondary, dict) else equipped_secondary
if isinstance(equipped_helmet, dict): self.equipped_helmet = self.dict_to_item(equipped_helmet) \
equipped_helmet = self.dict_to_item(equipped_helmet) if isinstance(equipped_helmet, dict) else equipped_helmet
self.equipped_main = equipped_main self.vision = vision
self.equipped_armor = equipped_armor
self.equipped_secondary = equipped_secondary
self.equipped_helmet = equipped_helmet
def move(self, y: int, x: int) -> None: def move(self, y: int, x: int) -> None:
""" """
@ -62,6 +59,7 @@ class Player(InventoryHolder, FightingEntity):
self.map.currenty = y self.map.currenty = y
self.map.currentx = x self.map.currentx = x
self.recalculate_paths() self.recalculate_paths()
self.map.compute_visibility(self.y, self.x, self.vision)
def level_up(self) -> None: def level_up(self) -> None:
""" """
@ -82,7 +80,7 @@ class Player(InventoryHolder, FightingEntity):
Adds some experience to the player. Adds some experience to the player.
If the required amount is reached, the player levels up. If the required amount is reached, the player levels up.
""" """
self.current_xp += int(xp*self.xp_buff) self.current_xp += int(xp * self.xp_buff)
self.level_up() self.level_up()
def remove_from_inventory(self, obj: Item) -> None: def remove_from_inventory(self, obj: Item) -> None:

View File

@ -47,6 +47,7 @@ class KeyValues(Enum):
SPACE = auto() SPACE = auto()
CHAT = auto() CHAT = auto()
WAIT = auto() WAIT = auto()
LADDER = auto()
@staticmethod @staticmethod
def translate_key(key: str, settings: Settings) -> Optional["KeyValues"]: def translate_key(key: str, settings: Settings) -> Optional["KeyValues"]:
@ -81,4 +82,6 @@ class KeyValues(Enum):
return KeyValues.CHAT return KeyValues.CHAT
elif key == settings.KEY_WAIT: elif key == settings.KEY_WAIT:
return KeyValues.WAIT return KeyValues.WAIT
elif key == settings.KEY_LADDER:
return KeyValues.LADDER
return None return None

View File

@ -3,7 +3,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, List
import curses import curses
import json import json
import os import os
@ -22,7 +22,8 @@ class Game:
""" """
The game object controls all actions in the game. The game object controls all actions in the game.
""" """
map: Map maps: List[Map]
map_index: int
player: Player player: Player
screen: Any screen: Any
# display_actions is a display interface set by the bootstrapper # display_actions is a display interface set by the bootstrapper
@ -52,7 +53,9 @@ class Game:
Creates a new game on the screen. Creates a new game on the screen.
""" """
# TODO generate a new map procedurally # TODO generate a new map procedurally
self.map = Map.load(ResourceManager.get_asset_path("example_map_2.txt")) self.maps = []
self.map_index = 0
self.map = Map.load(ResourceManager.get_asset_path("example_map.txt"))
self.map.logs = self.logs self.map.logs = self.logs
self.logs.clear() self.logs.clear()
self.player = Player() self.player = Player()
@ -61,6 +64,24 @@ class Game:
self.map.spawn_random_entities(randint(3, 10)) self.map.spawn_random_entities(randint(3, 10))
self.inventory_menu.update_player(self.player) self.inventory_menu.update_player(self.player)
@property
def map(self) -> Map:
"""
Return the current map where the user is.
"""
return self.maps[self.map_index]
@map.setter
def map(self, m: Map) -> None:
"""
Redefine the current map.
"""
if len(self.maps) == self.map_index:
# Insert new map
self.maps.append(m)
# Redefine the current map
self.maps[self.map_index] = m
def run(self, screen: Any) -> None: # pragma no cover def run(self, screen: Any) -> None: # pragma no cover
""" """
Main infinite loop. Main infinite loop.
@ -110,7 +131,7 @@ class Game:
self.state = GameMode.MAINMENU self.state = GameMode.MAINMENU
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: # noqa: C901
""" """
In play mode, arrows or zqsd move the main character. In play mode, arrows or zqsd move the main character.
""" """
@ -130,7 +151,10 @@ class Game:
self.state = GameMode.INVENTORY self.state = GameMode.INVENTORY
self.display_actions(DisplayActions.UPDATE) self.display_actions(DisplayActions.UPDATE)
elif key == KeyValues.USE and self.player.equipped_main: elif key == KeyValues.USE and self.player.equipped_main:
self.player.equipped_main.use() if self.player.equipped_main:
self.player.equipped_main.use()
if self.player.equipped_secondary:
self.player.equipped_secondary.use()
elif key == KeyValues.SPACE: elif key == KeyValues.SPACE:
self.state = GameMode.MAINMENU self.state = GameMode.MAINMENU
elif key == KeyValues.CHAT: elif key == KeyValues.CHAT:
@ -138,6 +162,54 @@ class Game:
self.waiting_for_friendly_key = True self.waiting_for_friendly_key = True
elif key == KeyValues.WAIT: elif key == KeyValues.WAIT:
self.map.tick(self.player) self.map.tick(self.player)
elif key == KeyValues.LADDER:
self.handle_ladder()
def handle_ladder(self) -> None:
"""
The player pressed the ladder key to switch map
"""
# On a ladder, we switch level
y, x = self.player.y, self.player.x
if not self.map.tiles[y][x].is_ladder():
return
# We move up on the ladder of the beginning,
# down at the end of the stage
move_down = y != self.map.start_y and x != self.map.start_x
old_map = self.map
self.map_index += 1 if move_down else -1
if self.map_index == -1:
self.map_index = 0
return
while self.map_index >= len(self.maps):
# TODO: generate a new map
self.maps.append(Map.load(ResourceManager.get_asset_path(
"example_map_2.txt")))
new_map = self.map
new_map.floor = self.map_index
old_map.remove_entity(self.player)
new_map.add_entity(self.player)
if move_down:
self.player.move(self.map.start_y, self.map.start_x)
self.logs.add_message(
_("The player climbs down to the floor {floor}.")
.format(floor=-self.map_index))
else:
# Find the ladder of the end of the game
ladder_y, ladder_x = -1, -1
for y in range(self.map.height):
for x in range(self.map.width):
if (y, x) != (self.map.start_y, self.map.start_x) \
and self.map.tiles[y][x].is_ladder():
ladder_y, ladder_x = y, x
break
self.player.move(ladder_y, ladder_x)
self.logs.add_message(
_("The player climbs up the floor {floor}.")
.format(floor=-self.map_index))
self.display_actions(DisplayActions.UPDATE)
def handle_friendly_entity_chat(self, key: KeyValues) -> None: def handle_friendly_entity_chat(self, key: KeyValues) -> None:
""" """

View File

@ -2,13 +2,11 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from enum import Enum, auto from enum import Enum, auto
from math import sqrt from math import ceil, sqrt
from random import choice, randint from random import choice, choices, randint
from typing import List, Optional, Any, Dict, Tuple from typing import List, Optional, Any, Dict, Tuple
from queue import PriorityQueue from queue import PriorityQueue
from functools import reduce from functools import reduce
from random import choice, randint, choices
from typing import List, Optional, Any
from .display.texturepack import TexturePack from .display.texturepack import TexturePack
from .translations import gettext as _ from .translations import gettext as _
@ -34,16 +32,47 @@ class Logs:
self.messages = [] self.messages = []
class Slope():
X: int
Y: int
def __init__(self, y: int, x: int) -> None:
self.Y = y
self.X = x
def compare(self, other: "Slope") -> int:
y, x = other.Y, other.X
return self.Y * x - self.X * y
def __lt__(self, other: "Slope") -> bool:
return self.compare(other) < 0
def __eq__(self, other: "Slope") -> bool:
return self.compare(other) == 0
def __gt__(self, other: "Slope") -> bool:
return self.compare(other) > 0
def __le__(self, other: "Slope") -> bool:
return self.compare(other) <= 0
def __ge__(self, other: "Slope") -> bool:
return self.compare(other) >= 0
class Map: class Map:
""" """
The Map object represents a with its width, height The Map object represents a with its width, height
and tiles, that have their custom properties. and tiles, that have their custom properties.
""" """
floor: int
width: int width: int
height: int height: int
start_y: int start_y: int
start_x: int start_x: int
tiles: List[List["Tile"]] tiles: List[List["Tile"]]
visibility: List[List[bool]]
seen_tiles: List[List[bool]]
entities: List["Entity"] entities: List["Entity"]
logs: Logs logs: Logs
# coordinates of the point that should be # coordinates of the point that should be
@ -53,11 +82,16 @@ class Map:
def __init__(self, width: int, height: int, tiles: list, def __init__(self, width: int, height: int, tiles: list,
start_y: int, start_x: int): start_y: int, start_x: int):
self.floor = 0
self.width = width self.width = width
self.height = height self.height = height
self.start_y = start_y self.start_y = start_y
self.start_x = start_x self.start_x = start_x
self.tiles = tiles self.tiles = tiles
self.visibility = [[False for _ in range(len(tiles[0]))]
for _ in range(len(tiles))]
self.seen_tiles = [[False for _ in range(len(tiles[0]))]
for _ in range(len(tiles))]
self.entities = [] self.entities = []
self.logs = Logs() self.logs = Logs()
@ -147,18 +181,138 @@ class Map:
""" """
Puts randomly {count} entities on the map, only on empty ground tiles. Puts randomly {count} entities on the map, only on empty ground tiles.
""" """
for ignored in range(count): for _ignored in range(count):
y, x = 0, 0 y, x = 0, 0
while True: while True:
y, x = randint(0, self.height - 1), randint(0, self.width - 1) y, x = randint(0, self.height - 1), randint(0, self.width - 1)
tile = self.tiles[y][x] tile = self.tiles[y][x]
if tile.can_walk(): if tile.can_walk():
break break
entity = choices(Entity.get_all_entity_classes(),\ entity = choices(Entity.get_all_entity_classes(),
weights = Entity.get_weights(), k=1)[0]() weights=Entity.get_weights(), k=1)[0]()
entity.move(y, x) entity.move(y, x)
self.add_entity(entity) self.add_entity(entity)
def compute_visibility(self, y: int, x: int, max_range: int) -> None:
"""
Sets the visible tiles to be the ones visible by an entity at point
(y, x), using a twaked shadow casting algorithm
"""
for line in self.visibility:
for i in range(len(line)):
line[i] = False
self.set_visible(0, 0, 0, (y, x))
for octant in range(8):
self.compute_visibility_octant(octant, (y, x), max_range, 1,
Slope(1, 1), Slope(0, 1))
def crop_top_visibility(self, octant: int, origin: Tuple[int, int],
x: int, top: Slope) -> int:
if top.X == 1:
top_y = x
else:
top_y = ceil(((x * 2 - 1) * top.Y + top.X) / (top.X * 2))
if self.is_wall(top_y, x, octant, origin):
top_y += top >= Slope(top_y * 2 + 1, x * 2) and not \
self.is_wall(top_y + 1, x, octant, origin)
else:
ax = x * 2
ax += self.is_wall(top_y + 1, x + 1, octant, origin)
top_y += top > Slope(top_y * 2 + 1, ax)
return top_y
def crop_bottom_visibility(self, octant: int, origin: Tuple[int, int],
x: int, bottom: Slope) -> int:
if bottom.Y == 0:
bottom_y = 0
else:
bottom_y = ceil(((x * 2 - 1) * bottom.Y + bottom.X)
/ (bottom.X * 2))
bottom_y += bottom >= Slope(bottom_y * 2 + 1, x * 2) and \
self.is_wall(bottom_y, x, octant, origin) and \
not self.is_wall(bottom_y + 1, x, octant, origin)
return bottom_y
def compute_visibility_octant(self, octant: int, origin: Tuple[int, int],
max_range: int, distance: int, top: Slope,
bottom: Slope) -> None:
for x in range(distance, max_range + 1):
top_y = self.crop_top_visibility(octant, origin, x, top)
bottom_y = self.crop_bottom_visibility(octant, origin, x, bottom)
was_opaque = -1
for y in range(top_y, bottom_y - 1, -1):
if x + y > max_range:
continue
is_opaque = self.is_wall(y, x, octant, origin)
is_visible = is_opaque\
or ((y != top_y or top > Slope(y * 4 - 1, x * 4 + 1))
and (y != bottom_y
or bottom < Slope(y * 4 + 1, x * 4 - 1)))
# is_visible = is_opaque\
# or ((y != top_y or top >= Slope(y, x))
# and (y != bottom_y or bottom <= Slope(y, x)))
if is_visible:
self.set_visible(y, x, octant, origin)
if x == max_range:
continue
if is_opaque and was_opaque == 0:
nx, ny = x * 2, y * 2 + 1
nx -= self.is_wall(y + 1, x, octant, origin)
if top > Slope(ny, nx):
if y == bottom_y:
bottom = Slope(ny, nx)
break
else:
self.compute_visibility_octant(
octant, origin, max_range, x + 1, top,
Slope(ny, nx))
elif y == bottom_y: # pragma: no cover
return
elif not is_opaque and was_opaque == 1:
nx, ny = x * 2, y * 2 + 1
nx += self.is_wall(y + 1, x + 1, octant, origin)
if bottom >= Slope(ny, nx): # pragma: no cover
return
top = Slope(ny, nx)
was_opaque = is_opaque
if was_opaque != 0:
break
@staticmethod
def translate_coord(y: int, x: int, octant: int,
origin: Tuple[int, int]) -> Tuple[int, int]:
ny, nx = origin
if octant == 0:
return ny - y, nx + x
elif octant == 1:
return ny - x, nx + y
elif octant == 2:
return ny - x, nx - y
elif octant == 3:
return ny - y, nx - x
elif octant == 4:
return ny + y, nx - x
elif octant == 5:
return ny + x, nx - y
elif octant == 6:
return ny + x, nx + y
elif octant == 7:
return ny + y, nx + x
def is_wall(self, y: int, x: int, octant: int,
origin: Tuple[int, int]) -> bool:
y, x = self.translate_coord(y, x, octant, origin)
return 0 <= y < len(self.tiles) and 0 <= x < len(self.tiles[0]) and \
self.tiles[y][x].is_wall()
def set_visible(self, y: int, x: int, octant: int,
origin: Tuple[int, int]) -> None:
y, x = self.translate_coord(y, x, octant, origin)
if 0 <= y < len(self.tiles) and 0 <= x < len(self.tiles[0]):
self.visibility[y][x] = True
self.seen_tiles[y][x] = True
def tick(self, p: Any) -> None: def tick(self, p: Any) -> None:
""" """
Triggers all entity events. Triggers all entity events.
@ -210,6 +364,7 @@ class Tile(Enum):
EMPTY = auto() EMPTY = auto()
WALL = auto() WALL = auto()
FLOOR = auto() FLOOR = auto()
LADDER = auto()
@staticmethod @staticmethod
def from_ascii_char(ch: str) -> "Tile": def from_ascii_char(ch: str) -> "Tile":
@ -226,7 +381,25 @@ class Tile(Enum):
Translates a Tile to the corresponding character according Translates a Tile to the corresponding character according
to the texture pack. to the texture pack.
""" """
return getattr(pack, self.name) val = getattr(pack, self.name)
return val[0] if isinstance(val, tuple) else val
def visible_color(self, pack: TexturePack) -> Tuple[int, int]:
"""
Retrieve the tuple (fg_color, bg_color) of the current Tile
if it is visible.
"""
val = getattr(pack, self.name)
return (val[2], val[4]) if isinstance(val, tuple) else \
(pack.tile_fg_visible_color, pack.tile_bg_color)
def hidden_color(self, pack: TexturePack) -> Tuple[int, int]:
"""
Retrieve the tuple (fg_color, bg_color) of the current Tile.
"""
val = getattr(pack, self.name)
return (val[1], val[3]) if isinstance(val, tuple) else \
(pack.tile_fg_color, pack.tile_bg_color)
def is_wall(self) -> bool: def is_wall(self) -> bool:
""" """
@ -234,6 +407,12 @@ class Tile(Enum):
""" """
return self == Tile.WALL return self == Tile.WALL
def is_ladder(self) -> bool:
"""
Is this Tile a ladder?
"""
return self == Tile.LADDER
def can_walk(self) -> bool: def can_walk(self) -> bool:
""" """
Checks if an entity (player or not) can move in this tile. Checks if an entity (player or not) can move in this tile.
@ -448,7 +627,7 @@ class Entity:
from squirrelbattle.entities.monsters import Tiger, Hedgehog, Rabbit, \ from squirrelbattle.entities.monsters import Tiger, Hedgehog, Rabbit, \
TeddyBear, GiantSeaEagle TeddyBear, GiantSeaEagle
from squirrelbattle.entities.friendly import Merchant, Sunflower, \ from squirrelbattle.entities.friendly import Merchant, Sunflower, \
Trumpet Trumpet
from squirrelbattle.entities.items import BodySnatchPotion, Bomb, \ from squirrelbattle.entities.items import BodySnatchPotion, Bomb, \
Heart, Sword, Shield, Chestplate, Helmet, RingCritical, RingXP Heart, Sword, Shield, Chestplate, Helmet, RingCritical, RingXP
return { return {
@ -525,12 +704,12 @@ class FightingEntity(Entity):
The entity deals damage to the opponent The entity deals damage to the opponent
based on their respective stats. based on their respective stats.
""" """
diceroll = randint(0, 100) diceroll = randint(1, 100)
damage = self.strength damage = self.strength
string = " " string = " "
if diceroll <= self.critical: # It is a critical hit if diceroll <= self.critical: # It is a critical hit
damage *= 4 damage *= 4
string = _(" It's a critical hit! ") string = " " + _("It's a critical hit!") + " "
return _("{name} hits {opponent}.")\ return _("{name} hits {opponent}.")\
.format(name=_(self.translated_name.capitalize()), .format(name=_(self.translated_name.capitalize()),
opponent=_(opponent.translated_name)) + string + \ opponent=_(opponent.translated_name)) + string + \

View File

@ -8,7 +8,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-12 18:02+0100\n" "POT-Creation-Date: 2021-01-08 01:57+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"
@ -17,19 +17,44 @@ 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:139 #, python-brace-format
msgid "{name} takes {amount} damage."
msgstr "{name} nimmt {amount} Schadenspunkte."
#: squirrelbattle/display/menudisplay.py:160
msgid "INVENTORY" msgid "INVENTORY"
msgstr "BESTAND" msgstr "BESTAND"
#: squirrelbattle/display/menudisplay.py:164 #: squirrelbattle/display/menudisplay.py:202
msgid "STALL" msgid "STALL"
msgstr "STAND" msgstr "STAND"
#: squirrelbattle/display/statsdisplay.py:33 #: squirrelbattle/display/statsdisplay.py:23
#: squirrelbattle/tests/translations_test.py:60
msgid "player"
msgstr "Spieler"
#: squirrelbattle/display/statsdisplay.py:35
msgid "Inventory:" msgid "Inventory:"
msgstr "Bestand:" msgstr "Bestand:"
#: squirrelbattle/display/statsdisplay.py:52 #: squirrelbattle/display/statsdisplay.py:52
msgid "Equipped main:"
msgstr ""
#: squirrelbattle/display/statsdisplay.py:56
msgid "Equipped secondary:"
msgstr ""
#: squirrelbattle/display/statsdisplay.py:61
msgid "Equipped chestplate:"
msgstr ""
#: squirrelbattle/display/statsdisplay.py:65
msgid "Equipped helmet:"
msgstr ""
#: squirrelbattle/display/statsdisplay.py:72
msgid "YOU ARE DEAD" msgid "YOU ARE DEAD"
msgstr "SIE WURDEN GESTORBEN" msgstr "SIE WURDEN GESTORBEN"
@ -49,20 +74,30 @@ 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:151 #: squirrelbattle/entities/items.py:163
msgid "Bomb is exploding." msgid "Bomb is exploding."
msgstr "Die Bombe explodiert." msgstr "Die Bombe explodiert."
#: squirrelbattle/entities/items.py:248 #: squirrelbattle/entities/items.py:344
#, 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:205 squirrelbattle/tests/game_test.py:573 #: squirrelbattle/game.py:182
#, python-brace-format
msgid "The player climbs down to the floor {floor}."
msgstr "Der Spieler klettert auf dem Stock {floor} hinunter."
#: squirrelbattle/game.py:195
#, python-brace-format
msgid "The player climbs up the floor {floor}."
msgstr "Der Spieler klettert auf dem Stock {floor} hinoben."
#: squirrelbattle/game.py:285 squirrelbattle/tests/game_test.py:592
msgid "The buyer does not have enough money" msgid "The buyer does not have enough money"
msgstr "Der Kaufer hat nicht genug Geld" msgstr "Der Kaufer hat nicht genug Geld"
#: squirrelbattle/game.py:249 #: squirrelbattle/game.py:328
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."
@ -70,7 +105,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:257 #: squirrelbattle/game.py:336
msgid "" msgid ""
"No player was found on this map!\n" "No player was found on this map!\n"
"Maybe you died?" "Maybe you died?"
@ -78,7 +113,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:277 #: squirrelbattle/game.py:356
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."
@ -86,22 +121,26 @@ 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:429 #: squirrelbattle/interfaces.py:452
msgid "It's a critical hit!"
msgstr ""
#: squirrelbattle/interfaces.py:453
#, 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:441 #: squirrelbattle/interfaces.py:465
#, python-brace-format #, python-brace-format
msgid "{name} takes {amount} damage." msgid "{name} takes {damage} damage."
msgstr "{name} nimmt {amount} Schadenspunkte." msgstr ""
#: squirrelbattle/interfaces.py:443 #: squirrelbattle/interfaces.py:467
#, python-brace-format #, python-brace-format
msgid "{name} dies." msgid "{name} dies."
msgstr "{name} stirbt." msgstr "{name} stirbt."
#: squirrelbattle/interfaces.py:477 #: squirrelbattle/interfaces.py:501
#, python-brace-format #, python-brace-format
msgid "{entity} said: {message}" msgid "{entity} said: {message}"
msgstr "{entity} hat gesagt: {message}" msgstr "{entity} hat gesagt: {message}"
@ -110,8 +149,8 @@ msgstr "{entity} hat gesagt: {message}"
msgid "Back" msgid "Back"
msgstr "Zurück" msgstr "Zurück"
#: squirrelbattle/tests/game_test.py:344 squirrelbattle/tests/game_test.py:347 #: squirrelbattle/tests/game_test.py:358 squirrelbattle/tests/game_test.py:361
#: squirrelbattle/tests/game_test.py:350 squirrelbattle/tests/game_test.py:353 #: squirrelbattle/tests/game_test.py:364 squirrelbattle/tests/game_test.py:367
#: squirrelbattle/tests/translations_test.py:16 #: squirrelbattle/tests/translations_test.py:16
msgid "New game" msgid "New game"
msgstr "Neu Spiel" msgstr "Neu Spiel"
@ -197,57 +236,57 @@ msgid "Key used to wait"
msgstr "Wartentaste" msgstr "Wartentaste"
#: squirrelbattle/tests/translations_test.py:56 #: squirrelbattle/tests/translations_test.py:56
msgid "Key used to use ladders"
msgstr "Leitertaste"
#: squirrelbattle/tests/translations_test.py:58
msgid "Texture pack" msgid "Texture pack"
msgstr "Textur-Packung" msgstr "Textur-Packung"
#: squirrelbattle/tests/translations_test.py:57 #: squirrelbattle/tests/translations_test.py:59
msgid "Language" msgid "Language"
msgstr "Sprache" msgstr "Sprache"
#: squirrelbattle/tests/translations_test.py:60 #: squirrelbattle/tests/translations_test.py:64
msgid "player"
msgstr "Spieler"
#: squirrelbattle/tests/translations_test.py:62
msgid "hedgehog" msgid "hedgehog"
msgstr "Igel" msgstr "Igel"
#: squirrelbattle/tests/translations_test.py:63 #: squirrelbattle/tests/translations_test.py:65
msgid "merchant" msgid "merchant"
msgstr "Kaufmann" msgstr "Kaufmann"
#: squirrelbattle/tests/translations_test.py:64 #: squirrelbattle/tests/translations_test.py:66
msgid "rabbit" msgid "rabbit"
msgstr "Kanninchen" msgstr "Kanninchen"
#: squirrelbattle/tests/translations_test.py:65 #: squirrelbattle/tests/translations_test.py:67
msgid "sunflower" msgid "sunflower"
msgstr "Sonnenblume" msgstr "Sonnenblume"
#: squirrelbattle/tests/translations_test.py:66 #: squirrelbattle/tests/translations_test.py:68
msgid "teddy bear" msgid "teddy bear"
msgstr "Teddybär" msgstr "Teddybär"
#: squirrelbattle/tests/translations_test.py:67 #: squirrelbattle/tests/translations_test.py:69
msgid "tiger" msgid "tiger"
msgstr "Tiger" msgstr "Tiger"
#: squirrelbattle/tests/translations_test.py:69 #: squirrelbattle/tests/translations_test.py:71
msgid "body snatch potion" msgid "body snatch potion"
msgstr "Leichenfleddererzaubertrank" msgstr "Leichenfleddererzaubertrank"
#: squirrelbattle/tests/translations_test.py:70 #: squirrelbattle/tests/translations_test.py:72
msgid "bomb" msgid "bomb"
msgstr "Bombe" msgstr "Bombe"
#: squirrelbattle/tests/translations_test.py:71 #: squirrelbattle/tests/translations_test.py:73
msgid "explosion" msgid "explosion"
msgstr "Explosion" msgstr "Explosion"
#: squirrelbattle/tests/translations_test.py:72 #: squirrelbattle/tests/translations_test.py:74
msgid "heart" msgid "heart"
msgstr "Herz" msgstr "Herz"
#: squirrelbattle/tests/translations_test.py:73 #: squirrelbattle/tests/translations_test.py:75
msgid "sword" msgid "sword"
msgstr "schwert" msgstr "schwert"

View File

@ -8,7 +8,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-12 18:02+0100\n" "POT-Creation-Date: 2021-01-06 15:19+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"
@ -17,19 +17,44 @@ 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:139 #, python-brace-format
msgid "{name} takes {amount} damage."
msgstr "{name} recibe {amount} daño."
#: squirrelbattle/display/menudisplay.py:160
msgid "INVENTORY" msgid "INVENTORY"
msgstr "INVENTORIO" msgstr "INVENTORIO"
#: squirrelbattle/display/menudisplay.py:164 #: squirrelbattle/display/menudisplay.py:202
msgid "STALL" msgid "STALL"
msgstr "PUESTO" msgstr "PUESTO"
#: squirrelbattle/display/statsdisplay.py:33 #: squirrelbattle/display/statsdisplay.py:23
#: squirrelbattle/tests/translations_test.py:60
msgid "player"
msgstr "jugador"
#: squirrelbattle/display/statsdisplay.py:35
msgid "Inventory:" msgid "Inventory:"
msgstr "Inventorio :" msgstr "Inventorio :"
#: squirrelbattle/display/statsdisplay.py:52 #: squirrelbattle/display/statsdisplay.py:52
msgid "Equipped main:"
msgstr ""
#: squirrelbattle/display/statsdisplay.py:56
msgid "Equipped secondary:"
msgstr ""
#: squirrelbattle/display/statsdisplay.py:61
msgid "Equipped chestplate:"
msgstr ""
#: squirrelbattle/display/statsdisplay.py:65
msgid "Equipped helmet:"
msgstr ""
#: squirrelbattle/display/statsdisplay.py:72
msgid "YOU ARE DEAD" msgid "YOU ARE DEAD"
msgstr "ERES MUERTO" msgstr "ERES MUERTO"
@ -48,20 +73,30 @@ msgstr "El sol está caliente hoy"
#. 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:151 #: squirrelbattle/entities/items.py:163
msgid "Bomb is exploding." msgid "Bomb is exploding."
msgstr "La bomba está explotando." msgstr "La bomba está explotando."
#: squirrelbattle/entities/items.py:248 #: squirrelbattle/entities/items.py:344
#, python-brace-format #, python-brace-format
msgid "{player} exchanged its body with {entity}." msgid "{player} exchanged its body with {entity}."
msgstr "{player} intercambió su cuerpo con {entity}." msgstr "{player} intercambió su cuerpo con {entity}."
#: squirrelbattle/game.py:205 squirrelbattle/tests/game_test.py:573 #: squirrelbattle/game.py:182
#, python-brace-format
msgid "The player climbs down to the floor {floor}."
msgstr ""
#: squirrelbattle/game.py:195
#, python-brace-format
msgid "The player climbs up the floor {floor}."
msgstr ""
#: squirrelbattle/game.py:285 squirrelbattle/tests/game_test.py:592
msgid "The buyer does not have enough money" msgid "The buyer does not have enough money"
msgstr "El comprador no tiene suficiente dinero" msgstr "El comprador no tiene suficiente dinero"
#: squirrelbattle/game.py:249 #: squirrelbattle/game.py:328
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."
@ -69,7 +104,7 @@ msgstr ""
"Algunas claves faltan en su archivo de guarda.\n" "Algunas claves faltan en su archivo de guarda.\n"
"Su guarda parece a ser corruptido. Fue eliminado." "Su guarda parece a ser corruptido. Fue eliminado."
#: squirrelbattle/game.py:257 #: squirrelbattle/game.py:336
msgid "" msgid ""
"No player was found on this map!\n" "No player was found on this map!\n"
"Maybe you died?" "Maybe you died?"
@ -77,7 +112,7 @@ msgstr ""
"No jugador encontrado sobre la carta !\n" "No jugador encontrado sobre la carta !\n"
"¿ Quizas murió ?" "¿ Quizas murió ?"
#: squirrelbattle/game.py:277 #: squirrelbattle/game.py:356
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."
@ -85,22 +120,26 @@ msgstr ""
"El JSON archivo no es correcto.\n" "El JSON archivo no es correcto.\n"
"Su guarda parece corrupta. Fue eliminada." "Su guarda parece corrupta. Fue eliminada."
#: squirrelbattle/interfaces.py:429 #: squirrelbattle/interfaces.py:452
msgid "It's a critical hit!"
msgstr ""
#: squirrelbattle/interfaces.py:453
#, python-brace-format #, python-brace-format
msgid "{name} hits {opponent}." msgid "{name} hits {opponent}."
msgstr "{name} golpea a {opponent}." msgstr "{name} golpea a {opponent}."
#: squirrelbattle/interfaces.py:441 #: squirrelbattle/interfaces.py:465
#, python-brace-format #, python-brace-format
msgid "{name} takes {amount} damage." msgid "{name} takes {damage} damage."
msgstr "{name} recibe {amount} daño." msgstr ""
#: squirrelbattle/interfaces.py:443 #: squirrelbattle/interfaces.py:467
#, python-brace-format #, python-brace-format
msgid "{name} dies." msgid "{name} dies."
msgstr "{name} se muere." msgstr "{name} se muere."
#: squirrelbattle/interfaces.py:477 #: squirrelbattle/interfaces.py:501
#, python-brace-format #, python-brace-format
msgid "{entity} said: {message}" msgid "{entity} said: {message}"
msgstr "{entity} dijo : {message}" msgstr "{entity} dijo : {message}"
@ -109,8 +148,8 @@ msgstr "{entity} dijo : {message}"
msgid "Back" msgid "Back"
msgstr "Volver" msgstr "Volver"
#: squirrelbattle/tests/game_test.py:344 squirrelbattle/tests/game_test.py:347 #: squirrelbattle/tests/game_test.py:358 squirrelbattle/tests/game_test.py:361
#: squirrelbattle/tests/game_test.py:350 squirrelbattle/tests/game_test.py:353 #: squirrelbattle/tests/game_test.py:364 squirrelbattle/tests/game_test.py:367
#: squirrelbattle/tests/translations_test.py:16 #: squirrelbattle/tests/translations_test.py:16
msgid "New game" msgid "New game"
msgstr "Nuevo partido" msgstr "Nuevo partido"
@ -196,57 +235,57 @@ msgid "Key used to wait"
msgstr "Tecla para espera" msgstr "Tecla para espera"
#: squirrelbattle/tests/translations_test.py:56 #: squirrelbattle/tests/translations_test.py:56
msgid "Key used to use ladders"
msgstr "Tecla para el uso de las escaleras"
#: squirrelbattle/tests/translations_test.py:58
msgid "Texture pack" msgid "Texture pack"
msgstr "Paquete de texturas" msgstr "Paquete de texturas"
#: squirrelbattle/tests/translations_test.py:57 #: squirrelbattle/tests/translations_test.py:59
msgid "Language" msgid "Language"
msgstr "Languaje" msgstr "Languaje"
#: squirrelbattle/tests/translations_test.py:60 #: squirrelbattle/tests/translations_test.py:64
msgid "player"
msgstr "jugador"
#: squirrelbattle/tests/translations_test.py:62
msgid "hedgehog" msgid "hedgehog"
msgstr "erizo" msgstr "erizo"
#: squirrelbattle/tests/translations_test.py:63 #: squirrelbattle/tests/translations_test.py:65
msgid "merchant" msgid "merchant"
msgstr "comerciante" msgstr "comerciante"
#: squirrelbattle/tests/translations_test.py:64 #: squirrelbattle/tests/translations_test.py:66
msgid "rabbit" msgid "rabbit"
msgstr "conejo" msgstr "conejo"
#: squirrelbattle/tests/translations_test.py:65 #: squirrelbattle/tests/translations_test.py:67
msgid "sunflower" msgid "sunflower"
msgstr "girasol" msgstr "girasol"
#: squirrelbattle/tests/translations_test.py:66 #: squirrelbattle/tests/translations_test.py:68
msgid "teddy bear" msgid "teddy bear"
msgstr "osito de peluche" msgstr "osito de peluche"
#: squirrelbattle/tests/translations_test.py:67 #: squirrelbattle/tests/translations_test.py:69
msgid "tiger" msgid "tiger"
msgstr "tigre" msgstr "tigre"
#: squirrelbattle/tests/translations_test.py:69 #: squirrelbattle/tests/translations_test.py:71
msgid "body snatch potion" msgid "body snatch potion"
msgstr "poción de intercambio" msgstr "poción de intercambio"
#: squirrelbattle/tests/translations_test.py:70 #: squirrelbattle/tests/translations_test.py:72
msgid "bomb" msgid "bomb"
msgstr "bomba" msgstr "bomba"
#: squirrelbattle/tests/translations_test.py:71 #: squirrelbattle/tests/translations_test.py:73
msgid "explosion" msgid "explosion"
msgstr "explosión" msgstr "explosión"
#: squirrelbattle/tests/translations_test.py:72 #: squirrelbattle/tests/translations_test.py:74
msgid "heart" msgid "heart"
msgstr "corazón" msgstr "corazón"
#: squirrelbattle/tests/translations_test.py:73 #: squirrelbattle/tests/translations_test.py:75
msgid "sword" msgid "sword"
msgstr "espada" msgstr "espada"

View File

@ -8,7 +8,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-12 18:02+0100\n" "POT-Creation-Date: 2021-01-06 15:19+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"
@ -17,19 +17,44 @@ 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:139 #, python-brace-format
msgid "{name} takes {amount} damage."
msgstr "{name} prend {amount} points de dégât."
#: squirrelbattle/display/menudisplay.py:160
msgid "INVENTORY" msgid "INVENTORY"
msgstr "INVENTAIRE" msgstr "INVENTAIRE"
#: squirrelbattle/display/menudisplay.py:164 #: squirrelbattle/display/menudisplay.py:202
msgid "STALL" msgid "STALL"
msgstr "STAND" msgstr "STAND"
#: squirrelbattle/display/statsdisplay.py:33 #: squirrelbattle/display/statsdisplay.py:23
#: squirrelbattle/tests/translations_test.py:60
msgid "player"
msgstr "joueur"
#: squirrelbattle/display/statsdisplay.py:35
msgid "Inventory:" msgid "Inventory:"
msgstr "Inventaire :" msgstr "Inventaire :"
#: squirrelbattle/display/statsdisplay.py:52 #: squirrelbattle/display/statsdisplay.py:52
msgid "Equipped main:"
msgstr "Équipement principal :"
#: squirrelbattle/display/statsdisplay.py:56
msgid "Equipped secondary:"
msgstr "Équipement secondaire :"
#: squirrelbattle/display/statsdisplay.py:61
msgid "Equipped chestplate:"
msgstr "Plastron équipé :"
#: squirrelbattle/display/statsdisplay.py:65
msgid "Equipped helmet:"
msgstr "Casque équipé :"
#: squirrelbattle/display/statsdisplay.py:72
msgid "YOU ARE DEAD" msgid "YOU ARE DEAD"
msgstr "VOUS ÊTES MORT" msgstr "VOUS ÊTES MORT"
@ -49,20 +74,30 @@ 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:151 #: squirrelbattle/entities/items.py:163
msgid "Bomb is exploding." msgid "Bomb is exploding."
msgstr "La bombe explose." msgstr "La bombe explose."
#: squirrelbattle/entities/items.py:248 #: squirrelbattle/entities/items.py:344
#, 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:205 squirrelbattle/tests/game_test.py:573 #: squirrelbattle/game.py:182
#, python-brace-format
msgid "The player climbs down to the floor {floor}."
msgstr "Le joueur descend à l'étage {floor}."
#: squirrelbattle/game.py:195
#, python-brace-format
msgid "The player climbs up the floor {floor}."
msgstr "Le joueur monte à l'étage {floor}."
#: squirrelbattle/game.py:285 squirrelbattle/tests/game_test.py:592
msgid "The buyer does not have enough money" msgid "The buyer does not have enough money"
msgstr "L'acheteur n'a pas assez d'argent" msgstr "L'acheteur n'a pas assez d'argent"
#: squirrelbattle/game.py:249 #: squirrelbattle/game.py:328
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."
@ -70,7 +105,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:257 #: squirrelbattle/game.py:336
msgid "" msgid ""
"No player was found on this map!\n" "No player was found on this map!\n"
"Maybe you died?" "Maybe you died?"
@ -78,7 +113,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:277 #: squirrelbattle/game.py:356
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."
@ -86,22 +121,26 @@ 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:429 #: squirrelbattle/interfaces.py:452
msgid "It's a critical hit!"
msgstr "C'est un coup critique !"
#: squirrelbattle/interfaces.py:453
#, 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:441 #: squirrelbattle/interfaces.py:465
#, python-brace-format #, python-brace-format
msgid "{name} takes {amount} damage." msgid "{name} takes {damage} damage."
msgstr "{name} prend {amount} points de dégât." msgstr "{name} prend {damage} dégâts."
#: squirrelbattle/interfaces.py:443 #: squirrelbattle/interfaces.py:467
#, python-brace-format #, python-brace-format
msgid "{name} dies." msgid "{name} dies."
msgstr "{name} meurt." msgstr "{name} meurt."
#: squirrelbattle/interfaces.py:477 #: squirrelbattle/interfaces.py:501
#, python-brace-format #, python-brace-format
msgid "{entity} said: {message}" msgid "{entity} said: {message}"
msgstr "{entity} a dit : {message}" msgstr "{entity} a dit : {message}"
@ -110,8 +149,8 @@ msgstr "{entity} a dit : {message}"
msgid "Back" msgid "Back"
msgstr "Retour" msgstr "Retour"
#: squirrelbattle/tests/game_test.py:344 squirrelbattle/tests/game_test.py:347 #: squirrelbattle/tests/game_test.py:358 squirrelbattle/tests/game_test.py:361
#: squirrelbattle/tests/game_test.py:350 squirrelbattle/tests/game_test.py:353 #: squirrelbattle/tests/game_test.py:364 squirrelbattle/tests/game_test.py:367
#: squirrelbattle/tests/translations_test.py:16 #: squirrelbattle/tests/translations_test.py:16
msgid "New game" msgid "New game"
msgstr "Nouvelle partie" msgstr "Nouvelle partie"
@ -197,57 +236,57 @@ msgid "Key used to wait"
msgstr "Touche pour attendre" msgstr "Touche pour attendre"
#: squirrelbattle/tests/translations_test.py:56 #: squirrelbattle/tests/translations_test.py:56
msgid "Key used to use ladders"
msgstr "Touche pour utiliser les échelles"
#: squirrelbattle/tests/translations_test.py:58
msgid "Texture pack" msgid "Texture pack"
msgstr "Pack de textures" msgstr "Pack de textures"
#: squirrelbattle/tests/translations_test.py:57 #: squirrelbattle/tests/translations_test.py:59
msgid "Language" msgid "Language"
msgstr "Langue" msgstr "Langue"
#: squirrelbattle/tests/translations_test.py:60 #: squirrelbattle/tests/translations_test.py:64
msgid "player"
msgstr "joueur"
#: squirrelbattle/tests/translations_test.py:62
msgid "hedgehog" msgid "hedgehog"
msgstr "hérisson" msgstr "hérisson"
#: squirrelbattle/tests/translations_test.py:63 #: squirrelbattle/tests/translations_test.py:65
msgid "merchant" msgid "merchant"
msgstr "marchand" msgstr "marchand"
#: squirrelbattle/tests/translations_test.py:64 #: squirrelbattle/tests/translations_test.py:66
msgid "rabbit" msgid "rabbit"
msgstr "lapin" msgstr "lapin"
#: squirrelbattle/tests/translations_test.py:65 #: squirrelbattle/tests/translations_test.py:67
msgid "sunflower" msgid "sunflower"
msgstr "tournesol" msgstr "tournesol"
#: squirrelbattle/tests/translations_test.py:66 #: squirrelbattle/tests/translations_test.py:68
msgid "teddy bear" msgid "teddy bear"
msgstr "nounours" msgstr "nounours"
#: squirrelbattle/tests/translations_test.py:67 #: squirrelbattle/tests/translations_test.py:69
msgid "tiger" msgid "tiger"
msgstr "tigre" msgstr "tigre"
#: squirrelbattle/tests/translations_test.py:69 #: squirrelbattle/tests/translations_test.py:71
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:70 #: squirrelbattle/tests/translations_test.py:72
msgid "bomb" msgid "bomb"
msgstr "bombe" msgstr "bombe"
#: squirrelbattle/tests/translations_test.py:71 #: squirrelbattle/tests/translations_test.py:73
msgid "explosion" msgid "explosion"
msgstr "" msgstr "explosion"
#: squirrelbattle/tests/translations_test.py:72 #: squirrelbattle/tests/translations_test.py:74
msgid "heart" msgid "heart"
msgstr "cœur" msgstr "cœur"
#: squirrelbattle/tests/translations_test.py:73 #: squirrelbattle/tests/translations_test.py:75
msgid "sword" msgid "sword"
msgstr "épée" msgstr "épée"

View File

@ -34,6 +34,7 @@ class Settings:
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.KEY_CHAT = ['t', 'Key used to talk to a friendly entity']
self.KEY_WAIT = ['w', 'Key used to wait'] self.KEY_WAIT = ['w', 'Key used to wait']
self.KEY_LADDER = ['<', 'Key used to use ladders']
self.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

@ -78,6 +78,7 @@ class TestEntities(unittest.TestCase):
{self.player.name.capitalize()} takes {entity.strength} damage.") {self.player.name.capitalize()} takes {entity.strength} damage.")
# Fight the rabbit # Fight the rabbit
self.player.critical = 0
old_health = entity.health old_health = entity.health
self.player.move_down() self.player.move_down()
self.assertEqual(entity.health, old_health - self.player.strength) self.assertEqual(entity.health, old_health - self.player.strength)
@ -177,7 +178,7 @@ class TestEntities(unittest.TestCase):
self.assertEqual(item.y, 42) self.assertEqual(item.y, 42)
self.assertEqual(item.x, 42) self.assertEqual(item.x, 42)
# Wait for the explosion # Wait for the explosion
for ignored in range(5): for _ignored in range(5):
item.act(self.map) item.act(self.map)
self.assertTrue(hedgehog.dead) self.assertTrue(hedgehog.dead)
self.assertTrue(teddy_bear.dead) self.assertTrue(teddy_bear.dead)

View File

@ -2,13 +2,16 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
import os import os
import random
import unittest import unittest
from ..bootstrap import Bootstrap from ..bootstrap import Bootstrap
from ..display.display import Display from ..display.display import Display
from ..display.display_manager import DisplayManager from ..display.display_manager import DisplayManager
from ..entities.friendly import Merchant, Sunflower from ..entities.friendly import Merchant, Sunflower
from ..entities.items import Bomb, Heart, Sword, Explosion from ..entities.items import Bomb, Heart, Sword, Explosion, Shield, Helmet, \
Chestplate, RingCritical
from ..entities.monsters import GiantSeaEagle
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
@ -46,11 +49,6 @@ class TestGame(unittest.TestCase):
bomb.hold(self.game.player) bomb.hold(self.game.player)
sword.hold(self.game.player) sword.hold(self.game.player)
for entity in self.game.map.entities:
# trumpets change order when they are loaded, this breaks the test.
if entity.name == 'trumpet':
self.game.map.remove_entity(entity)
# Ensure that merchants can be saved # Ensure that merchants can be saved
merchant = Merchant() merchant = Merchant()
merchant.move(3, 6) merchant.move(3, 6)
@ -153,6 +151,9 @@ class TestGame(unittest.TestCase):
self.assertEqual(KeyValues.translate_key( self.assertEqual(KeyValues.translate_key(
self.game.settings.KEY_WAIT, self.game.settings), self.game.settings.KEY_WAIT, self.game.settings),
KeyValues.WAIT) KeyValues.WAIT)
self.assertEqual(KeyValues.translate_key(
self.game.settings.KEY_LADDER, self.game.settings),
KeyValues.LADDER)
self.assertEqual(KeyValues.translate_key(' ', self.game.settings), self.assertEqual(KeyValues.translate_key(' ', self.game.settings),
KeyValues.SPACE) KeyValues.SPACE)
self.assertEqual(KeyValues.translate_key('plop', self.game.settings), self.assertEqual(KeyValues.translate_key('plop', self.game.settings),
@ -343,7 +344,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(11): for ignored in range(12):
self.game.handle_key_pressed(KeyValues.DOWN) self.game.handle_key_pressed(KeyValues.DOWN)
# Change texture pack # Change texture pack
@ -615,6 +616,131 @@ class TestGame(unittest.TestCase):
self.game.handle_key_pressed(KeyValues.SPACE) self.game.handle_key_pressed(KeyValues.SPACE)
self.assertEqual(self.game.state, GameMode.PLAY) self.assertEqual(self.game.state, GameMode.PLAY)
def test_equipment(self) -> None:
"""
Ensure that equipment is working.
"""
self.game.state = GameMode.INVENTORY
# sword goes into the main equipment slot
sword = Sword()
sword.hold(self.game.player)
self.game.handle_key_pressed(KeyValues.EQUIP)
self.assertEqual(self.game.player.equipped_main, sword)
self.assertFalse(self.game.player.inventory)
# shield goes into the secondary equipment slot
shield = Shield()
shield.hold(self.game.player)
self.game.handle_key_pressed(KeyValues.EQUIP)
self.assertEqual(self.game.player.equipped_secondary, shield)
self.assertFalse(self.game.player.inventory)
# helmet goes into the helmet slot
helmet = Helmet()
helmet.hold(self.game.player)
self.game.handle_key_pressed(KeyValues.EQUIP)
self.assertEqual(self.game.player.equipped_helmet, helmet)
self.assertFalse(self.game.player.inventory)
# helmet goes into the armor slot
chestplate = Chestplate()
chestplate.hold(self.game.player)
self.game.handle_key_pressed(KeyValues.EQUIP)
self.assertEqual(self.game.player.equipped_armor, chestplate)
self.assertFalse(self.game.player.inventory)
# Use bomb
bomb = Bomb()
bomb.hold(self.game.player)
self.game.handle_key_pressed(KeyValues.EQUIP)
self.assertEqual(self.game.player.equipped_secondary, bomb)
self.assertIn(shield, self.game.player.inventory)
self.game.state = GameMode.PLAY
self.game.handle_key_pressed(KeyValues.USE)
self.assertIsNone(self.game.player.equipped_secondary)
self.game.state = GameMode.INVENTORY
self.game.handle_key_pressed(KeyValues.EQUIP)
self.assertEqual(self.game.player.equipped_secondary, shield)
self.assertFalse(self.game.player.inventory)
# Reequip, which is useless but covers code
sword.equip()
shield.equip()
helmet.equip()
chestplate.equip()
self.game.save_state()
# Unequip all
sword.unequip()
shield.unequip()
helmet.unequip()
chestplate.unequip()
self.assertIsNone(self.game.player.equipped_main)
self.assertIsNone(self.game.player.equipped_secondary)
self.assertIsNone(self.game.player.equipped_helmet)
self.assertIsNone(self.game.player.equipped_armor)
self.assertIn(sword, self.game.player.inventory)
self.assertIn(shield, self.game.player.inventory)
self.assertIn(helmet, self.game.player.inventory)
self.assertIn(chestplate, self.game.player.inventory)
# Test rings
self.game.player.inventory.clear()
ring = RingCritical()
ring.hold(self.game.player)
old_critical = self.game.player.critical
self.game.handle_key_pressed(KeyValues.EQUIP)
self.assertEqual(self.game.player.critical,
old_critical + ring.critical)
self.game.save_state()
ring.unequip()
def test_critical_hit(self) -> None:
"""
Ensure that critical hits are working.
"""
random.seed(2) # Next random.randint(1, 100) will output 8
self.game.player.critical = 10
sea_eagle = GiantSeaEagle()
self.game.map.add_entity(sea_eagle)
sea_eagle.move(2, 6)
old_health = sea_eagle.health
self.game.player.hit(sea_eagle)
self.assertEqual(sea_eagle.health,
old_health - self.game.player.strength * 4)
def test_ladders(self) -> None:
"""
Ensure that the player can climb on ladders.
"""
self.game.state = GameMode.PLAY
self.assertEqual(self.game.player.map.floor, 0)
self.game.handle_key_pressed(KeyValues.LADDER)
self.assertEqual(self.game.player.map.floor, 0)
# Move nowhere
self.game.player.move(10, 10)
self.game.handle_key_pressed(KeyValues.LADDER)
self.assertEqual(self.game.player.map.floor, 0)
# Move down
self.game.player.move(3, 40) # Move on a ladder
self.game.handle_key_pressed(KeyValues.LADDER)
self.assertEqual(self.game.map_index, 1)
self.assertEqual(self.game.player.map.floor, 1)
self.assertEqual(self.game.player.y, 1)
self.assertEqual(self.game.player.x, 17)
self.game.display_actions(DisplayActions.UPDATE)
# Move up
self.game.handle_key_pressed(KeyValues.LADDER)
self.assertEqual(self.game.player.map.floor, 0)
self.assertEqual(self.game.player.y, 3)
self.assertEqual(self.game.player.x, 40)
self.game.display_actions(DisplayActions.UPDATE)
def test_credits(self) -> None: def test_credits(self) -> None:
""" """
Load credits menu. Load credits menu.

View File

@ -4,7 +4,7 @@
import unittest import unittest
from squirrelbattle.display.texturepack import TexturePack from squirrelbattle.display.texturepack import TexturePack
from squirrelbattle.interfaces import Map, Tile from squirrelbattle.interfaces import Map, Tile, Slope
from squirrelbattle.resources import ResourceManager from squirrelbattle.resources import ResourceManager
@ -37,3 +37,21 @@ class TestInterfaces(unittest.TestCase):
self.assertFalse(Tile.WALL.can_walk()) self.assertFalse(Tile.WALL.can_walk())
self.assertFalse(Tile.EMPTY.can_walk()) self.assertFalse(Tile.EMPTY.can_walk())
self.assertRaises(ValueError, Tile.from_ascii_char, 'unknown') self.assertRaises(ValueError, Tile.from_ascii_char, 'unknown')
def test_slope(self) -> None:
"""
Test good behaviour of slopes (basically vectors, compared according to
the determinant)
"""
a = Slope(1, 1)
b = Slope(0, 1)
self.assertTrue(b < a)
self.assertTrue(b <= a)
self.assertTrue(a <= a)
self.assertTrue(a == a)
self.assertTrue(a > b)
self.assertTrue(a >= b)
# def test_visibility(self) -> None:
# m = Map.load(ResourceManager.get_asset_path("example_map_3.txt"))
# m.compute_visibility(1, 1, 50)

View File

@ -53,6 +53,8 @@ class TestTranslations(unittest.TestCase):
self.assertEqual(_("Key used to talk to a friendly entity"), self.assertEqual(_("Key used to talk to a friendly entity"),
"Touche pour parler à une entité pacifique") "Touche pour parler à une entité pacifique")
self.assertEqual(_("Key used to wait"), "Touche pour attendre") self.assertEqual(_("Key used to wait"), "Touche pour attendre")
self.assertEqual(_("Key used to use ladders"),
"Touche pour utiliser les échelles")
self.assertEqual(_("Texture pack"), "Pack de textures") self.assertEqual(_("Texture pack"), "Pack de textures")
self.assertEqual(_("Language"), "Langue") self.assertEqual(_("Language"), "Langue")