Merge branch 'body-snatch' into 'master'

Body snatch

Closes #34 et #33

See merge request ynerant/squirrel-battle!38
This commit is contained in:
ynerant 2020-12-05 14:52:47 +01:00
commit 6fa11d9dfe
12 changed files with 138 additions and 50 deletions

View File

@ -109,7 +109,7 @@ class InventoryDisplay(MenuDisplay):
rep = self.pack[item.name.upper()] rep = self.pack[item.name.upper()]
selection = f"[{rep}]" if i == self.menu.position else f" {rep} " selection = f"[{rep}]" if i == self.menu.position else f" {rep} "
self.addstr(self.pad, 2 + i, 0, selection self.addstr(self.pad, 2 + i, 0, selection
+ " " + _(item.name).capitalize()) + " " + item.translated_name.capitalize())
@property @property
def truewidth(self) -> int: def truewidth(self) -> int:

View File

@ -56,6 +56,7 @@ TexturePack.ASCII_PACK = TexturePack(
RABBIT='Y', RABBIT='Y',
TIGER='n', TIGER='n',
TEDDY_BEAR='8', TEDDY_BEAR='8',
BODY_SNATCH_POTION='S',
) )
TexturePack.SQUIRREL_PACK = TexturePack( TexturePack.SQUIRREL_PACK = TexturePack(
@ -75,4 +76,5 @@ TexturePack.SQUIRREL_PACK = TexturePack(
RABBIT='🐇', RABBIT='🐇',
TIGER='🐅', TIGER='🐅',
TEDDY_BEAR='🧸', TEDDY_BEAR='🧸',
BODY_SNATCH_POTION='🔀',
) )

View File

@ -1,7 +1,7 @@
# Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse # Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from random import randint from random import choice, randint
from typing import Optional from typing import Optional
from .player import Player from .player import Player
@ -67,8 +67,8 @@ class Heart(Item):
""" """
healing: int healing: int
def __init__(self, healing: int = 5, *args, **kwargs): def __init__(self, name: str = "heart", healing: int = 5, *args, **kwargs):
super().__init__(name="heart", *args, **kwargs) super().__init__(name=name, *args, **kwargs)
self.healing = healing self.healing = healing
def hold(self, player: "Player") -> None: def hold(self, player: "Player") -> None:
@ -96,9 +96,9 @@ class Bomb(Item):
owner: Optional["Player"] owner: Optional["Player"]
tick: int tick: int
def __init__(self, damage: int = 5, exploding: bool = False, def __init__(self, name: str = "bomb", damage: int = 5,
*args, **kwargs): exploding: bool = False, *args, **kwargs):
super().__init__(name="bomb", *args, **kwargs) super().__init__(name=name, *args, **kwargs)
self.damage = damage self.damage = damage
self.exploding = exploding self.exploding = exploding
self.tick = 4 self.tick = 4
@ -143,3 +143,36 @@ class Bomb(Item):
d["exploding"] = self.exploding d["exploding"] = self.exploding
d["damage"] = self.damage d["damage"] = self.damage
return d return d
class BodySnatchPotion(Item):
"""
The body-snatch potion allows to exchange all characteristics with a random
other entity.
"""
def __init__(self, name: str = "body_snatch_potion", *args, **kwargs):
super().__init__(name=name, *args, **kwargs)
def use(self) -> None:
"""
Find a valid random entity, then exchange characteristics.
"""
valid_entities = self.held_by.map.find_entities(FightingEntity)
valid_entities.remove(self.held_by)
entity = choice(valid_entities)
entity_state = entity.save_state()
player_state = self.held_by.save_state()
self.held_by.__dict__.update(entity_state)
entity.__dict__.update(player_state)
self.held_by.map.currenty, self.held_by.map.currentx = self.held_by.y,\
self.held_by.x
self.held_by.map.logs.add_message(
_("{player} exchanged its body with {entity}.").format(
player=self.held_by.translated_name.capitalize(),
entity=entity.translated_name))
self.held_by.recalculate_paths()
self.held_by.inventory.remove(self)

View File

@ -63,9 +63,9 @@ class Tiger(Monster):
""" """
A tiger monster A tiger monster
""" """
def __init__(self, strength: int = 2, maxhealth: int = 20, def __init__(self, name: str = "tiger", strength: int = 2,
*args, **kwargs) -> None: maxhealth: int = 20, *args, **kwargs) -> None:
super().__init__(name="tiger", strength=strength, super().__init__(name=name, strength=strength,
maxhealth=maxhealth, *args, **kwargs) maxhealth=maxhealth, *args, **kwargs)
@ -73,9 +73,9 @@ class Hedgehog(Monster):
""" """
A really mean hedgehog monster A really mean hedgehog monster
""" """
def __init__(self, strength: int = 3, maxhealth: int = 10, def __init__(self, name: str = "hedgehog", strength: int = 3,
*args, **kwargs) -> None: maxhealth: int = 10, *args, **kwargs) -> None:
super().__init__(name="hedgehog", strength=strength, super().__init__(name=name, strength=strength,
maxhealth=maxhealth, *args, **kwargs) maxhealth=maxhealth, *args, **kwargs)
@ -83,9 +83,9 @@ class Rabbit(Monster):
""" """
A rabbit monster A rabbit monster
""" """
def __init__(self, strength: int = 1, maxhealth: int = 15, def __init__(self, name: str = "rabbit", strength: int = 1,
*args, **kwargs) -> None: maxhealth: int = 15, *args, **kwargs) -> None:
super().__init__(name="rabbit", strength=strength, super().__init__(name=name, strength=strength,
maxhealth=maxhealth, *args, **kwargs) maxhealth=maxhealth, *args, **kwargs)
@ -93,7 +93,7 @@ class TeddyBear(Monster):
""" """
A cute teddybear monster A cute teddybear monster
""" """
def __init__(self, strength: int = 0, maxhealth: int = 50, def __init__(self, name: str = "teddy_bear", strength: int = 0,
*args, **kwargs) -> None: maxhealth: int = 50, *args, **kwargs) -> None:
super().__init__(name="teddy_bear", strength=strength, super().__init__(name=name, strength=strength,
maxhealth=maxhealth, *args, **kwargs) maxhealth=maxhealth, *args, **kwargs)

View File

@ -16,17 +16,24 @@ class Player(FightingEntity):
inventory: list inventory: list
paths: Dict[Tuple[int, int], Tuple[int, int]] paths: Dict[Tuple[int, int], Tuple[int, int]]
def __init__(self, maxhealth: int = 20, strength: int = 5, def __init__(self, name: str = "player", maxhealth: int = 20,
intelligence: int = 1, charisma: int = 1, dexterity: int = 1, strength: int = 5, intelligence: int = 1, charisma: int = 1,
constitution: int = 1, level: int = 1, current_xp: int = 0, dexterity: int = 1, constitution: int = 1, level: int = 1,
max_xp: int = 10, *args, **kwargs) -> None: current_xp: int = 0, max_xp: int = 10, inventory: list = None,
super().__init__(name="player", maxhealth=maxhealth, strength=strength, *args, **kwargs) \
-> None:
super().__init__(name=name, maxhealth=maxhealth, strength=strength,
intelligence=intelligence, charisma=charisma, intelligence=intelligence, charisma=charisma,
dexterity=dexterity, constitution=constitution, dexterity=dexterity, constitution=constitution,
level=level, *args, **kwargs) level=level, *args, **kwargs)
self.current_xp = current_xp self.current_xp = current_xp
self.max_xp = max_xp self.max_xp = max_xp
self.inventory = list() self.inventory = inventory if inventory else list()
for i in range(len(self.inventory)):
if isinstance(self.inventory[i], dict):
entity_classes = self.get_all_entity_classes_in_a_dict()
item_class = entity_classes[self.inventory[i]["type"]]
self.inventory[i] = item_class(**self.inventory[i])
self.paths = dict() self.paths = dict()
def move(self, y: int, x: int) -> None: def move(self, y: int, x: int) -> None:
@ -117,4 +124,5 @@ class Player(FightingEntity):
d = super().save_state() d = super().save_state()
d["current_xp"] = self.current_xp d["current_xp"] = self.current_xp
d["max_xp"] = self.max_xp d["max_xp"] = self.max_xp
d["inventory"] = [item.save_state() for item in self.inventory]
return d return d

View File

@ -48,7 +48,7 @@ class Game:
Create a new game on the screen. Create 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.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()

View File

@ -324,10 +324,11 @@ class Entity:
""" """
Returns all entities subclasses Returns all entities subclasses
""" """
from squirrelbattle.entities.items import Heart, Bomb from squirrelbattle.entities.items import BodySnatchPotion, Bomb, Heart
from squirrelbattle.entities.monsters import Tiger, Hedgehog, \ from squirrelbattle.entities.monsters import Tiger, Hedgehog, \
Rabbit, TeddyBear Rabbit, TeddyBear
return [Tiger, Bomb, Heart, Hedgehog, Rabbit, TeddyBear] return [BodySnatchPotion, Bomb, Heart, Hedgehog,
Rabbit, TeddyBear, Tiger]
@staticmethod @staticmethod
def get_all_entity_classes_in_a_dict() -> dict: def get_all_entity_classes_in_a_dict() -> dict:
@ -337,11 +338,12 @@ class Entity:
from squirrelbattle.entities.player import Player from squirrelbattle.entities.player import Player
from squirrelbattle.entities.monsters import Tiger, Hedgehog, Rabbit, \ from squirrelbattle.entities.monsters import Tiger, Hedgehog, Rabbit, \
TeddyBear TeddyBear
from squirrelbattle.entities.items import Bomb, Heart from squirrelbattle.entities.items import BodySnatchPotion, Bomb, Heart
return { return {
"Tiger": Tiger, "Tiger": Tiger,
"Bomb": Bomb, "Bomb": Bomb,
"Heart": Heart, "Heart": Heart,
"BodySnatchPotion": BodySnatchPotion,
"Hedgehog": Hedgehog, "Hedgehog": Hedgehog,
"Rabbit": Rabbit, "Rabbit": Rabbit,
"TeddyBear": TeddyBear, "TeddyBear": TeddyBear,
@ -423,7 +425,7 @@ class FightingEntity(Entity):
""" """
Returns a fighting entities specific attributes Returns a fighting entities specific attributes
""" """
return ["maxhealth", "health", "level", "strength", return ["name", "maxhealth", "health", "level", "strength",
"intelligence", "charisma", "dexterity", "constitution"] "intelligence", "charisma", "dexterity", "constitution"]
def save_state(self) -> dict: def save_state(self) -> dict:

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-05 13:11+0100\n" "POT-Creation-Date: 2020-12-05 14:46+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"
@ -25,16 +25,21 @@ msgstr "== BESTAND =="
msgid "Inventory:" msgid "Inventory:"
msgstr "Bestand:" msgstr "Bestand:"
#: squirrelbattle/display/statsdisplay.py:39 #: squirrelbattle/display/statsdisplay.py:50
msgid "YOU ARE DEAD" msgid "YOU ARE DEAD"
msgstr "SIE WURDEN GESTORBEN" msgstr "SIE WURDEN GESTORBEN"
#. 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:129 #: squirrelbattle/entities/items.py:128
msgid "Bomb is exploding." msgid "Bomb is exploding."
msgstr "" msgstr "Die Bombe explodiert."
#: squirrelbattle/entities/items.py:172
#, python-brace-format
msgid "{player} exchanged its body with {entity}."
msgstr "{player} täuscht seinem Körper mit {entity} aus."
#: squirrelbattle/game.py:177 #: squirrelbattle/game.py:177
msgid "" msgid ""
@ -60,17 +65,17 @@ 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:398 #: squirrelbattle/interfaces.py:400
#, 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:410 #: squirrelbattle/interfaces.py:412
#, python-brace-format #, python-brace-format
msgid "{name} takes {amount} damage." msgid "{name} takes {amount} damage."
msgstr "{name} nimmt {amount} Schadenspunkte." msgstr "{name} nimmt {amount} Schadenspunkte."
#: squirrelbattle/interfaces.py:412 #: squirrelbattle/interfaces.py:414
#, python-brace-format #, python-brace-format
msgid "{name} dies." msgid "{name} dies."
msgstr "{name} stirbt." msgstr "{name} stirbt."
@ -79,8 +84,8 @@ msgstr "{name} stirbt."
msgid "Back" msgid "Back"
msgstr "Zurück" msgstr "Zurück"
#: squirrelbattle/tests/game_test.py:294 squirrelbattle/tests/game_test.py:297 #: squirrelbattle/tests/game_test.py:300 squirrelbattle/tests/game_test.py:303
#: squirrelbattle/tests/game_test.py:300 #: squirrelbattle/tests/game_test.py:306
#: squirrelbattle/tests/translations_test.py:16 #: squirrelbattle/tests/translations_test.py:16
msgid "New game" msgid "New game"
msgstr "Neu Spiel" msgstr "Neu Spiel"
@ -186,9 +191,13 @@ msgid "teddy bear"
msgstr "Teddybär" msgstr "Teddybär"
#: squirrelbattle/tests/translations_test.py:64 #: squirrelbattle/tests/translations_test.py:64
msgid "body snatch potion"
msgstr "Leichenfleddererzaubertrank"
#: squirrelbattle/tests/translations_test.py:65
msgid "bomb" msgid "bomb"
msgstr "Bombe" msgstr "Bombe"
#: squirrelbattle/tests/translations_test.py:65 #: squirrelbattle/tests/translations_test.py:66
msgid "heart" msgid "heart"
msgstr "Herz" msgstr "Herz"

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-05 13:11+0100\n" "POT-Creation-Date: 2020-12-05 14:46+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"
@ -25,17 +25,22 @@ msgstr "== INVENTAIRE =="
msgid "Inventory:" msgid "Inventory:"
msgstr "Inventaire :" msgstr "Inventaire :"
#: squirrelbattle/display/statsdisplay.py:39 #: squirrelbattle/display/statsdisplay.py:50
msgid "YOU ARE DEAD" msgid "YOU ARE DEAD"
msgstr "VOUS ÊTES MORT" msgstr "VOUS ÊTES MORT"
#. 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:129 #: squirrelbattle/entities/items.py:128
msgid "Bomb is exploding." msgid "Bomb is exploding."
msgstr "La bombe explose." msgstr "La bombe explose."
#: squirrelbattle/entities/items.py:172
#, python-brace-format
msgid "{player} exchanged its body with {entity}."
msgstr "{player} a échangé son corps avec {entity}."
#: squirrelbattle/game.py:177 #: squirrelbattle/game.py:177
msgid "" msgid ""
"Some keys are missing in your save file.\n" "Some keys are missing in your save file.\n"
@ -60,17 +65,17 @@ 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:398 #: squirrelbattle/interfaces.py:400
#, 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:410 #: squirrelbattle/interfaces.py:412
#, python-brace-format #, python-brace-format
msgid "{name} takes {amount} damage." msgid "{name} takes {amount} damage."
msgstr "{name} prend {amount} points de dégât." msgstr "{name} prend {amount} points de dégât."
#: squirrelbattle/interfaces.py:412 #: squirrelbattle/interfaces.py:414
#, python-brace-format #, python-brace-format
msgid "{name} dies." msgid "{name} dies."
msgstr "{name} meurt." msgstr "{name} meurt."
@ -79,8 +84,8 @@ msgstr "{name} meurt."
msgid "Back" msgid "Back"
msgstr "Retour" msgstr "Retour"
#: squirrelbattle/tests/game_test.py:294 squirrelbattle/tests/game_test.py:297 #: squirrelbattle/tests/game_test.py:300 squirrelbattle/tests/game_test.py:303
#: squirrelbattle/tests/game_test.py:300 #: squirrelbattle/tests/game_test.py:306
#: squirrelbattle/tests/translations_test.py:16 #: squirrelbattle/tests/translations_test.py:16
msgid "New game" msgid "New game"
msgstr "Nouvelle partie" msgstr "Nouvelle partie"
@ -186,9 +191,13 @@ msgid "teddy bear"
msgstr "nounours" msgstr "nounours"
#: squirrelbattle/tests/translations_test.py:64 #: squirrelbattle/tests/translations_test.py:64
msgid "body snatch potion"
msgstr "potion d'arrachage de corps"
#: squirrelbattle/tests/translations_test.py:65
msgid "bomb" msgid "bomb"
msgstr "bombe" msgstr "bombe"
#: squirrelbattle/tests/translations_test.py:65 #: squirrelbattle/tests/translations_test.py:66
msgid "heart" msgid "heart"
msgstr "cœur" msgstr "cœur"

View File

@ -3,7 +3,7 @@
import unittest import unittest
from squirrelbattle.entities.items import Bomb, Heart, Item from squirrelbattle.entities.items import BodySnatchPotion, Bomb, Heart, Item
from squirrelbattle.entities.monsters import Tiger, Hedgehog, Rabbit, TeddyBear from squirrelbattle.entities.monsters import Tiger, Hedgehog, Rabbit, TeddyBear
from squirrelbattle.entities.player import Player from squirrelbattle.entities.player import Player
from squirrelbattle.interfaces import Entity, Map from squirrelbattle.interfaces import Entity, Map
@ -154,6 +154,24 @@ class TestEntities(unittest.TestCase):
heart_state = item.save_state() heart_state = item.save_state()
self.assertEqual(heart_state["healing"], item.healing) self.assertEqual(heart_state["healing"], item.healing)
def test_body_snatch_potion(self) -> None:
"""
Test some random stuff with body snatch potions.
"""
item = BodySnatchPotion()
self.map.add_entity(item)
item.hold(self.player)
tiger = Tiger(y=42, x=42)
self.map.add_entity(tiger)
# The player becomes a tiger, and the tiger becomes a squirrel
item.use()
self.assertEqual(self.player.name, "tiger")
self.assertEqual(tiger.name, "player")
self.assertEqual(self.player.y, 42)
self.assertEqual(self.player.x, 42)
def test_players(self) -> None: def test_players(self) -> None:
""" """
Test some random stuff with players. Test some random stuff with players.

View File

@ -32,6 +32,9 @@ class TestGame(unittest.TestCase):
""" """
Save a game and reload it. Save a game and reload it.
""" """
bomb = Bomb()
self.game.map.add_entity(bomb)
bomb.hold(self.game.player)
old_state = self.game.save_state() old_state = self.game.save_state()
self.game.handle_key_pressed(KeyValues.DOWN) self.game.handle_key_pressed(KeyValues.DOWN)
@ -45,6 +48,9 @@ class TestGame(unittest.TestCase):
new_state = self.game.save_state() new_state = self.game.save_state()
self.assertEqual(old_state, new_state) self.assertEqual(old_state, new_state)
# Ensure that the bomb is loaded
self.assertTrue(self.game.player.inventory)
# Error on loading save # Error on loading save
with open(ResourceManager.get_config_path("save.json"), "w") as f: with open(ResourceManager.get_config_path("save.json"), "w") as f:
f.write("I am not a JSON file") f.write("I am not a JSON file")

View File

@ -61,5 +61,6 @@ class TestTranslations(unittest.TestCase):
self.assertEqual(_("rabbit"), "lapin") self.assertEqual(_("rabbit"), "lapin")
self.assertEqual(_("teddy bear"), "nounours") self.assertEqual(_("teddy bear"), "nounours")
self.assertEqual(_("body snatch potion"), "potion d'arrachage de corps")
self.assertEqual(_("bomb"), "bombe") self.assertEqual(_("bomb"), "bombe")
self.assertEqual(_("heart"), "cœur") self.assertEqual(_("heart"), "cœur")