diff --git a/squirrelbattle/display/statsdisplay.py b/squirrelbattle/display/statsdisplay.py index 120d284..35ff92c 100644 --- a/squirrelbattle/display/statsdisplay.py +++ b/squirrelbattle/display/statsdisplay.py @@ -33,7 +33,8 @@ class StatsDisplay(Display): f"INT {self.player.intelligence}\n" \ f"CHR {self.player.charisma}\n" \ f"DEX {self.player.dexterity}\n" \ - f"CON {self.player.constitution}" + f"CON {self.player.constitution}\n" \ + f"CRI {self.player.critical}%" self.addstr(self.pad, 3, 0, string3) inventory_str = _("Inventory:") + " " @@ -49,13 +50,31 @@ class StatsDisplay(Display): if count > 1: inventory_str += f"x{count} " printed_items.append(item) - self.addstr(self.pad, 8, 0, inventory_str) + self.addstr(self.pad, 9, 0, inventory_str) - self.addstr(self.pad, 9, 0, f"{self.pack.HAZELNUT} " - f"x{self.player.hazel}") + if self.player.equipped_main: + self.addstr(self.pad, 10, 0, + _("Equipped main:") + " " + f"{self.pack[self.player.equipped_main.name.upper()]}") + if self.player.equipped_secondary: + self.addstr(self.pad, 11, 0, + _("Equipped secondary:") + " " + + self.pack[self.player.equipped_secondary + .name.upper()]) + if self.player.equipped_armor: + self.addstr(self.pad, 12, 0, + _("Equipped chestplate:") + " " + + self.pack[self.player.equipped_armor.name.upper()]) + if self.player.equipped_helmet: + self.addstr(self.pad, 13, 0, + _("Equipped helmet:") + " " + + self.pack[self.player.equipped_helmet.name.upper()]) + + self.addstr(self.pad, 14, 0, f"{self.pack.HAZELNUT} " + f"x{self.player.hazel}") if self.player.dead: - self.addstr(self.pad, 11, 0, _("YOU ARE DEAD"), curses.COLOR_RED, + self.addstr(self.pad, 15, 0, _("YOU ARE DEAD"), curses.COLOR_RED, bold=True, blink=True, standout=True) def display(self) -> None: diff --git a/squirrelbattle/display/texturepack.py b/squirrelbattle/display/texturepack.py index dae0763..9ad513d 100644 --- a/squirrelbattle/display/texturepack.py +++ b/squirrelbattle/display/texturepack.py @@ -35,6 +35,12 @@ class TexturePack: TIGER: str TRUMPET: str WALL: str + EAGLE: str + SHIELD: str + CHESTPLATE: str + HELMET: str + RING_OF_MORE_EXPERIENCE: str + RING_OF_CRITICAL_DAMAGE: str ASCII_PACK: "TexturePack" SQUIRREL_PACK: "TexturePack" @@ -66,7 +72,7 @@ TexturePack.ASCII_PACK = TexturePack( entity_bg_color=curses.COLOR_BLACK, BODY_SNATCH_POTION='S', - BOMB='o', + BOMB='ç', EMPTY=' ', EXPLOSION='%', FLOOR='.', @@ -77,12 +83,18 @@ TexturePack.ASCII_PACK = TexturePack( MERCHANT='M', PLAYER='@', RABBIT='Y', + SHIELD='D', SUNFLOWER='I', SWORD='\u2020', TEDDY_BEAR='8', TIGER='n', TRUMPET='/', WALL='#', + EAGLE='µ', + CHESTPLATE='(', + HELMET='0', + RING_OF_MORE_EXPERIENCE='o', + RING_OF_CRITICAL_DAMAGE='o', ) TexturePack.SQUIRREL_PACK = TexturePack( @@ -107,10 +119,16 @@ TexturePack.SQUIRREL_PACK = TexturePack( PLAYER='🐿️ ️', MERCHANT='🦜', RABBIT='🐇', + SHIELD='🛡️ ', SUNFLOWER='🌻', SWORD='🗡️ ', TEDDY_BEAR='🧸', TIGER='🐅', TRUMPET='🎺', WALL='🧱', + EAGLE='🦅', + CHESTPLATE='🦺', + HELMET='⛑️', + RING_OF_MORE_EXPERIENCE='💍', + RING_OF_CRITICAL_DAMAGE='💍', ) diff --git a/squirrelbattle/entities/items.py b/squirrelbattle/entities/items.py index 0661d5d..874cf45 100644 --- a/squirrelbattle/entities/items.py +++ b/squirrelbattle/entities/items.py @@ -4,7 +4,6 @@ from random import choice, randint from typing import Optional -from .player import Player from ..interfaces import Entity, FightingEntity, Map, InventoryHolder from ..translations import gettext as _ @@ -30,7 +29,7 @@ class Item(Entity): The item is dropped from the inventory onto the floor. """ if self.held: - self.held_by.inventory.remove(self) + self.held_by.remove_from_inventory(self) self.held_by.map.add_entity(self) self.move(self.held_by.y, self.held_by.x) self.held = False @@ -45,15 +44,27 @@ class Item(Entity): """ Indicates what should be done when the item is equipped. """ + # 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 hold(self, player: InventoryHolder) -> None: + def unequip(self) -> None: + """ + Indicates what should be done when the item is unequipped. + """ + self.held_by.remove_from_inventory(self) + self.held_by.add_to_inventory(self) + + def hold(self, holder: InventoryHolder) -> None: """ The item is taken from the floor and put into the inventory. """ self.held = True - self.held_by = player + self.held_by = holder self.held_by.map.remove_entity(self) - player.add_to_inventory(self) + holder.add_to_inventory(self) def save_state(self) -> dict: """ @@ -68,7 +79,8 @@ class Item(Entity): """ Returns the list of all item classes. """ - return [BodySnatchPotion, Bomb, Heart, Sword] + return [BodySnatchPotion, Bomb, Heart, Shield, Sword, + Chestplate, Helmet, RingCritical, RingXP] def be_sold(self, buyer: InventoryHolder, seller: InventoryHolder) -> bool: """ @@ -120,7 +132,7 @@ class Bomb(Item): """ damage: int = 5 exploding: bool - owner: Optional["Player"] + owner: Optional["InventoryHolder"] tick: int def __init__(self, name: str = "bomb", damage: int = 5, @@ -193,7 +205,6 @@ class Explosion(Item): """ The player can't hold an explosion. """ - pass class Weapon(Item): @@ -214,14 +225,99 @@ class Weapon(Item): d["damage"] = self.damage return d + def equip(self) -> None: + """ + When a weapon is equipped, the player gains strength. + """ + self.held_by.remove_from_inventory(self) + self.held_by.equipped_main = self + self.held_by.strength += self.damage + + def unequip(self) -> None: + """ + Remove the strength earned by the weapon. + :return: + """ + super().unequip() + self.held_by.strength -= self.damage + class Sword(Weapon): """ A basic weapon """ - def __init__(self, name: str = "sword", price: int = 20, *args, **kwargs): + def __init__(self, name: str = "sword", price: int = 20, + *args, **kwargs): super().__init__(name=name, price=price, *args, **kwargs) - self.name = name + + +class Armor(Item): + """ + Class of items that increase the player's constitution. + """ + constitution: int + + def __init__(self, constitution: int, *args, **kwargs): + super().__init__(*args, **kwargs) + self.constitution = constitution + + def equip(self) -> None: + super().equip() + self.held_by.constitution += self.constitution + + def unequip(self) -> None: + super().unequip() + self.held_by.constitution -= self.constitution + + def save_state(self) -> dict: + d = super().save_state() + d["constitution"] = self.constitution + return d + + +class Shield(Armor): + """ + Class of shield items, they can be equipped in the other hand. + """ + + def __init__(self, name: str = "shield", constitution: int = 2, + price: int = 6, *args, **kwargs): + super().__init__(name=name, constitution=constitution, price=price, + *args, **kwargs) + + +class Helmet(Armor): + """ + Class of helmet items, they can be equipped on the head. + """ + + def __init__(self, name: str = "helmet", constitution: int = 2, + price: int = 8, *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 of chestplate items, they can be equipped on the body. + """ + + def __init__(self, name: str = "chestplate", constitution: int = 4, + price: int = 15, *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): @@ -256,3 +352,79 @@ class BodySnatchPotion(Item): self.held_by.recalculate_paths() self.held_by.inventory.remove(self) + + +class Ring(Item): + """ + A class of rings that boost the player's statistics. + """ + maxhealth: int + strength: int + intelligence: int + charisma: int + dexterity: int + constitution: int + critical: int + experience: float + + def __init__(self, maxhealth: int = 0, strength: int = 0, + intelligence: int = 0, charisma: int = 0, + dexterity: int = 0, constitution: int = 0, + critical: int = 0, experience: float = 0, *args, **kwargs): + super().__init__(*args, **kwargs) + self.maxhealth = maxhealth + self.strength = strength + self.intelligence = intelligence + self.charisma = charisma + self.dexterity = dexterity + self.constitution = constitution + self.critical = critical + self.experience = experience + + def equip(self) -> None: + super().equip() + self.held_by.maxhealth += self.maxhealth + self.held_by.strength += self.strength + self.held_by.intelligence += self.intelligence + self.held_by.charisma += self.charisma + self.held_by.dexterity += self.dexterity + self.held_by.constitution += self.constitution + self.held_by.critical += self.critical + self.held_by.xp_buff += self.experience + + def unequip(self) -> None: + super().unequip() + self.held_by.maxhealth -= self.maxhealth + self.held_by.strength -= self.strength + self.held_by.intelligence -= self.intelligence + self.held_by.charisma -= self.charisma + self.held_by.dexterity -= self.dexterity + self.held_by.constitution -= self.constitution + self.held_by.critical -= self.critical + self.held_by.xp_buff -= self.experience + + def save_state(self) -> dict: + 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["critical"] = self.critical + d["experience"] = self.experience + return d + + +class RingCritical(Ring): + def __init__(self, name: str = "ring_of_critical_damage", price: int = 15, + critical: int = 20, *args, **kwargs): + super().__init__(name=name, price=price, critical=critical, + *args, **kwargs) + + +class RingXP(Ring): + def __init__(self, name: str = "ring_of_more_experience", price: int = 25, + experience: float = 2, *args, **kwargs): + super().__init__(name=name, price=price, experience=experience, + *args, **kwargs) diff --git a/squirrelbattle/entities/monsters.py b/squirrelbattle/entities/monsters.py index 27c96a6..e22aa51 100644 --- a/squirrelbattle/entities/monsters.py +++ b/squirrelbattle/entities/monsters.py @@ -94,9 +94,11 @@ class Rabbit(Monster): A rabbit monster. """ def __init__(self, name: str = "rabbit", strength: int = 1, - maxhealth: int = 15, *args, **kwargs) -> None: + maxhealth: int = 15, critical: int = 30, + *args, **kwargs) -> None: super().__init__(name=name, strength=strength, - maxhealth=maxhealth, *args, **kwargs) + maxhealth=maxhealth, critical=critical, + *args, **kwargs) class TeddyBear(Monster): @@ -107,3 +109,13 @@ class TeddyBear(Monster): maxhealth: int = 50, *args, **kwargs) -> None: super().__init__(name=name, strength=strength, maxhealth=maxhealth, *args, **kwargs) + + +class GiantSeaEagle(Monster): + """ + An eagle boss + """ + def __init__(self, name: str = "eagle", strength: int = 1000, + maxhealth: int = 5000, *args, **kwargs) -> None: + super().__init__(name=name, strength=strength, + maxhealth=maxhealth, *args, **kwargs) diff --git a/squirrelbattle/entities/player.py b/squirrelbattle/entities/player.py index 6f84cc6..615dfd5 100644 --- a/squirrelbattle/entities/player.py +++ b/squirrelbattle/entities/player.py @@ -2,7 +2,9 @@ # SPDX-License-Identifier: GPL-3.0-or-later from random import randint +from typing import Dict, Optional, Tuple +from .items import Item from ..interfaces import FightingEntity, InventoryHolder @@ -12,22 +14,40 @@ class Player(InventoryHolder, FightingEntity): """ current_xp: int = 0 max_xp: int = 10 + xp_buff: float = 1 + paths: Dict[Tuple[int, int], Tuple[int, int]] + equipped_main: Optional[Item] + equipped_secondary: Optional[Item] + equipped_helmet: Optional[Item] + equipped_armor: Optional[Item] def __init__(self, name: str = "player", maxhealth: int = 20, strength: int = 5, intelligence: int = 1, charisma: int = 1, dexterity: int = 1, constitution: int = 1, level: int = 1, current_xp: int = 0, max_xp: int = 10, inventory: list = None, - hazel: int = 42, vision: int = 5, *args, **kwargs) \ - -> None: + hazel: int = 42, equipped_main: Optional[Item] = None, + equipped_armor: Optional[Item] = None, critical: int = 5, + equipped_secondary: Optional[Item] = None, + equipped_helmet: Optional[Item] = None, xp_buff: float = 1, + vision: int = 5, *args, **kwargs) -> None: super().__init__(name=name, maxhealth=maxhealth, strength=strength, intelligence=intelligence, charisma=charisma, dexterity=dexterity, constitution=constitution, - level=level, *args, **kwargs) + level=level, critical=critical, *args, **kwargs) self.current_xp = current_xp self.max_xp = max_xp + self.xp_buff = xp_buff self.inventory = self.translate_inventory(inventory or []) self.paths = dict() self.hazel = hazel + self.equipped_main = self.dict_to_item(equipped_main) \ + if isinstance(equipped_main, dict) else equipped_main + self.equipped_armor = self.dict_to_item(equipped_armor) \ + if isinstance(equipped_armor, dict) else equipped_armor + self.equipped_secondary = self.dict_to_item(equipped_secondary) \ + if isinstance(equipped_secondary, dict) else equipped_secondary + self.equipped_helmet = self.dict_to_item(equipped_helmet) \ + if isinstance(equipped_helmet, dict) else equipped_helmet self.vision = vision def move(self, y: int, x: int) -> None: @@ -60,9 +80,24 @@ class Player(InventoryHolder, FightingEntity): Adds some experience to the player. If the required amount is reached, the player levels up. """ - self.current_xp += xp + self.current_xp += int(xp * self.xp_buff) self.level_up() + def remove_from_inventory(self, obj: Item) -> None: + """ + Remove the given item from the inventory, even if the item is equipped. + """ + if obj == self.equipped_main: + self.equipped_main = None + elif obj == self.equipped_armor: + self.equipped_armor = None + elif obj == self.equipped_secondary: + self.equipped_secondary = None + elif obj == self.equipped_helmet: + self.equipped_helmet = None + else: + return super().remove_from_inventory(obj) + # noinspection PyTypeChecker,PyUnresolvedReferences def check_move(self, y: int, x: int, move_if_possible: bool = False) \ -> bool: @@ -92,4 +127,12 @@ class Player(InventoryHolder, FightingEntity): d = super().save_state() d["current_xp"] = self.current_xp d["max_xp"] = self.max_xp + d["equipped_main"] = self.equipped_main.save_state()\ + if self.equipped_main else None + d["equipped_armor"] = self.equipped_armor.save_state()\ + if self.equipped_armor else None + d["equipped_secondary"] = self.equipped_secondary.save_state()\ + if self.equipped_secondary else None + d["equipped_helmet"] = self.equipped_helmet.save_state()\ + if self.equipped_helmet else None return d diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index 779e98f..ecdfccb 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -131,7 +131,7 @@ class Game: self.state = GameMode.MAINMENU 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. """ @@ -149,6 +149,12 @@ class Game: self.map.tick(self.player) elif key == KeyValues.INVENTORY: self.state = GameMode.INVENTORY + self.display_actions(DisplayActions.UPDATE) + elif key == KeyValues.USE and self.player.equipped_main: + if self.player.equipped_main: + self.player.equipped_main.use() + if self.player.equipped_secondary: + self.player.equipped_secondary.use() elif key == KeyValues.SPACE: self.state = GameMode.MAINMENU elif key == KeyValues.CHAT: diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index 601229b..ff47fda 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -3,7 +3,7 @@ from enum import Enum, auto 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 queue import PriorityQueue from functools import reduce @@ -188,7 +188,8 @@ class Map: tile = self.tiles[y][x] if tile.can_walk(): break - entity = choice(Entity.get_all_entity_classes())() + entity = choices(Entity.get_all_entity_classes(), + weights=Entity.get_weights(), k=1)[0]() entity.move(y, x) self.add_entity(entity) @@ -602,11 +603,19 @@ class Entity: """ from squirrelbattle.entities.items import BodySnatchPotion, Bomb, Heart from squirrelbattle.entities.monsters import Tiger, Hedgehog, \ - Rabbit, TeddyBear - from squirrelbattle.entities.friendly import Merchant, Sunflower, \ - Trumpet + Rabbit, TeddyBear, GiantSeaEagle + from squirrelbattle.entities.friendly import Merchant, Sunflower return [BodySnatchPotion, Bomb, Heart, Hedgehog, Rabbit, TeddyBear, - Sunflower, Tiger, Merchant, Trumpet] + Sunflower, Tiger, Merchant, GiantSeaEagle] + + @staticmethod + def get_weights() -> list: + """ + Returns a weigth list associated to the above function, to + be used to spawn random entities with a certain probability. + """ + return [3, 5, 6, 5, 5, 5, + 5, 4, 4, 1] @staticmethod def get_all_entity_classes_in_a_dict() -> dict: @@ -615,11 +624,11 @@ class Entity: """ from squirrelbattle.entities.player import Player from squirrelbattle.entities.monsters import Tiger, Hedgehog, Rabbit, \ - TeddyBear + TeddyBear, GiantSeaEagle from squirrelbattle.entities.friendly import Merchant, Sunflower, \ Trumpet from squirrelbattle.entities.items import BodySnatchPotion, Bomb, \ - Heart, Sword + Heart, Sword, Shield, Chestplate, Helmet, RingCritical, RingXP return { "Tiger": Tiger, "Bomb": Bomb, @@ -633,6 +642,12 @@ class Entity: "Sunflower": Sunflower, "Sword": Sword, "Trumpet": Trumpet, + "Eagle": GiantSeaEagle, + "Shield": Shield, + "Chestplate": Chestplate, + "Helmet": Helmet, + "RingCritical": RingCritical, + "RingXP": RingXP, } def save_state(self) -> dict: @@ -659,11 +674,12 @@ class FightingEntity(Entity): dexterity: int constitution: int level: int + critical: int def __init__(self, maxhealth: int = 0, health: Optional[int] = None, strength: int = 0, intelligence: int = 0, charisma: int = 0, dexterity: int = 0, constitution: int = 0, level: int = 0, - *args, **kwargs) -> None: + critical: int = 0, *args, **kwargs) -> None: super().__init__(*args, **kwargs) self.maxhealth = maxhealth self.health = maxhealth if health is None else health @@ -673,6 +689,7 @@ class FightingEntity(Entity): self.dexterity = dexterity self.constitution = constitution self.level = level + self.critical = critical @property def dead(self) -> bool: @@ -686,21 +703,28 @@ class FightingEntity(Entity): The entity deals damage to the opponent based on their respective stats. """ + diceroll = randint(1, 100) + damage = self.strength + string = " " + if diceroll <= self.critical: # It is a critical hit + damage *= 4 + string = " " + _("It's a critical hit!") + " " return _("{name} hits {opponent}.")\ .format(name=_(self.translated_name.capitalize()), - opponent=_(opponent.translated_name)) + " " + \ - opponent.take_damage(self, self.strength) + opponent=_(opponent.translated_name)) + string + \ + opponent.take_damage(self, damage) def take_damage(self, attacker: "Entity", amount: int) -> str: """ The entity takes damage from the attacker based on their respective stats. """ - self.health -= amount + damage = max(0, amount - self.constitution) + self.health -= damage if self.health <= 0: self.die() - return _("{name} takes {amount} damage.")\ - .format(name=self.translated_name.capitalize(), amount=str(amount))\ + return _("{name} takes {damage} damage.")\ + .format(name=self.translated_name.capitalize(), damage=str(damage))\ + (" " + _("{name} dies.") .format(name=self.translated_name.capitalize()) if self.health <= 0 else "") @@ -757,10 +781,10 @@ class InventoryHolder(Entity): """ for i in range(len(inventory)): if isinstance(inventory[i], dict): - inventory[i] = self.dict_to_inventory(inventory[i]) + inventory[i] = self.dict_to_item(inventory[i]) return inventory - def dict_to_inventory(self, item_dict: dict) -> Entity: + def dict_to_item(self, item_dict: dict) -> Entity: """ Translates a dictionnary that contains the state of an item into an item object. @@ -783,13 +807,15 @@ class InventoryHolder(Entity): """ Adds an object to the inventory. """ - self.inventory.append(obj) + if obj not in self.inventory: + self.inventory.append(obj) def remove_from_inventory(self, obj: Any) -> None: """ Removes an object from the inventory. """ - self.inventory.remove(obj) + if obj in self.inventory: + self.inventory.remove(obj) def change_hazel_balance(self, hz: int) -> None: """ diff --git a/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po index 56d4540..20aa2a7 100644 --- a/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po +++ b/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: squirrelbattle 3.14.1\n" "Report-Msgid-Bugs-To: squirrel-battle@crans.org\n" -"POT-Creation-Date: 2021-01-06 15:19+0100\n" +"POT-Creation-Date: 2021-01-08 01:57+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,6 +17,10 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +#, python-brace-format +msgid "{name} takes {amount} damage." +msgstr "{name} nimmt {amount} Schadenspunkte." + #: squirrelbattle/display/menudisplay.py:160 msgid "INVENTORY" msgstr "BESTAND" @@ -25,11 +29,32 @@ msgstr "BESTAND" msgid "STALL" msgstr "STAND" -#: squirrelbattle/display/statsdisplay.py:36 +#: squirrelbattle/display/statsdisplay.py:23 +#: squirrelbattle/tests/translations_test.py:60 +msgid "player" +msgstr "Spieler" + +#: squirrelbattle/display/statsdisplay.py:35 msgid "Inventory:" msgstr "Bestand:" -#: squirrelbattle/display/statsdisplay.py:55 +#: 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" msgstr "SIE WURDEN GESTORBEN" @@ -49,11 +74,11 @@ msgstr "Die Sonne ist warm heute" #. The bomb is exploding. #. Each entity that is close to the bomb takes damages. #. The player earn XP if the entity was killed. -#: squirrelbattle/entities/items.py:151 +#: squirrelbattle/entities/items.py:163 msgid "Bomb is exploding." msgstr "Die Bombe explodiert." -#: squirrelbattle/entities/items.py:248 +#: squirrelbattle/entities/items.py:344 #, python-brace-format msgid "{player} exchanged its body with {entity}." msgstr "{player} täuscht seinem Körper mit {entity} aus." @@ -96,6 +121,10 @@ msgstr "" "Die JSON-Datei ist nicht korrekt.\n" "Ihre Speicherung scheint korrumpiert. Sie wurde gelöscht." +#: squirrelbattle/interfaces.py:452 +msgid "It's a critical hit!" +msgstr "" + #: squirrelbattle/interfaces.py:453 #, python-brace-format msgid "{name} hits {opponent}." @@ -103,8 +132,8 @@ msgstr "{name} schlägt {opponent}." #: squirrelbattle/interfaces.py:465 #, python-brace-format -msgid "{name} takes {amount} damage." -msgstr "{name} nimmt {amount} Schadenspunkte." +msgid "{name} takes {damage} damage." +msgstr "" #: squirrelbattle/interfaces.py:467 #, python-brace-format @@ -218,10 +247,6 @@ msgstr "Textur-Packung" msgid "Language" msgstr "Sprache" -#: squirrelbattle/tests/translations_test.py:62 -msgid "player" -msgstr "Spieler" - #: squirrelbattle/tests/translations_test.py:64 msgid "hedgehog" msgstr "Igel" diff --git a/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po index f85e681..0bfdac6 100644 --- a/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po +++ b/squirrelbattle/locale/es/LC_MESSAGES/squirrelbattle.po @@ -17,6 +17,10 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +#, python-brace-format +msgid "{name} takes {amount} damage." +msgstr "{name} recibe {amount} daño." + #: squirrelbattle/display/menudisplay.py:160 msgid "INVENTORY" msgstr "INVENTORIO" @@ -25,11 +29,32 @@ msgstr "INVENTORIO" msgid "STALL" msgstr "PUESTO" -#: squirrelbattle/display/statsdisplay.py:36 +#: squirrelbattle/display/statsdisplay.py:23 +#: squirrelbattle/tests/translations_test.py:60 +msgid "player" +msgstr "jugador" + +#: squirrelbattle/display/statsdisplay.py:35 msgid "Inventory:" msgstr "Inventorio :" -#: squirrelbattle/display/statsdisplay.py:55 +#: 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" msgstr "ERES MUERTO" @@ -48,11 +73,11 @@ msgstr "El sol está caliente hoy" #. The bomb is exploding. #. Each entity that is close to the bomb takes damages. #. The player earn XP if the entity was killed. -#: squirrelbattle/entities/items.py:151 +#: squirrelbattle/entities/items.py:163 msgid "Bomb is exploding." msgstr "La bomba está explotando." -#: squirrelbattle/entities/items.py:248 +#: squirrelbattle/entities/items.py:344 #, python-brace-format msgid "{player} exchanged its body with {entity}." msgstr "{player} intercambió su cuerpo con {entity}." @@ -95,6 +120,10 @@ msgstr "" "El JSON archivo no es correcto.\n" "Su guarda parece corrupta. Fue eliminada." +#: squirrelbattle/interfaces.py:452 +msgid "It's a critical hit!" +msgstr "" + #: squirrelbattle/interfaces.py:453 #, python-brace-format msgid "{name} hits {opponent}." @@ -102,8 +131,8 @@ msgstr "{name} golpea a {opponent}." #: squirrelbattle/interfaces.py:465 #, python-brace-format -msgid "{name} takes {amount} damage." -msgstr "{name} recibe {amount} daño." +msgid "{name} takes {damage} damage." +msgstr "" #: squirrelbattle/interfaces.py:467 #, python-brace-format @@ -217,10 +246,6 @@ msgstr "Paquete de texturas" msgid "Language" msgstr "Languaje" -#: squirrelbattle/tests/translations_test.py:62 -msgid "player" -msgstr "jugador" - #: squirrelbattle/tests/translations_test.py:64 msgid "hedgehog" msgstr "erizo" diff --git a/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po index 81a4ab3..42172c3 100644 --- a/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po +++ b/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po @@ -17,6 +17,10 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" +#, python-brace-format +msgid "{name} takes {amount} damage." +msgstr "{name} prend {amount} points de dégât." + #: squirrelbattle/display/menudisplay.py:160 msgid "INVENTORY" msgstr "INVENTAIRE" @@ -25,11 +29,32 @@ msgstr "INVENTAIRE" msgid "STALL" msgstr "STAND" -#: squirrelbattle/display/statsdisplay.py:36 +#: squirrelbattle/display/statsdisplay.py:23 +#: squirrelbattle/tests/translations_test.py:60 +msgid "player" +msgstr "joueur" + +#: squirrelbattle/display/statsdisplay.py:35 msgid "Inventory:" msgstr "Inventaire :" -#: squirrelbattle/display/statsdisplay.py:55 +#: 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" msgstr "VOUS ÊTES MORT" @@ -49,11 +74,11 @@ msgstr "Le soleil est chaud aujourd'hui" #. The bomb is exploding. #. Each entity that is close to the bomb takes damages. #. The player earn XP if the entity was killed. -#: squirrelbattle/entities/items.py:151 +#: squirrelbattle/entities/items.py:163 msgid "Bomb is exploding." msgstr "La bombe explose." -#: squirrelbattle/entities/items.py:248 +#: squirrelbattle/entities/items.py:344 #, python-brace-format msgid "{player} exchanged its body with {entity}." msgstr "{player} a échangé son corps avec {entity}." @@ -96,6 +121,10 @@ msgstr "" "Le fichier JSON de sauvegarde est incorrect.\n" "Votre sauvegarde semble corrompue. Elle a été supprimée." +#: squirrelbattle/interfaces.py:452 +msgid "It's a critical hit!" +msgstr "C'est un coup critique !" + #: squirrelbattle/interfaces.py:453 #, python-brace-format msgid "{name} hits {opponent}." @@ -103,8 +132,8 @@ msgstr "{name} frappe {opponent}." #: squirrelbattle/interfaces.py:465 #, python-brace-format -msgid "{name} takes {amount} damage." -msgstr "{name} prend {amount} points de dégât." +msgid "{name} takes {damage} damage." +msgstr "{name} prend {damage} dégâts." #: squirrelbattle/interfaces.py:467 #, python-brace-format @@ -218,10 +247,6 @@ msgstr "Pack de textures" msgid "Language" msgstr "Langue" -#: squirrelbattle/tests/translations_test.py:62 -msgid "player" -msgstr "joueur" - #: squirrelbattle/tests/translations_test.py:64 msgid "hedgehog" msgstr "hérisson" @@ -256,7 +281,7 @@ msgstr "bombe" #: squirrelbattle/tests/translations_test.py:73 msgid "explosion" -msgstr "" +msgstr "explosion" #: squirrelbattle/tests/translations_test.py:74 msgid "heart" diff --git a/squirrelbattle/tests/entities_test.py b/squirrelbattle/tests/entities_test.py index 028aebc..af6f7dd 100644 --- a/squirrelbattle/tests/entities_test.py +++ b/squirrelbattle/tests/entities_test.py @@ -19,6 +19,7 @@ class TestEntities(unittest.TestCase): """ self.map = Map.load(ResourceManager.get_asset_path("example_map.txt")) self.player = Player() + self.player.constitution = 0 self.map.add_entity(self.player) self.player.move(self.map.start_y, self.map.start_x) @@ -55,6 +56,7 @@ class TestEntities(unittest.TestCase): self.assertTrue(entity.dead) entity = Rabbit() + entity.critical = 0 self.map.add_entity(entity) entity.move(15, 44) # Move randomly @@ -76,6 +78,7 @@ class TestEntities(unittest.TestCase): {self.player.name.capitalize()} takes {entity.strength} damage.") # Fight the rabbit + self.player.critical = 0 old_health = entity.health self.player.move_down() self.assertEqual(entity.health, old_health - self.player.strength) diff --git a/squirrelbattle/tests/game_test.py b/squirrelbattle/tests/game_test.py index efc20da..0843ae8 100644 --- a/squirrelbattle/tests/game_test.py +++ b/squirrelbattle/tests/game_test.py @@ -2,13 +2,16 @@ # SPDX-License-Identifier: GPL-3.0-or-later import os +import random import unittest from ..bootstrap import Bootstrap from ..display.display import Display from ..display.display_manager import DisplayManager from ..entities.friendly import Merchant, Sunflower -from ..entities.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 ..enums import DisplayActions from ..game import Game, KeyValues, GameMode @@ -613,6 +616,100 @@ class TestGame(unittest.TestCase): self.game.handle_key_pressed(KeyValues.SPACE) 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.