diff --git a/setup.py b/setup.py index f051bbb..573eea7 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ with open("README.md", "r") as f: long_description = f.read() # Compile messages -for language in ["de", "en", "fr"]: +for language in ["de", "fr"]: args = ["msgfmt", "--check-format", "-o", f"squirrelbattle/locale/{language}/LC_MESSAGES" "/squirrelbattle.mo", diff --git a/squirrelbattle/display/display_manager.py b/squirrelbattle/display/display_manager.py index f7a0882..0e9cf04 100644 --- a/squirrelbattle/display/display_manager.py +++ b/squirrelbattle/display/display_manager.py @@ -6,8 +6,8 @@ from squirrelbattle.display.display import VerticalSplit, HorizontalSplit from squirrelbattle.display.mapdisplay import MapDisplay from squirrelbattle.display.messagedisplay import MessageDisplay from squirrelbattle.display.statsdisplay import StatsDisplay -from squirrelbattle.display.menudisplay import SettingsMenuDisplay, \ - MainMenuDisplay +from squirrelbattle.display.menudisplay import MainMenuDisplay, \ + InventoryDisplay, SettingsMenuDisplay from squirrelbattle.display.logsdisplay import LogsDisplay from squirrelbattle.display.texturepack import TexturePack from typing import Any @@ -23,10 +23,11 @@ class DisplayManager: pack = TexturePack.get_pack(self.game.settings.TEXTURE_PACK) self.mapdisplay = MapDisplay(screen, pack) self.statsdisplay = StatsDisplay(screen, pack) + self.logsdisplay = LogsDisplay(screen, pack) + self.inventorydisplay = InventoryDisplay(screen, pack) self.mainmenudisplay = MainMenuDisplay(self.game.main_menu, screen, pack) self.settingsmenudisplay = SettingsMenuDisplay(screen, pack) - self.logsdisplay = LogsDisplay(screen, pack) self.messagedisplay = MessageDisplay(screen=screen, pack=None) self.hbar = HorizontalSplit(screen, pack) self.vbar = VerticalSplit(screen, pack) @@ -46,12 +47,14 @@ class DisplayManager: d.pack = TexturePack.get_pack(self.game.settings.TEXTURE_PACK) self.mapdisplay.update_map(self.game.map) self.statsdisplay.update_player(self.game.player) + self.inventorydisplay.update_menu(self.game.inventory_menu) self.settingsmenudisplay.update_menu(self.game.settings_menu) self.logsdisplay.update_logs(self.game.logs) self.messagedisplay.update_message(self.game.message) def refresh(self) -> None: - if self.game.state == GameMode.PLAY: + if self.game.state == GameMode.PLAY \ + or self.game.state == GameMode.INVENTORY: # The map pad has already the good size self.mapdisplay.refresh(0, 0, self.rows * 4 // 5, self.mapdisplay.pack.tile_width @@ -64,10 +67,15 @@ class DisplayManager: self.rows // 5 - 1, self.cols * 4 // 5) self.hbar.refresh(self.rows * 4 // 5, 0, 1, self.cols * 4 // 5) self.vbar.refresh(0, self.cols * 4 // 5, self.rows, 1) - if self.game.state == GameMode.MAINMENU: + if self.game.state == GameMode.INVENTORY: + self.inventorydisplay.refresh(self.rows // 10, + self.cols // 2, + 8 * self.rows // 10, + 2 * self.cols // 5) + elif self.game.state == GameMode.MAINMENU: self.mainmenudisplay.refresh(0, 0, self.rows, self.cols) - if self.game.state == GameMode.SETTINGS: - self.settingsmenudisplay.refresh(0, 0, self.rows, self.cols - 1) + elif self.game.state == GameMode.SETTINGS: + self.settingsmenudisplay.refresh(0, 0, self.rows, self.cols) if self.game.message: height, width = 0, 0 diff --git a/squirrelbattle/display/menudisplay.py b/squirrelbattle/display/menudisplay.py index b3036a0..285b6db 100644 --- a/squirrelbattle/display/menudisplay.py +++ b/squirrelbattle/display/menudisplay.py @@ -1,6 +1,6 @@ # Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse # SPDX-License-Identifier: GPL-3.0-or-later - +import curses from typing import List from squirrelbattle.menus import Menu, MainMenu @@ -21,8 +21,6 @@ class MenuDisplay(Display): # Menu values are printed in pad self.pad = self.newpad(self.trueheight, self.truewidth + 2) - for i in range(self.trueheight): - self.addstr(self.pad, i, 0, " " + self.values[i]) def update_pad(self) -> None: for i in range(self.trueheight): @@ -100,3 +98,23 @@ class MainMenuDisplay(Display): self.menudisplay.refresh( menuy, menux, min(self.menudisplay.preferred_height, self.height - menuy), menuwidth) + + +class InventoryDisplay(MenuDisplay): + def update_pad(self) -> None: + message = _("== INVENTORY ==") + self.addstr(self.pad, 0, (self.width - len(message)) // 2, message, + curses.A_BOLD | curses.A_ITALIC) + for i, item in enumerate(self.menu.values): + rep = self.pack[item.name.upper()] + selection = f"[{rep}]" if i == self.menu.position else f" {rep} " + self.addstr(self.pad, 2 + i, 0, selection + + " " + _(item.name).capitalize()) + + @property + def truewidth(self) -> int: + return max(1, self.height if hasattr(self, "height") else 10) + + @property + def trueheight(self) -> int: + return 2 + super().trueheight diff --git a/squirrelbattle/entities/items.py b/squirrelbattle/entities/items.py index 147d72c..4800a58 100644 --- a/squirrelbattle/entities/items.py +++ b/squirrelbattle/entities/items.py @@ -1,10 +1,12 @@ # Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse # SPDX-License-Identifier: GPL-3.0-or-later +from random import randint from typing import Optional from .player import Player from ..interfaces import Entity, FightingEntity, Map +from ..translations import gettext as _ class Item(Entity): @@ -20,16 +22,26 @@ class Item(Entity): self.held = held self.held_by = held_by - def drop(self, y: int, x: int) -> None: + def drop(self) -> None: """ The item is dropped from the inventory onto the floor """ if self.held: self.held_by.inventory.remove(self) + self.map.add_entity(self) + self.move(self.held_by.y, self.held_by.x) self.held = False self.held_by = None - self.map.add_entity(self) - self.move(y, x) + + def use(self) -> None: + """ + Indicates what should be done when the item is used. + """ + + def equip(self) -> None: + """ + Indicates what should be done when the item is equipped. + """ def hold(self, player: "Player") -> None: """ @@ -81,26 +93,47 @@ class Bomb(Item): """ damage: int = 5 exploding: bool + owner: Optional["Player"] + tick: int def __init__(self, damage: int = 5, exploding: bool = False, *args, **kwargs): super().__init__(name="bomb", *args, **kwargs) self.damage = damage self.exploding = exploding + self.tick = 4 + self.owner = None - def drop(self, x: int, y: int) -> None: - super().drop(x, y) - self.exploding = True + def use(self) -> None: + """ + When the bomb is used, throw it and explodes it. + """ + if self.held: + self.owner = self.held_by + super().drop() + self.exploding = True def act(self, m: Map) -> None: """ Special exploding action of the bomb """ if self.exploding: - for e in m.entities.copy(): - if abs(e.x - self.x) + abs(e.y - self.y) <= 1 and \ - isinstance(e, FightingEntity): - e.take_damage(self, self.damage) + if self.tick > 0: + # The bomb will explode in moves + self.tick -= 1 + else: + # The bomb is exploding. + # Each entity that is close to the bomb takes damages. + # The player earn XP if the entity was killed. + log_message = _("Bomb is exploding.") + for e in m.entities.copy(): + if abs(e.x - self.x) + abs(e.y - self.y) <= 3 and \ + isinstance(e, FightingEntity): + log_message += " " + e.take_damage(self, self.damage) + if e.dead: + self.owner.add_xp(randint(3, 7)) + m.logs.add_message(log_message) + m.entities.remove(self) def save_state(self) -> dict: """ diff --git a/squirrelbattle/enums.py b/squirrelbattle/enums.py index 024f167..84eb498 100644 --- a/squirrelbattle/enums.py +++ b/squirrelbattle/enums.py @@ -37,6 +37,10 @@ class KeyValues(Enum): LEFT = auto() RIGHT = auto() ENTER = auto() + INVENTORY = auto() + USE = auto() + EQUIP = auto() + DROP = auto() SPACE = auto() @staticmethod @@ -58,6 +62,14 @@ class KeyValues(Enum): return KeyValues.UP elif key == settings.KEY_ENTER: return KeyValues.ENTER + elif key == settings.KEY_INVENTORY: + return KeyValues.INVENTORY + elif key == settings.KEY_USE: + return KeyValues.USE + elif key == settings.KEY_EQUIP: + return KeyValues.EQUIP + elif key == settings.KEY_DROP: + return KeyValues.DROP elif key == ' ': return KeyValues.SPACE return None diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index 44ad349..b9439b5 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -39,6 +39,7 @@ class Game: self.main_menu = menus.MainMenu() self.settings_menu = menus.SettingsMenu() self.settings_menu.update_values(self.settings) + self.inventory_menu = menus.InventoryMenu() self.logs = Logs() self.message = None @@ -54,6 +55,7 @@ class Game: self.map.add_entity(self.player) self.player.move(self.map.start_y, self.map.start_x) self.map.spawn_random_entities(randint(3, 10)) + self.inventory_menu.update_player(self.player) def run(self, screen: Any) -> None: """ @@ -82,6 +84,8 @@ class Game: if self.state == GameMode.PLAY: self.handle_key_pressed_play(key) + elif self.state == GameMode.INVENTORY: + self.handle_key_pressed_inventory(key) elif self.state == GameMode.MAINMENU: self.handle_key_pressed_main_menu(key) elif self.state == GameMode.SETTINGS: @@ -104,9 +108,34 @@ class Game: elif key == KeyValues.RIGHT: if self.player.move_right(): self.map.tick() + elif key == KeyValues.INVENTORY: + self.state = GameMode.INVENTORY elif key == KeyValues.SPACE: self.state = GameMode.MAINMENU + def handle_key_pressed_inventory(self, key: KeyValues) -> None: + """ + In the inventory menu, we can interact with items or close the menu. + """ + if key == KeyValues.SPACE or key == KeyValues.INVENTORY: + self.state = GameMode.PLAY + elif key == KeyValues.UP: + self.inventory_menu.go_up() + elif key == KeyValues.DOWN: + self.inventory_menu.go_down() + if self.inventory_menu.values and not self.player.dead: + if key == KeyValues.USE: + self.inventory_menu.validate().use() + elif key == KeyValues.EQUIP: + self.inventory_menu.validate().equip() + elif key == KeyValues.DROP: + self.inventory_menu.validate().drop() + + # Ensure that the cursor has a good position + self.inventory_menu.position = min(self.inventory_menu.position, + len(self.inventory_menu.values) + - 1) + def handle_key_pressed_main_menu(self, key: KeyValues) -> None: """ In the main menu, we can navigate through options. diff --git a/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/de/LC_MESSAGES/squirrelbattle.po index dfd3365..0d20e90 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: 2020-11-28 16:03+0100\n" +"POT-Creation-Date: 2020-12-05 13:11+0100\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -17,7 +17,70 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: squirrelbattle/tests/game_test.py:284 squirrelbattle/tests/game_test.py:287 +#: squirrelbattle/display/menudisplay.py:105 +msgid "== INVENTORY ==" +msgstr "== BESTAND ==" + +#: squirrelbattle/display/statsdisplay.py:34 +msgid "Inventory:" +msgstr "Bestand:" + +#: squirrelbattle/display/statsdisplay.py:39 +msgid "YOU ARE DEAD" +msgstr "SIE WURDEN GESTORBEN" + +#. 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:129 +msgid "Bomb is exploding." +msgstr "" + +#: squirrelbattle/game.py:177 +msgid "" +"Some keys are missing in your save file.\n" +"Your save seems to be corrupt. It got deleted." +msgstr "" +"In Ihrer Speicherdatei fehlen einige Schlüssel.\n" +"Ihre Speicherung scheint korrupt zu sein. Es wird gelöscht." + +#: squirrelbattle/game.py:185 +msgid "" +"No player was found on this map!\n" +"Maybe you died?" +msgstr "" +"Auf dieser Karte wurde kein Spieler gefunden!\n" +"Vielleicht sind Sie gestorben?" + +#: squirrelbattle/game.py:205 +msgid "" +"The JSON file is not correct.\n" +"Your save seems corrupted. It got deleted." +msgstr "" +"Die JSON-Datei ist nicht korrekt.\n" +"Ihre Speicherung scheint korrumpiert. Sie wurde gelöscht." + +#: squirrelbattle/interfaces.py:398 +#, python-brace-format +msgid "{name} hits {opponent}." +msgstr "{name} schlägt {opponent}." + +#: squirrelbattle/interfaces.py:410 +#, python-brace-format +msgid "{name} takes {amount} damage." +msgstr "{name} nimmt {amount} Schadenspunkte." + +#: squirrelbattle/interfaces.py:412 +#, python-brace-format +msgid "{name} dies." +msgstr "{name} stirbt." + +#: squirrelbattle/menus.py:72 +msgid "Back" +msgstr "Zurück" + +#: squirrelbattle/tests/game_test.py:294 squirrelbattle/tests/game_test.py:297 +#: squirrelbattle/tests/game_test.py:300 #: squirrelbattle/tests/translations_test.py:16 msgid "New game" msgstr "Neu Spiel" @@ -79,88 +142,53 @@ msgid "Key to validate a menu" msgstr "Menütaste" #: squirrelbattle/tests/translations_test.py:45 +msgid "Key used to open the inventory" +msgstr "Bestandtaste" + +#: squirrelbattle/tests/translations_test.py:47 +msgid "Key used to use an item in the inventory" +msgstr "Taste um eines Objekts im Bestand zu verwenden" + +#: squirrelbattle/tests/translations_test.py:49 +msgid "Key used to equip an item in the inventory" +msgstr "Taste um eines Objekts im Bestand auszurüsten" + +#: squirrelbattle/tests/translations_test.py:51 +msgid "Key used to drop an item in the inventory" +msgstr "Taste um eines Objekts im Bestand zu werfen" + +#: squirrelbattle/tests/translations_test.py:53 msgid "Texture pack" msgstr "Textur-Packung" -#: squirrelbattle/tests/translations_test.py:46 +#: squirrelbattle/tests/translations_test.py:54 msgid "Language" msgstr "Sprache" -#: squirrelbattle/tests/translations_test.py:49 +#: squirrelbattle/tests/translations_test.py:57 msgid "player" msgstr "Spieler" -#: squirrelbattle/tests/translations_test.py:51 +#: squirrelbattle/tests/translations_test.py:59 msgid "tiger" msgstr "Tiger" -#: squirrelbattle/tests/translations_test.py:52 +#: squirrelbattle/tests/translations_test.py:60 msgid "hedgehog" msgstr "Igel" -#: squirrelbattle/tests/translations_test.py:53 +#: squirrelbattle/tests/translations_test.py:61 msgid "rabbit" msgstr "Kanninchen" -#: squirrelbattle/tests/translations_test.py:54 +#: squirrelbattle/tests/translations_test.py:62 msgid "teddy bear" msgstr "Teddybär" -#: squirrelbattle/tests/translations_test.py:56 +#: squirrelbattle/tests/translations_test.py:64 msgid "bomb" msgstr "Bombe" -#: squirrelbattle/tests/translations_test.py:57 +#: squirrelbattle/tests/translations_test.py:65 msgid "heart" msgstr "Herz" - -#: squirrelbattle/display/statsdisplay.py:34 -msgid "Inventory:" -msgstr "Bestand:" - -#: squirrelbattle/display/statsdisplay.py:39 -msgid "YOU ARE DEAD" -msgstr "SIE WURDEN GESTORBEN" - -#: squirrelbattle/interfaces.py:398 -#, python-brace-format -msgid "{name} hits {opponent}." -msgstr "{name} schlägt {opponent}." - -#: squirrelbattle/interfaces.py:410 -#, python-brace-format -msgid "{name} takes {amount} damage." -msgstr "{name} nimmt {amount} Schadenspunkte." - -#: squirrelbattle/interfaces.py:412 -#, python-brace-format -msgid "{name} dies." -msgstr "{name} stirbt." - -#: squirrelbattle/menus.py:71 -msgid "Back" -msgstr "Zurück" - -#: squirrelbattle/game.py:148 -msgid "" -"Some keys are missing in your save file.\n" -"Your save seems to be corrupt. It got deleted." -msgstr "" -"In Ihrer Speicherdatei fehlen einige Schlüssel.\n" -"Ihre Speicherung scheint korrupt zu sein. Es wird gelöscht." - -#: squirrelbattle/game.py:156 -msgid "" -"No player was found on this map!\n" -"Maybe you died?" -msgstr "" -"Auf dieser Karte wurde kein Spieler gefunden!\n" -"Vielleicht sind Sie gestorben?" - -#: squirrelbattle/game.py:176 -msgid "" -"The JSON file is not correct.\n" -"Your save seems corrupted. It got deleted." -msgstr "" -"Die JSON-Datei ist nicht korrekt.\n" -"Ihre Speicherung scheint korrumpiert. Sie wurde gelöscht." diff --git a/squirrelbattle/locale/en/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/en/LC_MESSAGES/squirrelbattle.po deleted file mode 100644 index 3f563fa..0000000 --- a/squirrelbattle/locale/en/LC_MESSAGES/squirrelbattle.po +++ /dev/null @@ -1,195 +0,0 @@ -# SOME DESCRIPTIVE TITLE. -# Copyright (C) YEAR ÿnérant, eichhornchen, nicomarg, charlse -# This file is distributed under the same license as the squirrelbattle package. -# FIRST AUTHOR , YEAR. -# -#, fuzzy -msgid "" -msgstr "" -"Project-Id-Version: squirrelbattle 3.14.1\n" -"Report-Msgid-Bugs-To: squirrel-battle@crans.org\n" -"POT-Creation-Date: 2020-11-28 16:03+0100\n" -"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: FULL NAME \n" -"Language-Team: LANGUAGE \n" -"Language: \n" -"MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" -"Content-Transfer-Encoding: 8bit\n" - -#: squirrelbattle/display/statsdisplay.py:34 -msgid "Inventory:" -msgstr "" - -#: squirrelbattle/display/statsdisplay.py:39 -msgid "YOU ARE DEAD" -msgstr "" - -#: squirrelbattle/interfaces.py:394 squirrelbattle/interfaces.py:398 -#, python-brace-format -msgid "{name} hits {opponent}." -msgstr "" - -#: squirrelbattle/interfaces.py:405 squirrelbattle/interfaces.py:410 -#, python-brace-format -msgid "{name} takes {amount} damage." -msgstr "" - -#: squirrelbattle/menus.py:45 squirrelbattle/tests/translations_test.py:14 -#: squirrelbattle/tests/game_test.py:284 squirrelbattle/tests/game_test.py:287 -#: squirrelbattle/tests/translations_test.py:16 -msgid "New game" -msgstr "" - -#: squirrelbattle/menus.py:46 squirrelbattle/tests/translations_test.py:15 -#: squirrelbattle/tests/translations_test.py:17 -msgid "Resume" -msgstr "" - -#: squirrelbattle/menus.py:47 squirrelbattle/tests/translations_test.py:17 -#: squirrelbattle/tests/translations_test.py:19 -msgid "Save" -msgstr "" - -#: squirrelbattle/menus.py:48 squirrelbattle/tests/translations_test.py:16 -#: squirrelbattle/tests/translations_test.py:18 -msgid "Load" -msgstr "" - -#: squirrelbattle/menus.py:49 squirrelbattle/tests/translations_test.py:18 -#: squirrelbattle/tests/translations_test.py:20 -msgid "Settings" -msgstr "" - -#: squirrelbattle/menus.py:50 squirrelbattle/tests/translations_test.py:19 -#: squirrelbattle/tests/translations_test.py:21 -msgid "Exit" -msgstr "" - -#: squirrelbattle/menus.py:71 -msgid "Back" -msgstr "" - -#: squirrelbattle/game.py:147 squirrelbattle/game.py:148 -msgid "" -"Some keys are missing in your save file.\n" -"Your save seems to be corrupt. It got deleted." -msgstr "" - -#: squirrelbattle/game.py:155 squirrelbattle/game.py:156 -msgid "" -"No player was found on this map!\n" -"Maybe you died?" -msgstr "" - -#: squirrelbattle/game.py:175 squirrelbattle/game.py:176 -msgid "" -"The JSON file is not correct.\n" -"Your save seems corrupted. It got deleted." -msgstr "" - -#: squirrelbattle/settings.py:21 squirrelbattle/tests/translations_test.py:21 -#: squirrelbattle/tests/translations_test.py:25 -#: squirrelbattle/tests/translations_test.py:27 -msgid "Main key to move up" -msgstr "" - -#: squirrelbattle/settings.py:22 squirrelbattle/tests/translations_test.py:23 -#: squirrelbattle/tests/translations_test.py:27 -#: squirrelbattle/tests/translations_test.py:29 -msgid "Secondary key to move up" -msgstr "" - -#: squirrelbattle/settings.py:23 squirrelbattle/tests/translations_test.py:25 -#: squirrelbattle/tests/translations_test.py:29 -#: squirrelbattle/tests/translations_test.py:31 -msgid "Main key to move down" -msgstr "" - -#: squirrelbattle/settings.py:24 squirrelbattle/tests/translations_test.py:27 -#: squirrelbattle/tests/translations_test.py:31 -#: squirrelbattle/tests/translations_test.py:33 -msgid "Secondary key to move down" -msgstr "" - -#: squirrelbattle/settings.py:25 squirrelbattle/tests/translations_test.py:29 -#: squirrelbattle/tests/translations_test.py:33 -#: squirrelbattle/tests/translations_test.py:35 -msgid "Main key to move left" -msgstr "" - -#: squirrelbattle/settings.py:26 squirrelbattle/tests/translations_test.py:31 -#: squirrelbattle/tests/translations_test.py:35 -#: squirrelbattle/tests/translations_test.py:37 -msgid "Secondary key to move left" -msgstr "" - -#: squirrelbattle/settings.py:27 squirrelbattle/tests/translations_test.py:33 -#: squirrelbattle/tests/translations_test.py:37 -#: squirrelbattle/tests/translations_test.py:39 -msgid "Main key to move right" -msgstr "" - -#: squirrelbattle/settings.py:29 squirrelbattle/tests/translations_test.py:35 -#: squirrelbattle/tests/translations_test.py:39 -#: squirrelbattle/tests/translations_test.py:41 -msgid "Secondary key to move right" -msgstr "" - -#: squirrelbattle/settings.py:30 squirrelbattle/tests/translations_test.py:37 -#: squirrelbattle/tests/translations_test.py:41 -#: squirrelbattle/tests/translations_test.py:43 -msgid "Key to validate a menu" -msgstr "" - -#: squirrelbattle/settings.py:31 squirrelbattle/tests/translations_test.py:39 -#: squirrelbattle/tests/translations_test.py:43 -#: squirrelbattle/tests/translations_test.py:45 -msgid "Texture pack" -msgstr "" - -#: squirrelbattle/settings.py:32 squirrelbattle/tests/translations_test.py:40 -#: squirrelbattle/tests/translations_test.py:44 -#: squirrelbattle/tests/translations_test.py:46 -msgid "Language" -msgstr "" - -#: squirrelbattle/interfaces.py:407 squirrelbattle/interfaces.py:412 -#, python-brace-format -msgid "{name} dies." -msgstr "" - -#: squirrelbattle/tests/translations_test.py:47 -#: squirrelbattle/tests/translations_test.py:49 -msgid "player" -msgstr "" - -#: squirrelbattle/tests/translations_test.py:49 -#: squirrelbattle/tests/translations_test.py:51 -msgid "tiger" -msgstr "" - -#: squirrelbattle/tests/translations_test.py:50 -#: squirrelbattle/tests/translations_test.py:52 -msgid "hedgehog" -msgstr "" - -#: squirrelbattle/tests/translations_test.py:51 -#: squirrelbattle/tests/translations_test.py:53 -msgid "rabbit" -msgstr "" - -#: squirrelbattle/tests/translations_test.py:52 -#: squirrelbattle/tests/translations_test.py:54 -msgid "teddy bear" -msgstr "" - -#: squirrelbattle/tests/translations_test.py:54 -#: squirrelbattle/tests/translations_test.py:56 -msgid "bomb" -msgstr "" - -#: squirrelbattle/tests/translations_test.py:55 -#: squirrelbattle/tests/translations_test.py:57 -msgid "heart" -msgstr "" diff --git a/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po b/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po index d46cee6..33ab440 100644 --- a/squirrelbattle/locale/fr/LC_MESSAGES/squirrelbattle.po +++ b/squirrelbattle/locale/fr/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: 2020-11-28 16:03+0100\n" +"POT-Creation-Date: 2020-12-05 13:11+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" +#: squirrelbattle/display/menudisplay.py:105 +msgid "== INVENTORY ==" +msgstr "== INVENTAIRE ==" + #: squirrelbattle/display/statsdisplay.py:34 msgid "Inventory:" msgstr "Inventaire :" @@ -25,52 +29,14 @@ msgstr "Inventaire :" msgid "YOU ARE DEAD" msgstr "VOUS ÊTES MORT" -#: squirrelbattle/interfaces.py:394 squirrelbattle/interfaces.py:398 -#, python-brace-format -msgid "{name} hits {opponent}." -msgstr "{name} frappe {opponent}." +#. 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:129 +msgid "Bomb is exploding." +msgstr "La bombe explose." -#: squirrelbattle/interfaces.py:405 squirrelbattle/interfaces.py:410 -#, python-brace-format -msgid "{name} takes {amount} damage." -msgstr "{name} prend {amount} points de dégât." - -#: squirrelbattle/menus.py:45 squirrelbattle/tests/translations_test.py:14 -#: squirrelbattle/tests/game_test.py:284 squirrelbattle/tests/game_test.py:287 -#: squirrelbattle/tests/translations_test.py:16 -msgid "New game" -msgstr "Nouvelle partie" - -#: squirrelbattle/menus.py:46 squirrelbattle/tests/translations_test.py:15 -#: squirrelbattle/tests/translations_test.py:17 -msgid "Resume" -msgstr "Continuer" - -#: squirrelbattle/menus.py:47 squirrelbattle/tests/translations_test.py:17 -#: squirrelbattle/tests/translations_test.py:19 -msgid "Save" -msgstr "Sauvegarder" - -#: squirrelbattle/menus.py:48 squirrelbattle/tests/translations_test.py:16 -#: squirrelbattle/tests/translations_test.py:18 -msgid "Load" -msgstr "Charger" - -#: squirrelbattle/menus.py:49 squirrelbattle/tests/translations_test.py:18 -#: squirrelbattle/tests/translations_test.py:20 -msgid "Settings" -msgstr "Paramètres" - -#: squirrelbattle/menus.py:50 squirrelbattle/tests/translations_test.py:19 -#: squirrelbattle/tests/translations_test.py:21 -msgid "Exit" -msgstr "Quitter" - -#: squirrelbattle/menus.py:71 -msgid "Back" -msgstr "Retour" - -#: squirrelbattle/game.py:147 squirrelbattle/game.py:148 +#: squirrelbattle/game.py:177 msgid "" "Some keys are missing in your save file.\n" "Your save seems to be corrupt. It got deleted." @@ -78,7 +44,7 @@ msgstr "" "Certaines clés de votre ficher de sauvegarde sont manquantes.\n" "Votre sauvegarde semble corrompue. Elle a été supprimée." -#: squirrelbattle/game.py:155 squirrelbattle/game.py:156 +#: squirrelbattle/game.py:185 msgid "" "No player was found on this map!\n" "Maybe you died?" @@ -86,7 +52,7 @@ msgstr "" "Aucun joueur n'a été trouvé sur la carte !\n" "Peut-être êtes-vous mort ?" -#: squirrelbattle/game.py:175 squirrelbattle/game.py:176 +#: squirrelbattle/game.py:205 msgid "" "The JSON file is not correct.\n" "Your save seems corrupted. It got deleted." @@ -94,108 +60,135 @@ msgstr "" "Le fichier JSON de sauvegarde est incorrect.\n" "Votre sauvegarde semble corrompue. Elle a été supprimée." -#: squirrelbattle/settings.py:21 squirrelbattle/tests/translations_test.py:21 -#: squirrelbattle/tests/translations_test.py:25 -#: squirrelbattle/tests/translations_test.py:27 -msgid "Main key to move up" -msgstr "Touche principale pour aller vers le haut" +#: squirrelbattle/interfaces.py:398 +#, python-brace-format +msgid "{name} hits {opponent}." +msgstr "{name} frappe {opponent}." -#: squirrelbattle/settings.py:22 squirrelbattle/tests/translations_test.py:23 -#: squirrelbattle/tests/translations_test.py:27 -#: squirrelbattle/tests/translations_test.py:29 -msgid "Secondary key to move up" -msgstr "Touche secondaire pour aller vers le haut" +#: squirrelbattle/interfaces.py:410 +#, python-brace-format +msgid "{name} takes {amount} damage." +msgstr "{name} prend {amount} points de dégât." -#: squirrelbattle/settings.py:23 squirrelbattle/tests/translations_test.py:25 -#: squirrelbattle/tests/translations_test.py:29 -#: squirrelbattle/tests/translations_test.py:31 -msgid "Main key to move down" -msgstr "Touche principale pour aller vers le bas" - -#: squirrelbattle/settings.py:24 squirrelbattle/tests/translations_test.py:27 -#: squirrelbattle/tests/translations_test.py:31 -#: squirrelbattle/tests/translations_test.py:33 -msgid "Secondary key to move down" -msgstr "Touche secondaire pour aller vers le bas" - -#: squirrelbattle/settings.py:25 squirrelbattle/tests/translations_test.py:29 -#: squirrelbattle/tests/translations_test.py:33 -#: squirrelbattle/tests/translations_test.py:35 -msgid "Main key to move left" -msgstr "Touche principale pour aller vers la gauche" - -#: squirrelbattle/settings.py:26 squirrelbattle/tests/translations_test.py:31 -#: squirrelbattle/tests/translations_test.py:35 -#: squirrelbattle/tests/translations_test.py:37 -msgid "Secondary key to move left" -msgstr "Touche secondaire pour aller vers la gauche" - -#: squirrelbattle/settings.py:27 squirrelbattle/tests/translations_test.py:33 -#: squirrelbattle/tests/translations_test.py:37 -#: squirrelbattle/tests/translations_test.py:39 -msgid "Main key to move right" -msgstr "Touche principale pour aller vers la droite" - -#: squirrelbattle/settings.py:29 squirrelbattle/tests/translations_test.py:35 -#: squirrelbattle/tests/translations_test.py:39 -#: squirrelbattle/tests/translations_test.py:41 -msgid "Secondary key to move right" -msgstr "Touche secondaire pour aller vers la droite" - -#: squirrelbattle/settings.py:30 squirrelbattle/tests/translations_test.py:37 -#: squirrelbattle/tests/translations_test.py:41 -#: squirrelbattle/tests/translations_test.py:43 -msgid "Key to validate a menu" -msgstr "Touche pour valider un menu" - -#: squirrelbattle/settings.py:31 squirrelbattle/tests/translations_test.py:39 -#: squirrelbattle/tests/translations_test.py:43 -#: squirrelbattle/tests/translations_test.py:45 -msgid "Texture pack" -msgstr "Pack de textures" - -#: squirrelbattle/settings.py:32 squirrelbattle/tests/translations_test.py:40 -#: squirrelbattle/tests/translations_test.py:44 -#: squirrelbattle/tests/translations_test.py:46 -msgid "Language" -msgstr "Langue" - -#: squirrelbattle/interfaces.py:407 squirrelbattle/interfaces.py:412 +#: squirrelbattle/interfaces.py:412 #, python-brace-format msgid "{name} dies." msgstr "{name} meurt." +#: squirrelbattle/menus.py:72 +msgid "Back" +msgstr "Retour" + +#: squirrelbattle/tests/game_test.py:294 squirrelbattle/tests/game_test.py:297 +#: squirrelbattle/tests/game_test.py:300 +#: squirrelbattle/tests/translations_test.py:16 +msgid "New game" +msgstr "Nouvelle partie" + +#: squirrelbattle/tests/translations_test.py:17 +msgid "Resume" +msgstr "Continuer" + +#: squirrelbattle/tests/translations_test.py:18 +msgid "Load" +msgstr "Charger" + +#: squirrelbattle/tests/translations_test.py:19 +msgid "Save" +msgstr "Sauvegarder" + +#: squirrelbattle/tests/translations_test.py:20 +msgid "Settings" +msgstr "Paramètres" + +#: squirrelbattle/tests/translations_test.py:21 +msgid "Exit" +msgstr "Quitter" + +#: squirrelbattle/tests/translations_test.py:27 +msgid "Main key to move up" +msgstr "Touche principale pour aller vers le haut" + +#: squirrelbattle/tests/translations_test.py:29 +msgid "Secondary key to move up" +msgstr "Touche secondaire pour aller vers le haut" + +#: squirrelbattle/tests/translations_test.py:31 +msgid "Main key to move down" +msgstr "Touche principale pour aller vers le bas" + +#: squirrelbattle/tests/translations_test.py:33 +msgid "Secondary key to move down" +msgstr "Touche secondaire pour aller vers le bas" + +#: squirrelbattle/tests/translations_test.py:35 +msgid "Main key to move left" +msgstr "Touche principale pour aller vers la gauche" + +#: squirrelbattle/tests/translations_test.py:37 +msgid "Secondary key to move left" +msgstr "Touche secondaire pour aller vers la gauche" + +#: squirrelbattle/tests/translations_test.py:39 +msgid "Main key to move right" +msgstr "Touche principale pour aller vers la droite" + +#: squirrelbattle/tests/translations_test.py:41 +msgid "Secondary key to move right" +msgstr "Touche secondaire pour aller vers la droite" + +#: squirrelbattle/tests/translations_test.py:43 +msgid "Key to validate a menu" +msgstr "Touche pour valider un menu" + +#: squirrelbattle/tests/translations_test.py:45 +msgid "Key used to open the inventory" +msgstr "Touche utilisée pour ouvrir l'inventaire" + #: squirrelbattle/tests/translations_test.py:47 +msgid "Key used to use an item in the inventory" +msgstr "Touche pour utiliser un objet de l'inventaire" + #: squirrelbattle/tests/translations_test.py:49 +msgid "Key used to equip an item in the inventory" +msgstr "Touche pour équiper un objet de l'inventaire" + +#: squirrelbattle/tests/translations_test.py:51 +msgid "Key used to drop an item in the inventory" +msgstr "Touche pour jeter un objet de l'inventaire" + +#: squirrelbattle/tests/translations_test.py:53 +msgid "Texture pack" +msgstr "Pack de textures" + +#: squirrelbattle/tests/translations_test.py:54 +msgid "Language" +msgstr "Langue" + +#: squirrelbattle/tests/translations_test.py:57 msgid "player" msgstr "joueur" -#: squirrelbattle/tests/translations_test.py:49 -#: squirrelbattle/tests/translations_test.py:51 +#: squirrelbattle/tests/translations_test.py:59 msgid "tiger" msgstr "tigre" -#: squirrelbattle/tests/translations_test.py:50 -#: squirrelbattle/tests/translations_test.py:52 +#: squirrelbattle/tests/translations_test.py:60 msgid "hedgehog" msgstr "hérisson" -#: squirrelbattle/tests/translations_test.py:51 -#: squirrelbattle/tests/translations_test.py:53 +#: squirrelbattle/tests/translations_test.py:61 msgid "rabbit" msgstr "lapin" -#: squirrelbattle/tests/translations_test.py:52 -#: squirrelbattle/tests/translations_test.py:54 +#: squirrelbattle/tests/translations_test.py:62 msgid "teddy bear" msgstr "nounours" -#: squirrelbattle/tests/translations_test.py:54 -#: squirrelbattle/tests/translations_test.py:56 +#: squirrelbattle/tests/translations_test.py:64 msgid "bomb" msgstr "bombe" -#: squirrelbattle/tests/translations_test.py:55 -#: squirrelbattle/tests/translations_test.py:57 +#: squirrelbattle/tests/translations_test.py:65 msgid "heart" msgstr "cœur" diff --git a/squirrelbattle/menus.py b/squirrelbattle/menus.py index 4fcfabe..d6946d0 100644 --- a/squirrelbattle/menus.py +++ b/squirrelbattle/menus.py @@ -5,6 +5,7 @@ from enum import Enum from typing import Any, Optional from .display.texturepack import TexturePack +from .entities.player import Player from .enums import GameMode, KeyValues, DisplayActions from .settings import Settings from .translations import gettext as _, Translator @@ -115,3 +116,14 @@ class SettingsMenu(Menu): game.settings.write_settings() self.waiting_for_key = False self.update_values(game.settings) + + +class InventoryMenu(Menu): + player: Player + + def update_player(self, player: Player) -> None: + self.player = player + + @property + def values(self) -> list: + return self.player.inventory diff --git a/squirrelbattle/settings.py b/squirrelbattle/settings.py index 3090679..4004645 100644 --- a/squirrelbattle/settings.py +++ b/squirrelbattle/settings.py @@ -27,6 +27,10 @@ class Settings: self.KEY_RIGHT_PRIMARY = ['d', 'Main key to move right'] self.KEY_RIGHT_SECONDARY = ['KEY_RIGHT', 'Secondary key to move right'] self.KEY_ENTER = ['\n', 'Key to validate a menu'] + self.KEY_INVENTORY = ['i', 'Key used to open the inventory'] + self.KEY_USE = ['u', 'Key used to use an item in the inventory'] + self.KEY_EQUIP = ['e', 'Key used to equip an item in the inventory'] + self.KEY_DROP = ['r', 'Key used to drop an item in the inventory'] self.TEXTURE_PACK = ['ascii', 'Texture pack'] self.LOCALE = [locale.getlocale()[0][:2], 'Language'] diff --git a/squirrelbattle/tests/entities_test.py b/squirrelbattle/tests/entities_test.py index 371bfc7..efd3397 100644 --- a/squirrelbattle/tests/entities_test.py +++ b/squirrelbattle/tests/entities_test.py @@ -97,12 +97,13 @@ class TestEntities(unittest.TestCase): self.assertFalse(item.held) item.hold(self.player) self.assertTrue(item.held) - item.drop(2, 6) - self.assertEqual(item.y, 2) + item.drop() + self.assertEqual(item.y, 1) self.assertEqual(item.x, 6) # Pick up item - self.player.move_down() + self.player.move_left() + self.player.move_right() self.assertTrue(item.held) self.assertEqual(item.held_by, self.player) self.assertIn(item, self.player.inventory) @@ -125,10 +126,14 @@ class TestEntities(unittest.TestCase): item.act(self.map) self.assertFalse(hedgehog.dead) self.assertFalse(teddy_bear.dead) - item.drop(42, 42) + self.player.move(42, 42) + item.hold(self.player) + item.use() self.assertEqual(item.y, 42) self.assertEqual(item.x, 42) - item.act(self.map) + # Wait for the explosion + for ignored in range(5): + item.act(self.map) self.assertTrue(hedgehog.dead) self.assertTrue(teddy_bear.dead) bomb_state = item.save_state() diff --git a/squirrelbattle/tests/game_test.py b/squirrelbattle/tests/game_test.py index a23b6f9..f0120eb 100644 --- a/squirrelbattle/tests/game_test.py +++ b/squirrelbattle/tests/game_test.py @@ -7,6 +7,7 @@ import unittest from ..bootstrap import Bootstrap from ..display.display import Display from ..display.display_manager import DisplayManager +from ..entities.items import Bomb from ..entities.player import Player from ..enums import DisplayActions from ..game import Game, KeyValues, GameMode @@ -107,6 +108,18 @@ class TestGame(unittest.TestCase): self.assertEqual(KeyValues.translate_key( self.game.settings.KEY_ENTER, self.game.settings), KeyValues.ENTER) + self.assertEqual(KeyValues.translate_key( + self.game.settings.KEY_INVENTORY, self.game.settings), + KeyValues.INVENTORY) + self.assertEqual(KeyValues.translate_key( + self.game.settings.KEY_USE, self.game.settings), + KeyValues.USE) + self.assertEqual(KeyValues.translate_key( + self.game.settings.KEY_EQUIP, self.game.settings), + KeyValues.EQUIP) + self.assertEqual(KeyValues.translate_key( + self.game.settings.KEY_DROP, self.game.settings), + KeyValues.DROP) self.assertEqual(KeyValues.translate_key(' ', self.game.settings), KeyValues.SPACE) self.assertEqual(KeyValues.translate_key('plop', self.game.settings), @@ -261,11 +274,8 @@ class TestGame(unittest.TestCase): self.assertEqual(self.game.settings.KEY_LEFT_PRIMARY, 'a') # Navigate to "texture pack" - self.game.handle_key_pressed(KeyValues.DOWN) - self.game.handle_key_pressed(KeyValues.DOWN) - self.game.handle_key_pressed(KeyValues.DOWN) - self.game.handle_key_pressed(KeyValues.DOWN) - self.game.handle_key_pressed(KeyValues.DOWN) + for ignored in range(9): + self.game.handle_key_pressed(KeyValues.DOWN) # Change texture pack self.assertEqual(self.game.settings.TEXTURE_PACK, "ascii") @@ -337,3 +347,64 @@ class TestGame(unittest.TestCase): self.game.display_actions(DisplayActions.REFRESH) self.game.handle_key_pressed(None, "random key") self.assertIsNone(self.game.message) + + def test_inventory_menu(self) -> None: + """ + Open the inventory menu and interact with items. + """ + self.game.state = GameMode.PLAY + # Open and close the inventory + self.game.handle_key_pressed(KeyValues.INVENTORY) + self.assertEqual(self.game.state, GameMode.INVENTORY) + self.game.handle_key_pressed(KeyValues.SPACE) + self.assertEqual(self.game.state, GameMode.PLAY) + + # Add five bombs in the inventory + for ignored in range(5): + bomb = Bomb() + bomb.map = self.game.map + bomb.map.add_entity(bomb) + bomb.hold(self.game.player) + + self.game.handle_key_pressed(KeyValues.INVENTORY) + self.assertEqual(self.game.state, GameMode.INVENTORY) + + # Navigate in the menu + self.game.handle_key_pressed(KeyValues.DOWN) + self.game.handle_key_pressed(KeyValues.DOWN) + self.game.handle_key_pressed(KeyValues.DOWN) + self.assertEqual(self.game.inventory_menu.position, 3) + self.game.handle_key_pressed(KeyValues.DOWN) + self.game.handle_key_pressed(KeyValues.DOWN) + self.game.handle_key_pressed(KeyValues.UP) + self.game.handle_key_pressed(KeyValues.DOWN) + self.assertEqual(self.game.inventory_menu.position, 4) + + # Equip key does nothing + self.game.handle_key_pressed(KeyValues.EQUIP) + + # Drop an item + bomb = self.game.player.inventory[-1] + self.assertEqual(self.game.inventory_menu.validate(), bomb) + self.assertTrue(bomb.held) + self.assertEqual(bomb.held_by, self.game.player) + self.game.handle_key_pressed(KeyValues.DROP) + self.assertFalse(bomb.held) + self.assertIsNone(bomb.held_by) + self.assertIsNone(bomb.owner) + self.assertFalse(bomb.exploding) + self.assertEqual(bomb.y, self.game.player.y) + self.assertEqual(bomb.x, self.game.player.x) + + # Use the bomb + bomb = self.game.player.inventory[-1] + self.assertEqual(self.game.inventory_menu.validate(), bomb) + self.assertTrue(bomb.held) + self.assertEqual(bomb.held_by, self.game.player) + self.game.handle_key_pressed(KeyValues.USE) + self.assertFalse(bomb.held) + self.assertIsNone(bomb.held_by) + self.assertEqual(bomb.owner, self.game.player) + self.assertTrue(bomb.exploding) + self.assertEqual(bomb.y, self.game.player.y) + self.assertEqual(bomb.x, self.game.player.x) diff --git a/squirrelbattle/tests/settings_test.py b/squirrelbattle/tests/settings_test.py index b0d9739..06225b2 100644 --- a/squirrelbattle/tests/settings_test.py +++ b/squirrelbattle/tests/settings_test.py @@ -4,9 +4,13 @@ import unittest from squirrelbattle.settings import Settings +from squirrelbattle.translations import Translator class TestSettings(unittest.TestCase): + def setUp(self) -> None: + Translator.setlocale("en") + def test_settings(self) -> None: """ Ensure that settings are well loaded. diff --git a/squirrelbattle/tests/translations_test.py b/squirrelbattle/tests/translations_test.py index 6c18840..ef7369f 100644 --- a/squirrelbattle/tests/translations_test.py +++ b/squirrelbattle/tests/translations_test.py @@ -42,6 +42,14 @@ class TestTranslations(unittest.TestCase): "Touche secondaire pour aller vers la droite") self.assertEqual(_("Key to validate a menu"), "Touche pour valider un menu") + self.assertEqual(_("Key used to open the inventory"), + "Touche utilisée pour ouvrir l'inventaire") + self.assertEqual(_("Key used to use an item in the inventory"), + "Touche pour utiliser un objet de l'inventaire") + self.assertEqual(_("Key used to equip an item in the inventory"), + "Touche pour équiper un objet de l'inventaire") + self.assertEqual(_("Key used to drop an item in the inventory"), + "Touche pour jeter un objet de l'inventaire") self.assertEqual(_("Texture pack"), "Pack de textures") self.assertEqual(_("Language"), "Langue") diff --git a/squirrelbattle/translations.py b/squirrelbattle/translations.py index f532bb0..1e97df6 100644 --- a/squirrelbattle/translations.py +++ b/squirrelbattle/translations.py @@ -3,6 +3,7 @@ import gettext as gt import os +import re import subprocess from pathlib import Path from typing import Any, List @@ -53,6 +54,9 @@ class Translator: Analyse all strings in the project and extract them. """ for language in cls.SUPPORTED_LOCALES: + if language == "en": + # Don't translate the main language + continue file_name = Path(__file__).parent / "locale" / language \ / "LC_MESSAGES" / "squirrelbattle.po" args = ["find", "squirrelbattle", "-iname", "*.py"] @@ -65,9 +69,14 @@ class Translator: "--copyright-holder=ÿnérant, eichhornchen, " "nicomarg, charlse", "--msgid-bugs-address=squirrel-battle@crans.org", + "--sort-by-file", "-o", file_name] if file_name.is_file(): args.append("--join-existing") + with open(file_name, "r") as f: + content = f.read() + with open(file_name, "w") as f: + f.write(re.sub("#:.*\n", "", content)) print(f"Make {language} messages...") subprocess.Popen(args, stdin=find.stdout).wait() @@ -77,6 +86,8 @@ class Translator: Compile translation messages from source files. """ for language in cls.SUPPORTED_LOCALES: + if language == "en": + continue args = ["msgfmt", "--check-format", "-o", Path(__file__).parent / "locale" / language / "LC_MESSAGES" / "squirrelbattle.mo",