Merge branch 'master' into map_generation
# Conflicts: # squirrelbattle/game.py # squirrelbattle/interfaces.py # squirrelbattle/tests/game_test.py
This commit is contained in:
commit
1ab63434f6
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
env/
|
env/
|
||||||
venv/
|
venv/
|
||||||
|
local/
|
||||||
|
|
||||||
.coverage
|
.coverage
|
||||||
.pytest_cache/
|
.pytest_cache/
|
||||||
|
|
|
@ -3,21 +3,16 @@ stages:
|
||||||
- quality-assurance
|
- quality-assurance
|
||||||
- build
|
- build
|
||||||
|
|
||||||
py37:
|
|
||||||
stage: test
|
|
||||||
image: python:3.7-alpine
|
|
||||||
before_script:
|
|
||||||
- apk add --no-cache gettext
|
|
||||||
- pip install tox
|
|
||||||
script: tox -e py3
|
|
||||||
|
|
||||||
py38:
|
py310:
|
||||||
stage: test
|
stage: test
|
||||||
image: python:3.8-alpine
|
image: python:rc-alpine
|
||||||
before_script:
|
before_script:
|
||||||
- apk add --no-cache gettext
|
- apk add --no-cache gettext
|
||||||
- pip install tox
|
- pip install tox
|
||||||
script: tox -e py3
|
script: tox -e py3
|
||||||
|
only:
|
||||||
|
- master
|
||||||
|
|
||||||
|
|
||||||
py39:
|
py39:
|
||||||
|
@ -28,6 +23,38 @@ py39:
|
||||||
- pip install tox
|
- pip install tox
|
||||||
script: tox -e py3
|
script: tox -e py3
|
||||||
|
|
||||||
|
|
||||||
|
py38:
|
||||||
|
stage: test
|
||||||
|
image: python:3.8-alpine
|
||||||
|
before_script:
|
||||||
|
- apk add --no-cache gettext
|
||||||
|
- pip install tox
|
||||||
|
script: tox -e py3
|
||||||
|
|
||||||
|
|
||||||
|
py37:
|
||||||
|
stage: test
|
||||||
|
image: python:3.7-alpine
|
||||||
|
before_script:
|
||||||
|
- apk add --no-cache gettext
|
||||||
|
- pip install tox
|
||||||
|
script: tox -e py3
|
||||||
|
only:
|
||||||
|
- master
|
||||||
|
|
||||||
|
|
||||||
|
py36:
|
||||||
|
stage: test
|
||||||
|
image: python:3.6-alpine
|
||||||
|
before_script:
|
||||||
|
- apk add --no-cache gettext
|
||||||
|
- pip install tox
|
||||||
|
script: tox -e py3
|
||||||
|
only:
|
||||||
|
- master
|
||||||
|
|
||||||
|
|
||||||
linters:
|
linters:
|
||||||
stage: quality-assurance
|
stage: quality-assurance
|
||||||
image: python:3-alpine
|
image: python:3-alpine
|
||||||
|
@ -36,11 +63,15 @@ linters:
|
||||||
script: tox -e linters
|
script: tox -e linters
|
||||||
allow_failure: true
|
allow_failure: true
|
||||||
|
|
||||||
|
|
||||||
build-deb:
|
build-deb:
|
||||||
image: debian:buster-slim
|
image: debian:buster-slim
|
||||||
stage: build
|
stage: build
|
||||||
before_script:
|
before_script:
|
||||||
- apt-get update && apt-get -y --no-install-recommends install build-essential debmake dh-python debhelper gettext python3-all python3-setuptools
|
- >
|
||||||
|
apt-get update && apt-get -y --no-install-recommends install
|
||||||
|
build-essential debmake dh-python debhelper gettext python3-all
|
||||||
|
python3-setuptools
|
||||||
script:
|
script:
|
||||||
- dpkg-buildpackage
|
- dpkg-buildpackage
|
||||||
- mkdir build && cp ../*.deb build/
|
- mkdir build && cp ../*.deb build/
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
[![pipeline status](https://gitlab.crans.org/ynerant/squirrel-battle/badges/master/pipeline.svg)](https://gitlab.crans.org/ynerant/squirrel-battle/-/commits/master)
|
[![pipeline status](https://gitlab.crans.org/ynerant/squirrel-battle/badges/master/pipeline.svg)](https://gitlab.crans.org/ynerant/squirrel-battle/-/commits/master)
|
||||||
[![coverage report](https://gitlab.crans.org/ynerant/squirrel-battle/badges/master/coverage.svg)](https://gitlab.crans.org/ynerant/squirrel-battle/-/commits/master)
|
[![coverage report](https://gitlab.crans.org/ynerant/squirrel-battle/badges/master/coverage.svg)](https://gitlab.crans.org/ynerant/squirrel-battle/-/commits/master)
|
||||||
[![Documentation Status](https://readthedocs.org/projects/squirrel-battle/badge/?version=latest)](https://squirrel-battle.readthedocs.io/fr/latest/?badge=latest)
|
[![Documentation Status](https://readthedocs.org/projects/squirrel-battle/badge/?version=latest)](https://squirrel-battle.readthedocs.io/fr/latest/?badge=latest)
|
||||||
|
[![Supported Python versions](https://img.shields.io/pypi/pyversions/squirrel-battle)](https://pypi.org/project/squirrel-battle/)
|
||||||
[![PyPI](https://img.shields.io/pypi/v/squirrel-battle)](https://pypi.org/project/squirrel-battle/)
|
[![PyPI](https://img.shields.io/pypi/v/squirrel-battle)](https://pypi.org/project/squirrel-battle/)
|
||||||
[![PYPI downloads](https://img.shields.io/pypi/dm/squirrel-battle)](https://pypi.org/project/squirrel-battle/)
|
[![PYPI downloads](https://img.shields.io/pypi/dm/squirrel-battle)](https://pypi.org/project/squirrel-battle/)
|
||||||
[![AUR version](https://img.shields.io/aur/version/python-squirrel-battle)](https://aur.archlinux.org/packages/python-squirrel-battle/)
|
[![AUR version](https://img.shields.io/aur/version/python-squirrel-battle)](https://aur.archlinux.org/packages/python-squirrel-battle/)
|
||||||
|
|
|
@ -275,7 +275,7 @@ Construction du paquet Debian
|
||||||
-----------------------------
|
-----------------------------
|
||||||
|
|
||||||
Structure du paquet
|
Structure du paquet
|
||||||
-------------------
|
~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
L'ensemble des instructions pour construire le paquet Debian est situé dans le
|
L'ensemble des instructions pour construire le paquet Debian est situé dans le
|
||||||
dossier ``debian/``.
|
dossier ``debian/``.
|
||||||
|
@ -292,14 +292,14 @@ Le paquet ``fonts-noto-color-emoji`` est en dépendance pour le bon affichage
|
||||||
des émojis.
|
des émojis.
|
||||||
|
|
||||||
Mettre à jour le paquet
|
Mettre à jour le paquet
|
||||||
-----------------------
|
~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Pour changer la version du paquet, il faut ajouter des lignes dans le fichier
|
Pour changer la version du paquet, il faut ajouter des lignes dans le fichier
|
||||||
``changelog``.
|
``changelog``.
|
||||||
|
|
||||||
|
|
||||||
Construire le paquet
|
Construire le paquet
|
||||||
--------------------
|
~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Il faut partir d'une installation de Debian.
|
Il faut partir d'une installation de Debian.
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
Affichage de l'historique
|
Affichage de l'historique
|
||||||
=========================
|
=========================
|
||||||
|
|
||||||
Pas encore documenté.
|
L'historique des actions est affiché en bas de l'écran. À chaque action d'une entité, comme frapper quelqu'un, ou lorsque le joueur parle à une entité, cela s'affiche dans l'historique.
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
Affichage de la carte
|
Affichage de la carte
|
||||||
=====================
|
=====================
|
||||||
|
|
||||||
Pas encore documenté.
|
La carte s'affiche dans la partie en haut à gauche de l'écran, sur la plus grande partie de l'écran. On affiche les tuiles une par une, selon le texture pack sélectionné. La map est actualisée à chaque action d'une entité.
|
||||||
|
|
||||||
|
L'afffichage de la carte suit les déplacements du joueur.
|
||||||
|
|
|
@ -1,4 +1,14 @@
|
||||||
Affichage des menus
|
Affichage des menus
|
||||||
===================
|
===================
|
||||||
|
|
||||||
Pas encore documenté.
|
Les menus sont affichés dans une boîte. On peut naviguer dedans avec les flèches haut et bas,
|
||||||
|
et valider avec la touche entrée.
|
||||||
|
|
||||||
|
Il y a plusieurs menus dans le jeu :
|
||||||
|
|
||||||
|
* Le main menu, qui s'affiche au lancement du jeu.
|
||||||
|
* Le menu des paramètres : si on sélectionne un choix de touche et qu'on appuie sur entrée, on peut ensuite appuyer sur une touche pour remplacer la touche utilisée.
|
||||||
|
* Le menu des crédits : ce menu fonctionne avec la souris. En cliquant on affiche une image.
|
||||||
|
* Le menu d'inventaire : dans l'inventaire, on peut utiliser les touches pour utiliser un item ou l'équiper...
|
||||||
|
* Le menu de vente : on peut utiliser les touches gauche et droite pour switcher entre l'inventaire du joueur et celui du marchand.
|
||||||
|
* Menu des warnings : Pas vraiment un menu, mais affiche juste un message dans une petite boite pour prévenir le joueur que quelquechose ne va pas.
|
||||||
|
|
|
@ -1,4 +1,26 @@
|
||||||
Affichage des statistiques
|
Affichage des statistiques
|
||||||
==========================
|
==========================
|
||||||
|
|
||||||
Pas encore documenté.
|
.. _Hazel: ../index.html
|
||||||
|
|
||||||
|
Les statistiques du joueur sont affichées en haut à droite de l'écran
|
||||||
|
et séparées du reste de l'affichage par une barre verticale.
|
||||||
|
|
||||||
|
Les informations affichées sont :
|
||||||
|
|
||||||
|
* **LVL** - le niveau du joueur
|
||||||
|
* **EXP** - la quantité d'expérience que le joueur a gagné et combien il lui en faut avant le prochain niveau.
|
||||||
|
* **HP** - la quantité de vie que le joueur a actuellement et combien il peut en avoir au maximum.
|
||||||
|
* **STR** - la force du joueur.
|
||||||
|
* **INT** - l'intelligence du joueur.
|
||||||
|
* **CHR** - le charisme du joueur.
|
||||||
|
* **DEX** - la dextérité du joueur.
|
||||||
|
* **CON** - la constitution du joueur.
|
||||||
|
* **CRI** - le pourcentage de chance de coup critique.
|
||||||
|
* **Inventory** - le contenu de l'inventaire du joueur.
|
||||||
|
* **Equipped main** - l'objet équipé dans la main principale.
|
||||||
|
* **Equipped secondary** - l'objet équipé dans la main secondaire.
|
||||||
|
* **Equipped armor** - le plastron porté par le joueur.
|
||||||
|
* **Equipped helmet** - le casque porté par le joueur.
|
||||||
|
* **Hazel** - le nombre d'Hazel_ que le joueur possède.
|
||||||
|
* **Vous êtes mort** - Éventuellement, si le joueur est mort.
|
|
@ -0,0 +1,70 @@
|
||||||
|
Entités pacifiques
|
||||||
|
==================
|
||||||
|
|
||||||
|
.. _`entité attaquante`: index.html#entites-attaquantes
|
||||||
|
.. _`pack de textures`: ../texture-pack.html
|
||||||
|
|
||||||
|
Chaque entité pacifique est en particulier une `entité attaquante`_,
|
||||||
|
et hérite donc de ses attributs, et peut alors être attaquée.
|
||||||
|
Ils sont cependant non-hostiles.
|
||||||
|
|
||||||
|
Il est possible d'interagir avec ces entités. En s'approchant d'elles, en
|
||||||
|
appuyant sur la touche ``T`` suivie de la direction où regarder, un échange
|
||||||
|
débute.
|
||||||
|
|
||||||
|
Si l'on s'adresse à un marchand, on devrait voir à l'écran l'inventaire du joueur
|
||||||
|
et l'inventaire du marchand. Les flèches haut et bas permettent de sélectionner
|
||||||
|
un objet, les touches droite et gauche de passer d'un inventaire à l'autre, et la
|
||||||
|
touche entrée valide l'action.
|
||||||
|
|
||||||
|
On dénombre actuellement 3 types d'entités pacifiques :
|
||||||
|
|
||||||
|
Tournesol
|
||||||
|
---------
|
||||||
|
|
||||||
|
Son nom est fixé à `sunflower`. Il a par défaut une **15** points de vie.
|
||||||
|
|
||||||
|
Interagir avec un tournesol n'a pas de réel intérêt, si ce n'est déclencher
|
||||||
|
le « pouvoir des fleurs !! » ou bien savoir que « le soleil est chaud
|
||||||
|
aujourd'hui ».
|
||||||
|
|
||||||
|
Dans le `pack de textures`_ ASCII, il est représenté par le caractère ``I``.
|
||||||
|
|
||||||
|
Dans le `pack de textures`_ écureuil, il est représenté par l'émoji ``🌻``.
|
||||||
|
|
||||||
|
|
||||||
|
Marchand
|
||||||
|
--------
|
||||||
|
|
||||||
|
Son nom est fixé à `merchant`. Il a par défaut **5** points de vie.
|
||||||
|
|
||||||
|
En interagissant avec un marchand, il est possible de lui acheter et de lui
|
||||||
|
vendre différents objets contre des Hazels, la monnaie du jeu.
|
||||||
|
Les prix sont fixés :
|
||||||
|
|
||||||
|
* Bombe : 4 Hazels
|
||||||
|
* Coeur : 3 Hazels
|
||||||
|
* Potion d'arrachage de corps : 14 Hazels
|
||||||
|
* Épée : 20 Hazels
|
||||||
|
* Bouclier : 16 Hazels
|
||||||
|
* Casque : 18 Hazels
|
||||||
|
* Plastron : 30 Hazels
|
||||||
|
|
||||||
|
Le marchand commence avec 75 Hazels en sa possession, contre 42 pour le joueur.
|
||||||
|
|
||||||
|
Dans le `pack de textures`_ ASCII, il est représenté par le caractère ``M``.
|
||||||
|
|
||||||
|
Dans le `pack de textures`_ écureuil, il est représenté par l'émoji ``🦜``.
|
||||||
|
|
||||||
|
Trompette
|
||||||
|
---------
|
||||||
|
|
||||||
|
Son nom est fixé à 'trumpet'. Une trompette est un familier, c'est à dire que
|
||||||
|
c'est une entité attaquante qui suit globalement le joueurs et attaque les monstres
|
||||||
|
qui se rapprochent trop du joueur.
|
||||||
|
|
||||||
|
Elle a 20 point de vie et une attaque de 3.
|
||||||
|
|
||||||
|
Dans le `pack de textures`_ ASCII, elle est représentée par le caractère ``/``.
|
||||||
|
|
||||||
|
Dans le `pack de textures`_ écureuil, elle est représentée par l'émoji ``🎺``.
|
|
@ -7,6 +7,7 @@ Entités
|
||||||
|
|
||||||
player
|
player
|
||||||
monsters
|
monsters
|
||||||
|
friendly
|
||||||
items
|
items
|
||||||
|
|
||||||
Entité
|
Entité
|
||||||
|
@ -38,11 +39,12 @@ Entité attaquante
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
.. _monstre: monsters.html
|
.. _monstre: monsters.html
|
||||||
|
.. _entité pacifique: friendly.html
|
||||||
.. _joueur: player.html
|
.. _joueur: player.html
|
||||||
|
|
||||||
Une entité attaquante (``FightingEntity``) est un type d'entités représentant
|
Une entité attaquante (``FightingEntity``) est un type d'entités représentant
|
||||||
les personnages présents sur la carte, pouvant alors se battre. Ce peut être
|
les personnages présents sur la carte, pouvant alors se battre. Ce peut être
|
||||||
un monstre_ ou bien le joueur_.
|
un monstre_, une `entité pacifique`_ ou bien le joueur_.
|
||||||
|
|
||||||
Elles disposent toutes, en plus des paramètres d'entité, des attributs suivants :
|
Elles disposent toutes, en plus des paramètres d'entité, des attributs suivants :
|
||||||
|
|
||||||
|
@ -77,3 +79,14 @@ en-dessous de 0 point de vie. À ce moment-là, l'entité est retirée de la car
|
||||||
|
|
||||||
Lorsqu'une entité en frappe une autre, celle-ci inflige autant de dégâts qu'elle
|
Lorsqu'une entité en frappe une autre, celle-ci inflige autant de dégâts qu'elle
|
||||||
n'a de force, et autant de points de vie sont perdus.
|
n'a de force, et autant de points de vie sont perdus.
|
||||||
|
|
||||||
|
|
||||||
|
Entité pacifique
|
||||||
|
----------------
|
||||||
|
|
||||||
|
Une entité pacifique (``FriendlyEntity``) est un cas particulier d'entité
|
||||||
|
attaquante. Contrairement aux montres, elles ne peuvent pas attaquer le joueur.
|
||||||
|
|
||||||
|
On peut parler à une entité pacifique en appuyant sur la touche ``T`` puis en
|
||||||
|
appuyant sur la direction dans laquelle on veut parler à l'entité.
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ Un objet dispose de deux paramètres :
|
||||||
Si l'objet est dans l'inventaire, renvoie son propriétaire.
|
Si l'objet est dans l'inventaire, renvoie son propriétaire.
|
||||||
|
|
||||||
|
|
||||||
Deux types d'objets sont pour l'instant présents :
|
Il y a plusieurs types d'objets :
|
||||||
|
|
||||||
|
|
||||||
Bombe
|
Bombe
|
||||||
|
@ -28,23 +28,94 @@ Bombe
|
||||||
.. _entités attaquantes: index.html#entite-attaquante
|
.. _entités attaquantes: index.html#entite-attaquante
|
||||||
|
|
||||||
Une bombe est un objet que l'on peut ramasser. Une fois ramassée, elle est placée
|
Une bombe est un objet que l'on peut ramasser. Une fois ramassée, elle est placée
|
||||||
dans l'inventaire. Le joueur peut ensuite lâcher la bombe, qui fera alors
|
dans l'inventaire. Le joueur peut ensuite utiliser la bombe, via l'inventaire
|
||||||
3 dégâts à toutes les `entités attaquantes`_ situées à moins de une case.
|
ou après l'avoir équipée, qui fera alors 3 dégâts à toutes les
|
||||||
|
`entités attaquantes`_ situées à moins de trois cases au bout de 4 ticks de jeu.
|
||||||
|
|
||||||
Elle est représentée dans le `pack de textures`_ ASCII par le caractère ``o``
|
Elle est représentée dans le `pack de textures`_ ASCII par le caractère ``o``
|
||||||
et dans le `pack de textures`_ écureuil par l'émoji ``💣``.
|
et dans le `pack de textures`_ écureuil par l'émoji ``💣``. Lors de l'explosion,
|
||||||
|
la bombe est remplacée par un symbole ``%`` ou l'émoji ``💥`` selon le pack de
|
||||||
|
textures utilisé.
|
||||||
|
|
||||||
.. note::
|
La bombe coûte 4 Hazels auprès des marchands.
|
||||||
|
|
||||||
La gestion de l'inventaire n'ayant pas encore été implémentée, il n'est à
|
|
||||||
l'heure actuelle pas possible de lancer une bombe.
|
|
||||||
|
|
||||||
|
|
||||||
Cœur
|
Cœur
|
||||||
----
|
----
|
||||||
|
|
||||||
Une cœur est un objet que l'on ne peut pas ramasser. Dès que le joueur s'en
|
Un cœur est un objet que l'on ne peut pas ramasser. Dès que le joueur s'en
|
||||||
approche, il est régénéré automatiquement de 3 points de vie, et le cœur disparaît.
|
approche ou qu'il l'achète auprès d'un marchand, il est régénéré automatiquement
|
||||||
|
de 3 points de vie, et le cœur disparaît.
|
||||||
|
|
||||||
Elle est représentée dans le `pack de textures`_ ASCII par le caractère ``❤``
|
Il est représenté dans le `pack de textures`_ ASCII par le caractère ``❤``
|
||||||
et dans le `pack de textures`_ écureuil par l'émoji ``💜``.
|
et dans le `pack de textures`_ écureuil par l'émoji ``💜``.
|
||||||
|
|
||||||
|
Le cœur coûte 3 Hazels auprès des marchands.
|
||||||
|
|
||||||
|
|
||||||
|
Potion d'arrachage de corps
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
Cette potion permet, une fois utilisée, d'échanger toutes ses caractéristiques
|
||||||
|
avec une autre entité aléatoire sur la carte. Cela inclut la force, la position,
|
||||||
|
l'icône, ...
|
||||||
|
|
||||||
|
Elle est représentée par les caractères ``I`` et ``🔀``
|
||||||
|
|
||||||
|
Cette potion coûte 14 Hazels auprès des marchands.
|
||||||
|
|
||||||
|
|
||||||
|
Épée
|
||||||
|
----
|
||||||
|
|
||||||
|
L'épée est un objet que l'on peut trouver uniquement par achat auprès d'un
|
||||||
|
marchand pour le coût de 20 Hazels. Une fois équipée, l'épée ajoute 3 de force
|
||||||
|
à son porteur.
|
||||||
|
|
||||||
|
Elle est représentée par les caractères ``†`` et ``🗡️``.
|
||||||
|
|
||||||
|
|
||||||
|
Bouclier
|
||||||
|
--------
|
||||||
|
|
||||||
|
Le bouclier est un objet que l'on peut trouver uniquement par achat auprès d'un
|
||||||
|
marchand pour le coût de 16 Hazels. Il s'équippe dans la main secondaire.
|
||||||
|
Une fois équipé, le bouclier ajoute 1 de
|
||||||
|
constitution à son porteur, lui permettant de parer mieux les coups.
|
||||||
|
|
||||||
|
Il est représenté par les caractères ``D`` et ``🛡️``.
|
||||||
|
|
||||||
|
Casque
|
||||||
|
------
|
||||||
|
|
||||||
|
Le casque est un objet que l'on peut trouver uniquement par achat auprès d'un
|
||||||
|
marchand pour le coût de 18 Hazels. Il s'équippe sur la tête.
|
||||||
|
Une fois équipé, le casque ajoute 2 de
|
||||||
|
constitution à son porteur, lui permettant de prendre moins de dêgats.
|
||||||
|
|
||||||
|
Il est représenté par les caractères ``0`` et ``⛑️``.
|
||||||
|
|
||||||
|
Plastron
|
||||||
|
--------
|
||||||
|
|
||||||
|
Le plastron est un objet que l'on peut trouver uniquement par achat auprès d'un
|
||||||
|
marchand pour le coût de 30 Hazels. Il s'équippe sur le corps.
|
||||||
|
Une fois équipé, le casque ajoute 4 de
|
||||||
|
constitution à son porteur, lui permettant de prendre moins de dêgats.
|
||||||
|
|
||||||
|
Il est représenté par les caractères ``(`` et ``🦺``.
|
||||||
|
|
||||||
|
Anneau
|
||||||
|
------
|
||||||
|
|
||||||
|
L'anneau est un objet que l'on peut trouver uniquement par achat auprès d'un
|
||||||
|
marchand. Il s'équippe sur la main secondaire.
|
||||||
|
Une fois équipé, l'anneau ajoute un bonus à une ou plusieurs statistiques du
|
||||||
|
joueur, améliorant sa capacité à se débarasser des monstres.
|
||||||
|
|
||||||
|
Il y a plusieurs types d'anneaux :
|
||||||
|
|
||||||
|
* **Anneau de coup critique**, qui augmente la chance de coup critique de 20%. Il coute 15 Hazels.
|
||||||
|
* **Anneau de gain d'expérience amélioré**, qui multiplie le gain d'expérience du joueur par 2. Il coûte 25 Hazels.
|
||||||
|
|
||||||
|
Un anneau est représenté par les caractères ``o`` et ``💍``.
|
|
@ -13,7 +13,7 @@ au plus vite sur le joueur pour le frapper selon l'algorithme de Dijkstra,
|
||||||
et s'il est suffisamment proche frappe le joueur et lui fait autant de dégâts
|
et s'il est suffisamment proche frappe le joueur et lui fait autant de dégâts
|
||||||
qu'il n'a de force.
|
qu'il n'a de force.
|
||||||
|
|
||||||
On dénombre actuellement 4 types de monstres :
|
On dénombre actuellement 5 types de monstres :
|
||||||
|
|
||||||
Hérisson
|
Hérisson
|
||||||
--------
|
--------
|
||||||
|
@ -40,6 +40,8 @@ Lapin
|
||||||
|
|
||||||
Son nom est fixé à `rabbit`. Il a par défaut une force à **1** et **15** points de vie.
|
Son nom est fixé à `rabbit`. Il a par défaut une force à **1** et **15** points de vie.
|
||||||
|
|
||||||
|
Il a une chance de coup critique de 30%.
|
||||||
|
|
||||||
Dans le `pack de textures`_ ASCII, il est représenté par le caractère ``Y``.
|
Dans le `pack de textures`_ ASCII, il est représenté par le caractère ``Y``.
|
||||||
|
|
||||||
Dans le `pack de textures`_ écureuil, il est représenté par l'émoji ``🐇``.
|
Dans le `pack de textures`_ écureuil, il est représenté par l'émoji ``🐇``.
|
||||||
|
@ -53,3 +55,14 @@ Son nom est fixé à `teddy_bear`. Il n'a pas de force et **50** points de vie.
|
||||||
Dans le `pack de textures`_ ASCII, il est représenté par le caractère ``8``.
|
Dans le `pack de textures`_ ASCII, il est représenté par le caractère ``8``.
|
||||||
|
|
||||||
Dans le `pack de textures`_ écureuil, il est représenté par l'émoji ``🧸``.
|
Dans le `pack de textures`_ écureuil, il est représenté par l'émoji ``🧸``.
|
||||||
|
|
||||||
|
|
||||||
|
Pygargue
|
||||||
|
---------
|
||||||
|
Son nom est fixé à `eagle`. Il a par défaut une force à **1000** et **5000** points de vie.
|
||||||
|
|
||||||
|
Il s'agit d'un boss difficilement tuable, qui apparait plus rarement que les autres monstres.
|
||||||
|
|
||||||
|
Dans le `pack de textures`_ ASCII, il est représenté par le caractère ``µ``.
|
||||||
|
|
||||||
|
Dans le `pack de textures`_ écureuil, il est représenté par l'émoji ``🦅``.
|
||||||
|
|
|
@ -17,6 +17,10 @@ Bienvenue dans la documentation de Squirrel Battle !
|
||||||
:target: https://pypi.org/project/squirrel-battle/
|
:target: https://pypi.org/project/squirrel-battle/
|
||||||
:alt: PyPI
|
:alt: PyPI
|
||||||
|
|
||||||
|
.. image:: https://img.shields.io/pypi/pyversions/squirrel-battle
|
||||||
|
:target: https://pypi.org/project/squirrel-battle/
|
||||||
|
:alt: Supported Python versions
|
||||||
|
|
||||||
.. image:: https://img.shields.io/pypi/dm/squirrel-battle
|
.. image:: https://img.shields.io/pypi/dm/squirrel-battle
|
||||||
:target: https://pypi.org/project/squirrel-battle/
|
:target: https://pypi.org/project/squirrel-battle/
|
||||||
:alt: PyPI downloads
|
:alt: PyPI downloads
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
sphinx
|
sphinx>=3.3
|
||||||
sphinx-rtd-theme
|
sphinx-rtd-theme>=0.5
|
||||||
|
|
|
@ -1,4 +1,38 @@
|
||||||
Paramètres
|
Paramètres
|
||||||
==========
|
==========
|
||||||
|
|
||||||
Pas encore documenté.
|
.. _pack de textures: texture-pack.html
|
||||||
|
|
||||||
|
Il est possible de changer les touches utilisées dans le jeu dans le menu des paramètres.
|
||||||
|
|
||||||
|
On peut aussi changer le `pack de textures`_ utilisé.
|
||||||
|
|
||||||
|
Touches
|
||||||
|
-------
|
||||||
|
|
||||||
|
Les touches utilisées de base sont :
|
||||||
|
|
||||||
|
* **Aller vers le haut** : z
|
||||||
|
* **Aller vers le haut (secondaire)** : ↑
|
||||||
|
* **Aller vers le bas** : s
|
||||||
|
* **Aller vers le bas (secondaire)** : ↓
|
||||||
|
* **Aller à droite** : d
|
||||||
|
* **Aller à droite (secondaire)** : →
|
||||||
|
* **Aller à gauche** : q
|
||||||
|
* **Aller à gauche (secondaire)** : ←
|
||||||
|
* **Valider le choix** : Entrée
|
||||||
|
* **Inventaire** : i
|
||||||
|
* **Utiliser un objet** : u
|
||||||
|
* **Équiper un objet** : e
|
||||||
|
* **Lacher un objet** : r
|
||||||
|
* **Parler** : t
|
||||||
|
* **Attendre** : w
|
||||||
|
|
||||||
|
Autres
|
||||||
|
------
|
||||||
|
|
||||||
|
.. _ascii: texture-pack.html#Pack ASCII
|
||||||
|
.. _squirrel: texture-pack.html#Pack Écureuil
|
||||||
|
|
||||||
|
* **Texture pack utilisé** : parmi ascii_ et squirrel_
|
||||||
|
* **Langue utilisée** : parmi anglais, français, espagnol, allemand
|
|
@ -9,18 +9,30 @@ Pack de textures
|
||||||
|
|
||||||
.. _Joueur: entities/player.html
|
.. _Joueur: entities/player.html
|
||||||
.. _Hérisson: entities/monsters.html#herisson
|
.. _Hérisson: entities/monsters.html#herisson
|
||||||
.. _Cœur: entities/items.html#coeur
|
|
||||||
.. _Bombe: entities/items.html#bombe
|
|
||||||
.. _Lapin: entities/monsters.html#lapin
|
.. _Lapin: entities/monsters.html#lapin
|
||||||
.. _Tigre: entities/monsters.html#tigre
|
.. _Tigre: entities/monsters.html#tigre
|
||||||
.. _Nounours: entities/monsters.html#nounours
|
.. _Nounours: entities/monsters.html#nounours
|
||||||
|
.. _Tournesol: entities/friendly.html#tournesol
|
||||||
|
.. _Marchand: entities/friendly.html#marchand
|
||||||
|
.. _Cœur: entities/items.html#coeur
|
||||||
|
.. _Bombe: entities/items.html#bombe
|
||||||
|
.. _Explosion: entities/items.html#bombe
|
||||||
|
.. _Potion d'arrachage de corps: entities/items.html
|
||||||
|
.. _Épée: entities/items.html#epee
|
||||||
|
.. _Bouclier: entities/items.html#bouclier
|
||||||
|
.. _Hazel: ../index.html
|
||||||
|
.. _Plastron: ../entities/items.html#plastron
|
||||||
|
.. _Pygargue: ../entities/monsters.html#Pygargue
|
||||||
|
.. _Casque: ../entities/items.html#Casque
|
||||||
|
.. _Anneau: ../entities/items.html#Anneau
|
||||||
|
.. _Trompette: ../entities/items.html#Trompette
|
||||||
|
|
||||||
Chaque entité_ et chaque tuile_ de la carte_ est associé à un caractère pour
|
Chaque entité_ et chaque tuile_ de la carte_ est associé à un caractère pour
|
||||||
être affiché dans le terminal. Cependant, afin de pouvoir proposer plusieurs
|
être affiché dans le terminal. Cependant, afin de pouvoir proposer plusieurs
|
||||||
expériences graphiques (notamment en fonction du support des émojis), différents
|
expériences graphiques (notamment en fonction du support des émojis), différents
|
||||||
packs de textures sont proposés.
|
packs de textures sont proposés.
|
||||||
|
|
||||||
Il est possible de changer de pack dans les paramètres.
|
Il est possible de changer de pack dans les paramètres_.
|
||||||
|
|
||||||
Les packs de textures peuvent influencer la taille que prennent les tuiles_,
|
Les packs de textures peuvent influencer la taille que prennent les tuiles_,
|
||||||
en raison du fait que les émojis ne sont pas monospace.
|
en raison du fait que les émojis ne sont pas monospace.
|
||||||
|
@ -39,11 +51,23 @@ Chaque tuile fait un caractère de large.
|
||||||
* Entités
|
* Entités
|
||||||
* Joueur_ : ``@``
|
* Joueur_ : ``@``
|
||||||
* Hérisson_ : ``*``
|
* Hérisson_ : ``*``
|
||||||
* Cœur_ : ``❤``
|
|
||||||
* Bombe_ : ``o``
|
|
||||||
* Lapin_ : ``Y``
|
* Lapin_ : ``Y``
|
||||||
* Tigre_ : ``n``
|
* Tigre_ : ``n``
|
||||||
* Nounours_ : ``8``
|
* Nounours_ : ``8``
|
||||||
|
* Tournesol_ : ``I``
|
||||||
|
* Marchand_ : ``M``
|
||||||
|
* Cœur_ : ``❤``
|
||||||
|
* Bombe_ : ``o``
|
||||||
|
* Explosion_ : ``%``
|
||||||
|
* `Potion d'arrachage de corps`_ : ``S``
|
||||||
|
* Épée_ : ``†``
|
||||||
|
* Bouclier_ : ``D``
|
||||||
|
* Hazel_ : ``¤``
|
||||||
|
* Plastron_ : ``(``
|
||||||
|
* Pygargue_ : ``µ``
|
||||||
|
* Casque_ : ``0``
|
||||||
|
* Anneau_ : ``o``
|
||||||
|
* Trompette_ : ``/``
|
||||||
|
|
||||||
|
|
||||||
Pack Écureuil
|
Pack Écureuil
|
||||||
|
@ -58,8 +82,20 @@ Chaque tuile fait 2 caractères de large pour afficher les émojis proprement.
|
||||||
* Entités
|
* Entités
|
||||||
* Joueur_ : ``🐿``
|
* Joueur_ : ``🐿``
|
||||||
* Hérisson_ : ``🦔``
|
* Hérisson_ : ``🦔``
|
||||||
* Cœur_ : ``💜``
|
|
||||||
* Bombe_ : ``💣``
|
|
||||||
* Lapin_ : ``🐇``
|
* Lapin_ : ``🐇``
|
||||||
* Tigre_ : ``🐅``
|
* Tigre_ : ``🐅``
|
||||||
* Nounours_ : ``🧸``
|
* Nounours_ : ``🧸``
|
||||||
|
* Tournesol_ : ``🌻``
|
||||||
|
* Marchand_ : ``🦜``
|
||||||
|
* Cœur_ : ``💜``
|
||||||
|
* Bombe_ : ``💣``
|
||||||
|
* Explosion_ : ``💥``
|
||||||
|
* `Potion d'arrachage de corps`_ : ``🔀``
|
||||||
|
* Épée_ : ``🗡️``
|
||||||
|
* Bouclier_ : ``🛡️``
|
||||||
|
* Hazel_ : ``🌰``
|
||||||
|
* Plastron_ : ``🦺``
|
||||||
|
* Pygargue_ : ``🦅``
|
||||||
|
* Casque_ : ``⛑️``
|
||||||
|
* Anneau_ : ``💍``
|
||||||
|
* Trompette_ : ``🎺``
|
||||||
|
|
|
@ -0,0 +1,44 @@
|
||||||
|
|
||||||
|
⋀
|
||||||
|
┃|┃
|
||||||
|
┃|┃ ▓▓▒ ▓▓
|
||||||
|
┃|┃ ▓▓ ▓▓▒
|
||||||
|
┃|┃ ▓▓▓ ▓▓ ▓▓▓ ▒▒▒▒▒▒▒▒▒
|
||||||
|
┃|┃ ▓▓▓▓▓▓▓▓▓▓▓▓▓ ▒▒▒▒▒▒▒▒▒▒▒▒▒
|
||||||
|
┃|┃ ▓▓▓▓▓▓▓▓▓▓▓▓▓ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
|
||||||
|
┃|┃ ▓▓▓▬█▓▓▓▓▓▓▬█▓▓▓ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
|
||||||
|
┃|┃ ▓▓▓▓░██░░▓▓░░██░▓▓▓▓ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
|
||||||
|
━━▓▓▓▓━━ ▓▓░░░░░░░░ ░░░░░░░░▓▓ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒
|
||||||
|
▓▓▓▓▓▓ ▓░░░░░░░░░░░░░░░░░░░░▓▓▒▒▒▒▒▒▒▒▒▒▒▒
|
||||||
|
┃ ▓▓▓▓▓ ▓░░░░░░░░▄▄▄▄░░░░░░░▓▒▒▒▒▒▒▒▒▒▒▒▒▒
|
||||||
|
┃ ▓▓▓▓▓ ▓▓░░░░░░░░░░░░░░░▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
|
||||||
|
▓▓▓▓ ▓▓▓▓░░░░░░░▓▓ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
|
||||||
|
▓▓▓▓▓▓▒▒░░░░░░░░░▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
|
||||||
|
▓▓▓▓▒░░░░░░░░░░░░▓▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒
|
||||||
|
▓▓▒░░░░░░░░░░░░░▓▓▓▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒
|
||||||
|
▓▒▒░░░░░░░░░░░░▓▓▓▓▒▒▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒
|
||||||
|
▓▓▒░░░░░░░░░░░░░░░▓▒▒▒▒▒▒▓▓▓▓▓▒▒▒▒▒▒▒▒▒
|
||||||
|
▓▓▒▒░░░░░░░░░░░░░░▒▒▒▒▒▒▒▒▒▓▓▒▒▒▒▒▒▒▒▒
|
||||||
|
▓▓▓▒░░░░░░░░░░░░░▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒
|
||||||
|
▓▓▒▓▒▒░░░░░░░░░░░░░░▓▓▓▓▓▓▓▒▒▒▒▒▒▒▒▒▒▒
|
||||||
|
▓▓▓▓▓▒▒░░░░░░░░░░░░░░░░░▓▓▓▓▓▓▓▓▒▒▒▒▒▒▒
|
||||||
|
▓▓▓▓▓▓▓▓░░░░░░░░░░░░░░░░░░░▓▓▓▓▓▓▓▒▒▒▒▒
|
||||||
|
▓▓▓▓▓▓▓▓░░░░░░░░░░░░░░░░░░░▓▓▓▓▓▓▓▓▒▒▒
|
||||||
|
▓▓▓▓▓▓▓▓░░░░░░░░░░░░░░░░░░░░▓▓▓▓▓▓▓▓▒
|
||||||
|
▓▓▓▓▓▓▓▓░░░░░░░░░░░░░░░░░░░░▓▓▓▓▓▓▓
|
||||||
|
▓▓▓▓▓▓▓▓▓▒░░░░░░░░░░░░░░░░░▓▓▓▓▓▓▓▓
|
||||||
|
▓▓▓▓▓▓▓▓▓▒▒░░░░░░░░░░░░░░▓▓▓▓▓▓▓▓ ░
|
||||||
|
▓▓▓▓▓▓▓▓▓▒░░░░░░░░░░░░▓▓▓▓▓▓▓▓▓ ░░
|
||||||
|
▓▓▓▓▓▓▓▓▒▒░░░▒▒▒▒░░░░░░▓▓░▒▒▒▓▓▓▓▓▓▓▓▓▓░░░ ░
|
||||||
|
▓▓▓▓▓▓▓▒░░░░░░░░░▒░░░░░░░░░░░░▒▒▒▓▓▓▓▓▓▓▓░░ ░░▒
|
||||||
|
░ ░░░░░▒░░░░░░▒░░░▒░░░░░░░░░░░░░░░░░▒▒▒▒▒▒░░░░░░░▒
|
||||||
|
▒▒░░▓▓░░▒░░░░░░░░▒░░░░░░▒░░░░░░░░▒░░░░░░░░░░▒░░░░░▒ ░░
|
||||||
|
▒▒▒▒▒▓▒▒▓░░░░░░░░░▒░░░░░░░░▒░░░░░░░░▒░░░░░░░░▒░░░░░░░░░░░░
|
||||||
|
▒▒█▒█▒▒▒▓░░▒░░░░░░░░░░░░░░░▒░░░░░░░░▒░░░░░░░░░░░░░░░░░░░░░
|
||||||
|
▒▒▒▒█▒▒▒▒░░░░▒░░░▒░░░░░░░░░░░░░░░░░░░░░░▒░░░░░░░░░░░░▒░░░
|
||||||
|
▓█▒▒▒▒█▒█▒▒▒▒░░▒░░░░░▒░░░░▒░░░░░░░░░░░░░░░░░▒░░░░▒░░░░░░░▒░░░░░▒▒
|
||||||
|
██▒▒▒▒▒▒▒▒▒▒▒▒░░░░░░▒░░░░░░▒░░░░░░░░▒░░░░░░▒░░░░░░▒░░░░░▒░░░░░
|
||||||
|
▒▒▒▒█▒▒▒▒▒▒▒░░░░░░░░░░▒░░░░░░░░░░▒░░░░░░░░░░░▒░░░░░░░░░░░░░░░
|
||||||
|
▒▒█▒▒▒▒▒░▒░▒░░░░▓▓▓░░░░░░░▒░░░░▒░░░▒░░░░░░░▓▓░░░░░░░░░░░░ ░
|
||||||
|
▒▒▒▒▒▒▒░▒░░░▓▓▓▓▓▓░░░░░░░▒░░░░░░░░▒░░░░▓▓▓▓▓▓░░░░░░░░ ░
|
||||||
|
░▓▓▓▓▓▓░░░░░░▒░░░░░░░░▒░░░░░░▓▓▓▓▓░░░ ░ ░░
|
|
@ -1,8 +1,8 @@
|
||||||
1 6
|
1 6
|
||||||
####### #############
|
####### #############
|
||||||
#.....# #...........#
|
#.H...# #...........#
|
||||||
#.....# #####...........#
|
#.....# #####...........#
|
||||||
#.....# #...............#
|
#.....# #............H..#
|
||||||
#.##### #.###...........#
|
#.##### #.###...........#
|
||||||
#.# #.# #...........#
|
#.# #.# #...........#
|
||||||
#.# #.# #############
|
#.# #.# #############
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
1 17
|
1 17
|
||||||
########### #########
|
########### #########
|
||||||
#.........# #.......#
|
#....H....# #.......#
|
||||||
#.........# ############.......#
|
#.........# ############.......#
|
||||||
#.........###############..........#.......##############
|
#.........###############..........#.......##############
|
||||||
#.........#........................#....................#
|
#.........#........................#....................#
|
||||||
|
@ -13,7 +13,7 @@
|
||||||
########.##########......# #.........# #.........#
|
########.##########......# #.........# #.........#
|
||||||
#...........##......# #.........# #.........#
|
#...........##......# #.........# #.........#
|
||||||
#...........##......# #.........# #.........#
|
#...........##......# #.........# #.........#
|
||||||
#...........##......# #.........# ################.######
|
#...........##..H...# #.........# ################.######
|
||||||
#...........##......# #.........# #.................############
|
#...........##......# #.........# #.................############
|
||||||
#...........##......# ########.########.......#.........#..........#
|
#...........##......# ########.########.......#.........#..........#
|
||||||
#...........##......# #...............#.......#.........#..........#
|
#...........##......# #...............#.......#.........#..........#
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
1 6
|
||||||
|
################################################################################
|
||||||
|
#..............................................................................#
|
||||||
|
#..#...........................................................................#
|
||||||
|
#...........#..................................................................#
|
||||||
|
#..............................................................................#
|
||||||
|
#..............................................................................#
|
||||||
|
#..............................................................................#
|
||||||
|
#..............................................................................#
|
||||||
|
#..............................................................................#
|
||||||
|
#..............................................................................#
|
||||||
|
#..............................................................................#
|
||||||
|
#..............................................................................#
|
||||||
|
#..............................................................................#
|
||||||
|
#..............................................................................#
|
||||||
|
#..............................................................................#
|
||||||
|
#..............................................................................#
|
||||||
|
#..............................................................................#
|
||||||
|
#..............................................................................#
|
||||||
|
#..............................................................................#
|
||||||
|
#..............................................................................#
|
||||||
|
#..............................................................................#
|
||||||
|
#..............................................................................#
|
||||||
|
#..............................................................................#
|
||||||
|
#..............................................................................#
|
||||||
|
#..............................................................................#
|
||||||
|
#..............................................................................#
|
||||||
|
#..............................................................................#
|
||||||
|
#..............................................................................#
|
||||||
|
#..............................................................................#
|
||||||
|
#..............................................................................#
|
||||||
|
#..............................................................................#
|
||||||
|
#..............................................................................#
|
||||||
|
#..............................................................................#
|
||||||
|
#..............................................................................#
|
||||||
|
#..............................................................................#
|
||||||
|
#..............................................................................#
|
||||||
|
#..............................................................................#
|
||||||
|
#..............................................................................#
|
||||||
|
#..............................................................................#
|
||||||
|
################################################################################
|
|
@ -0,0 +1,97 @@
|
||||||
|
# Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
import curses
|
||||||
|
|
||||||
|
from ..display.display import Box, Display
|
||||||
|
from ..game import Game
|
||||||
|
from ..resources import ResourceManager
|
||||||
|
from ..translations import gettext as _
|
||||||
|
|
||||||
|
|
||||||
|
class CreditsDisplay(Display):
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.box = Box(*args, **kwargs)
|
||||||
|
self.pad = self.newpad(1, 1)
|
||||||
|
self.ascii_art_displayed = False
|
||||||
|
|
||||||
|
def update(self, game: Game) -> None:
|
||||||
|
return
|
||||||
|
|
||||||
|
def display(self) -> None:
|
||||||
|
self.box.refresh(self.y, self.x, self.height, self.width)
|
||||||
|
self.box.display()
|
||||||
|
self.pad.erase()
|
||||||
|
|
||||||
|
messages = [
|
||||||
|
_("Credits"),
|
||||||
|
"",
|
||||||
|
"Squirrel Battle",
|
||||||
|
"",
|
||||||
|
_("Developers:"),
|
||||||
|
"Yohann \"ÿnérant\" D'ANELLO",
|
||||||
|
"Mathilde \"eichhornchen\" DÉPRÉS",
|
||||||
|
"Nicolas \"nicomarg\" MARGULIES",
|
||||||
|
"Charles \"charsle\" PEYRAT",
|
||||||
|
"",
|
||||||
|
_("Translators:"),
|
||||||
|
"Hugo \"ifugao\" JACOB (español)",
|
||||||
|
]
|
||||||
|
|
||||||
|
for i, msg in enumerate(messages):
|
||||||
|
self.addstr(self.pad, i + (self.height - len(messages)) // 2,
|
||||||
|
(self.width - len(msg)) // 2, msg,
|
||||||
|
bold=(i == 0), italic=(":" in msg))
|
||||||
|
|
||||||
|
if self.ascii_art_displayed:
|
||||||
|
self.display_ascii_art()
|
||||||
|
|
||||||
|
self.refresh_pad(self.pad, 0, 0, self.y + 1, self.x + 1,
|
||||||
|
self.height + self.y - 2,
|
||||||
|
self.width + self.x - 2)
|
||||||
|
|
||||||
|
def display_ascii_art(self) -> None:
|
||||||
|
with open(ResourceManager.get_asset_path("ascii-art-ecureuil.txt"))\
|
||||||
|
as f:
|
||||||
|
ascii_art = f.read().split("\n")
|
||||||
|
|
||||||
|
height, width = len(ascii_art), len(ascii_art[0])
|
||||||
|
y_offset, x_offset = (self.height - height) // 2,\
|
||||||
|
(self.width - width) // 2
|
||||||
|
|
||||||
|
for i, line in enumerate(ascii_art):
|
||||||
|
for j, c in enumerate(line):
|
||||||
|
bg_color = curses.COLOR_WHITE
|
||||||
|
fg_color = curses.COLOR_BLACK
|
||||||
|
bold = False
|
||||||
|
if c == ' ':
|
||||||
|
bg_color = curses.COLOR_BLACK
|
||||||
|
elif c == '━' or c == '┃' or c == '⋀':
|
||||||
|
bold = True
|
||||||
|
fg_color = curses.COLOR_WHITE
|
||||||
|
bg_color = curses.COLOR_BLACK
|
||||||
|
elif c == '|':
|
||||||
|
bold = True # c = '┃'
|
||||||
|
fg_color = (100, 700, 1000)
|
||||||
|
bg_color = curses.COLOR_BLACK
|
||||||
|
elif c == '▓':
|
||||||
|
fg_color = (700, 300, 0)
|
||||||
|
elif c == '▒':
|
||||||
|
fg_color = (700, 300, 0)
|
||||||
|
bg_color = curses.COLOR_BLACK
|
||||||
|
elif c == '░':
|
||||||
|
fg_color = (350, 150, 0)
|
||||||
|
elif c == '█':
|
||||||
|
fg_color = (0, 0, 0)
|
||||||
|
bg_color = curses.COLOR_BLACK
|
||||||
|
elif c == '▬':
|
||||||
|
c = '█'
|
||||||
|
fg_color = (1000, 1000, 1000)
|
||||||
|
bg_color = curses.COLOR_BLACK
|
||||||
|
self.addstr(self.pad, y_offset + i, x_offset + j, c,
|
||||||
|
fg_color, bg_color, bold=bold)
|
||||||
|
|
||||||
|
def handle_click(self, y: int, x: int, attr: int, game: Game) -> None:
|
||||||
|
if self.pad.inch(y - 1, x - 1) != ord(" "):
|
||||||
|
self.ascii_art_displayed = True
|
|
@ -2,7 +2,8 @@
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
import curses
|
import curses
|
||||||
from typing import Any, Optional, Union
|
import sys
|
||||||
|
from typing import Any, Optional, Tuple, Union
|
||||||
|
|
||||||
from squirrelbattle.display.texturepack import TexturePack
|
from squirrelbattle.display.texturepack import TexturePack
|
||||||
from squirrelbattle.game import Game
|
from squirrelbattle.game import Game
|
||||||
|
@ -16,14 +17,24 @@ class Display:
|
||||||
height: int
|
height: int
|
||||||
pad: Any
|
pad: Any
|
||||||
|
|
||||||
|
_color_pairs = {(curses.COLOR_WHITE, curses.COLOR_BLACK): 0}
|
||||||
|
_colors_rgb = {}
|
||||||
|
|
||||||
def __init__(self, screen: Any, pack: Optional[TexturePack] = None):
|
def __init__(self, screen: Any, pack: Optional[TexturePack] = None):
|
||||||
self.screen = screen
|
self.screen = screen
|
||||||
self.pack = pack or TexturePack.get_pack("ascii")
|
self.pack = pack or TexturePack.get_pack("ascii")
|
||||||
|
|
||||||
def newpad(self, height: int, width: int) -> Union[FakePad, Any]:
|
def newpad(self, height: int, width: int) -> Union[FakePad, Any]:
|
||||||
|
"""
|
||||||
|
Overwrites the native curses function of the same name.
|
||||||
|
"""
|
||||||
return curses.newpad(height, width) if self.screen else FakePad()
|
return curses.newpad(height, width) if self.screen else FakePad()
|
||||||
|
|
||||||
def truncate(self, msg: str, height: int, width: int) -> str:
|
def truncate(self, msg: str, height: int, width: int) -> str:
|
||||||
|
"""
|
||||||
|
Truncates a string into a string adapted to the width and height of
|
||||||
|
the screen.
|
||||||
|
"""
|
||||||
height = max(0, height)
|
height = max(0, height)
|
||||||
width = max(0, width)
|
width = max(0, width)
|
||||||
lines = msg.split("\n")
|
lines = msg.split("\n")
|
||||||
|
@ -31,15 +42,86 @@ class Display:
|
||||||
lines = [line[:width] for line in lines]
|
lines = [line[:width] for line in lines]
|
||||||
return "\n".join(lines)
|
return "\n".join(lines)
|
||||||
|
|
||||||
def addstr(self, pad: Any, y: int, x: int, msg: str, *options) -> None:
|
def translate_color(self, color: Union[int, Tuple[int, int, int]]) -> int:
|
||||||
"""
|
"""
|
||||||
Display a message onto the pad.
|
Translates a tuple (R, G, B) into a curses color index.
|
||||||
|
If we already have a color index, then nothing is processed.
|
||||||
|
If this is a tuple, we construct a new color index if non-existing
|
||||||
|
and we return this index.
|
||||||
|
The values of R, G and B must be between 0 and 1000, and not
|
||||||
|
between 0 and 255.
|
||||||
|
"""
|
||||||
|
if isinstance(color, tuple):
|
||||||
|
# The color is a tuple (R, G, B), that is potentially unknown.
|
||||||
|
# We translate it into a curses color number.
|
||||||
|
if color not in self._colors_rgb:
|
||||||
|
# The color does not exist, we create it.
|
||||||
|
color_nb = len(self._colors_rgb) + 8
|
||||||
|
self.init_color(color_nb, color[0], color[1], color[2])
|
||||||
|
self._colors_rgb[color] = color_nb
|
||||||
|
color = self._colors_rgb[color]
|
||||||
|
return color
|
||||||
|
|
||||||
|
def addstr(self, pad: Any, y: int, x: int, msg: str,
|
||||||
|
fg_color: Union[int, Tuple[int, int, int]] = curses.COLOR_WHITE,
|
||||||
|
bg_color: Union[int, Tuple[int, int, int]] = curses.COLOR_BLACK,
|
||||||
|
*, altcharset: bool = False, blink: bool = False,
|
||||||
|
bold: bool = False, dim: bool = False, invis: bool = False,
|
||||||
|
italic: bool = False, normal: bool = False,
|
||||||
|
protect: bool = False, reverse: bool = False,
|
||||||
|
standout: bool = False, underline: bool = False,
|
||||||
|
horizontal: bool = False, left: bool = False,
|
||||||
|
low: bool = False, right: bool = False, top: bool = False,
|
||||||
|
vertical: bool = False, chartext: bool = False) -> None:
|
||||||
|
"""
|
||||||
|
Displays a message onto the pad.
|
||||||
If the message is too large, it is truncated vertically and horizontally
|
If the message is too large, it is truncated vertically and horizontally
|
||||||
|
The text can be bold, italic, blinking, ... if the right parameters are
|
||||||
|
given. These parameters are translated into curses attributes.
|
||||||
|
The foreground and background colors can be given as curses constants
|
||||||
|
(curses.COLOR_*), or by giving a tuple (R, G, B) that corresponds to
|
||||||
|
the color. R, G, B must be between 0 and 1000, and not 0 and 255.
|
||||||
"""
|
"""
|
||||||
height, width = pad.getmaxyx()
|
height, width = pad.getmaxyx()
|
||||||
|
# Truncate message if it is too large
|
||||||
msg = self.truncate(msg, height - y, width - x - 1)
|
msg = self.truncate(msg, height - y, width - x - 1)
|
||||||
if msg.replace("\n", "") and x >= 0 and y >= 0:
|
if msg.replace("\n", "") and x >= 0 and y >= 0:
|
||||||
return pad.addstr(y, x, msg, *options)
|
fg_color = self.translate_color(fg_color)
|
||||||
|
bg_color = self.translate_color(bg_color)
|
||||||
|
|
||||||
|
# Get the pair number for the tuple (fg, bg)
|
||||||
|
# If it does not exist, create it and give a new unique id.
|
||||||
|
if (fg_color, bg_color) in self._color_pairs:
|
||||||
|
pair_nb = self._color_pairs[(fg_color, bg_color)]
|
||||||
|
else:
|
||||||
|
pair_nb = len(self._color_pairs)
|
||||||
|
self.init_pair(pair_nb, fg_color, bg_color)
|
||||||
|
self._color_pairs[(fg_color, bg_color)] = pair_nb
|
||||||
|
|
||||||
|
# Compute curses attributes from the parameters
|
||||||
|
attr = self.color_pair(pair_nb)
|
||||||
|
attr |= curses.A_ALTCHARSET if altcharset else 0
|
||||||
|
attr |= curses.A_BLINK if blink else 0
|
||||||
|
attr |= curses.A_BOLD if bold else 0
|
||||||
|
attr |= curses.A_DIM if dim else 0
|
||||||
|
attr |= curses.A_INVIS if invis else 0
|
||||||
|
# Italic is supported since Python 3.7
|
||||||
|
italic &= sys.version_info >= (3, 7,)
|
||||||
|
attr |= curses.A_ITALIC if italic else 0
|
||||||
|
attr |= curses.A_NORMAL if normal else 0
|
||||||
|
attr |= curses.A_PROTECT if protect else 0
|
||||||
|
attr |= curses.A_REVERSE if reverse else 0
|
||||||
|
attr |= curses.A_STANDOUT if standout else 0
|
||||||
|
attr |= curses.A_UNDERLINE if underline else 0
|
||||||
|
attr |= curses.A_HORIZONTAL if horizontal else 0
|
||||||
|
attr |= curses.A_LEFT if left else 0
|
||||||
|
attr |= curses.A_LOW if low else 0
|
||||||
|
attr |= curses.A_RIGHT if right else 0
|
||||||
|
attr |= curses.A_TOP if top else 0
|
||||||
|
attr |= curses.A_VERTICAL if vertical else 0
|
||||||
|
attr |= curses.A_CHARTEXT if chartext else 0
|
||||||
|
|
||||||
|
return pad.addstr(y, x, msg, attr)
|
||||||
|
|
||||||
def init_pair(self, number: int, foreground: int, background: int) -> None:
|
def init_pair(self, number: int, foreground: int, background: int) -> None:
|
||||||
return curses.init_pair(number, foreground, background) \
|
return curses.init_pair(number, foreground, background) \
|
||||||
|
@ -48,17 +130,28 @@ class Display:
|
||||||
def color_pair(self, number: int) -> int:
|
def color_pair(self, number: int) -> int:
|
||||||
return curses.color_pair(number) if self.screen else 0
|
return curses.color_pair(number) if self.screen else 0
|
||||||
|
|
||||||
|
def init_color(self, number: int, red: int, green: int, blue: int) -> None:
|
||||||
|
return curses.init_color(number, red, green, blue) \
|
||||||
|
if self.screen else None
|
||||||
|
|
||||||
def resize(self, y: int, x: int, height: int, width: int,
|
def resize(self, y: int, x: int, height: int, width: int,
|
||||||
resize_pad: bool = True) -> None:
|
resize_pad: bool = True) -> None:
|
||||||
|
"""
|
||||||
|
Resizes a pad.
|
||||||
|
"""
|
||||||
self.x = x
|
self.x = x
|
||||||
self.y = y
|
self.y = y
|
||||||
self.width = width
|
self.width = width
|
||||||
self.height = height
|
self.height = height
|
||||||
if hasattr(self, "pad") and resize_pad and \
|
if hasattr(self, "pad") and resize_pad and \
|
||||||
self.height >= 0 and self.width >= 0:
|
self.height >= 0 and self.width >= 0:
|
||||||
|
self.pad.erase()
|
||||||
self.pad.resize(self.height + 1, self.width + 1)
|
self.pad.resize(self.height + 1, self.width + 1)
|
||||||
|
|
||||||
def refresh(self, *args, resize_pad: bool = True) -> None:
|
def refresh(self, *args, resize_pad: bool = True) -> None:
|
||||||
|
"""
|
||||||
|
Refreshes a pad
|
||||||
|
"""
|
||||||
if len(args) == 4:
|
if len(args) == 4:
|
||||||
self.resize(*args, resize_pad)
|
self.resize(*args, resize_pad)
|
||||||
self.display()
|
self.display()
|
||||||
|
@ -67,10 +160,10 @@ class Display:
|
||||||
window_y: int, window_x: int,
|
window_y: int, window_x: int,
|
||||||
last_y: int, last_x: int) -> None:
|
last_y: int, last_x: int) -> None:
|
||||||
"""
|
"""
|
||||||
Refresh a pad on a part of the window.
|
Refreshes a pad on a part of the window.
|
||||||
The refresh starts at coordinates (top_y, top_x) from the pad,
|
The refresh starts at coordinates (top_y, top_x) from the pad,
|
||||||
and is drawn from (window_y, window_x) to (last_y, last_x).
|
and is drawn from (window_y, window_x) to (last_y, last_x).
|
||||||
If coordinates are invalid (negative indexes/length..., then nothing
|
If coordinates are invalid (negative indexes/length...), then nothing
|
||||||
is drawn and no error is raised.
|
is drawn and no error is raised.
|
||||||
"""
|
"""
|
||||||
top_y, top_x = max(0, top_y), max(0, top_x)
|
top_y, top_x = max(0, top_y), max(0, top_x)
|
||||||
|
@ -82,17 +175,26 @@ class Display:
|
||||||
|
|
||||||
if last_y >= window_y and last_x >= window_x:
|
if last_y >= window_y and last_x >= window_x:
|
||||||
# Refresh the pad only if coordinates are valid
|
# Refresh the pad only if coordinates are valid
|
||||||
pad.refresh(top_y, top_x, window_y, window_x, last_y, last_x)
|
pad.noutrefresh(top_y, top_x, window_y, window_x, last_y, last_x)
|
||||||
|
|
||||||
def display(self) -> None:
|
def display(self) -> None:
|
||||||
|
"""
|
||||||
|
Draw the content of the display and refresh pads.
|
||||||
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def handle_click(self, y: int, x: int, game: Game) -> None:
|
def update(self, game: Game) -> None:
|
||||||
|
"""
|
||||||
|
The game state was updated.
|
||||||
|
Indicate what to do with the new state.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def handle_click(self, y: int, x: int, attr: int, game: Game) -> None:
|
||||||
"""
|
"""
|
||||||
A mouse click was performed on the coordinates (y, x) of the pad.
|
A mouse click was performed on the coordinates (y, x) of the pad.
|
||||||
Maybe it can do something.
|
Maybe it should do something.
|
||||||
"""
|
"""
|
||||||
pass
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def rows(self) -> int:
|
def rows(self) -> int:
|
||||||
|
@ -104,7 +206,9 @@ class Display:
|
||||||
|
|
||||||
|
|
||||||
class VerticalSplit(Display):
|
class VerticalSplit(Display):
|
||||||
|
"""
|
||||||
|
A class to split the screen in two vertically with a pretty line.
|
||||||
|
"""
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.pad = self.newpad(self.rows, 1)
|
self.pad = self.newpad(self.rows, 1)
|
||||||
|
@ -125,7 +229,9 @@ class VerticalSplit(Display):
|
||||||
|
|
||||||
|
|
||||||
class HorizontalSplit(Display):
|
class HorizontalSplit(Display):
|
||||||
|
"""
|
||||||
|
A class to split the screen in two horizontally with a pretty line.
|
||||||
|
"""
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.pad = self.newpad(1, self.cols)
|
self.pad = self.newpad(1, self.cols)
|
||||||
|
@ -146,23 +252,32 @@ class HorizontalSplit(Display):
|
||||||
|
|
||||||
|
|
||||||
class Box(Display):
|
class Box(Display):
|
||||||
|
"""
|
||||||
|
A class for pretty boxes to print menus and other content.
|
||||||
|
"""
|
||||||
|
title: str = ""
|
||||||
|
|
||||||
|
def update_title(self, title: str) -> None:
|
||||||
|
self.title = title
|
||||||
|
|
||||||
def __init__(self, *args, fg_border_color: Optional[int] = None, **kwargs):
|
def __init__(self, *args, fg_border_color: Optional[int] = None, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.pad = self.newpad(self.rows, self.cols)
|
self.pad = self.newpad(self.rows, self.cols)
|
||||||
self.fg_border_color = fg_border_color or curses.COLOR_WHITE
|
self.fg_border_color = fg_border_color or curses.COLOR_WHITE
|
||||||
|
|
||||||
pair_number = 4 + self.fg_border_color
|
|
||||||
self.init_pair(pair_number, self.fg_border_color, curses.COLOR_BLACK)
|
|
||||||
self.pair = self.color_pair(pair_number)
|
|
||||||
|
|
||||||
def display(self) -> None:
|
def display(self) -> None:
|
||||||
self.addstr(self.pad, 0, 0, "┏" + "━" * (self.width - 2) + "┓",
|
self.addstr(self.pad, 0, 0, "┏" + "━" * (self.width - 2) + "┓",
|
||||||
self.pair)
|
self.fg_border_color)
|
||||||
for i in range(1, self.height - 1):
|
for i in range(1, self.height - 1):
|
||||||
self.addstr(self.pad, i, 0, "┃", self.pair)
|
self.addstr(self.pad, i, 0, "┃", self.fg_border_color)
|
||||||
self.addstr(self.pad, i, self.width - 1, "┃", self.pair)
|
self.addstr(self.pad, i, self.width - 1, "┃", self.fg_border_color)
|
||||||
self.addstr(self.pad, self.height - 1, 0,
|
self.addstr(self.pad, self.height - 1, 0,
|
||||||
"┗" + "━" * (self.width - 2) + "┛", self.pair)
|
"┗" + "━" * (self.width - 2) + "┛", self.fg_border_color)
|
||||||
|
|
||||||
|
if self.title:
|
||||||
|
self.addstr(self.pad, 0, (self.width - len(self.title) - 8) // 2,
|
||||||
|
f" == {self.title} == ", curses.COLOR_GREEN,
|
||||||
|
italic=True, bold=True)
|
||||||
|
|
||||||
self.refresh_pad(self.pad, 0, 0, self.y, self.x,
|
self.refresh_pad(self.pad, 0, 0, self.y, self.x,
|
||||||
self.y + self.height - 1, self.x + self.width - 1)
|
self.y + self.height - 1, self.x + self.width - 1)
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
import curses
|
import curses
|
||||||
|
|
||||||
|
from squirrelbattle.display.creditsdisplay import CreditsDisplay
|
||||||
from squirrelbattle.display.display import VerticalSplit, HorizontalSplit, \
|
from squirrelbattle.display.display import VerticalSplit, HorizontalSplit, \
|
||||||
Display
|
Display
|
||||||
from squirrelbattle.display.mapdisplay import MapDisplay
|
from squirrelbattle.display.mapdisplay import MapDisplay
|
||||||
|
@ -30,17 +32,21 @@ class DisplayManager:
|
||||||
self.mainmenudisplay = MainMenuDisplay(self.game.main_menu,
|
self.mainmenudisplay = MainMenuDisplay(self.game.main_menu,
|
||||||
screen, pack)
|
screen, pack)
|
||||||
self.settingsmenudisplay = SettingsMenuDisplay(screen, pack)
|
self.settingsmenudisplay = SettingsMenuDisplay(screen, pack)
|
||||||
self.messagedisplay = MessageDisplay(screen=screen, pack=None)
|
self.messagedisplay = MessageDisplay(screen, pack)
|
||||||
self.hbar = HorizontalSplit(screen, pack)
|
self.hbar = HorizontalSplit(screen, pack)
|
||||||
self.vbar = VerticalSplit(screen, pack)
|
self.vbar = VerticalSplit(screen, pack)
|
||||||
|
self.creditsdisplay = CreditsDisplay(screen, pack)
|
||||||
self.displays = [self.statsdisplay, self.mapdisplay,
|
self.displays = [self.statsdisplay, self.mapdisplay,
|
||||||
self.mainmenudisplay, self.settingsmenudisplay,
|
self.mainmenudisplay, self.settingsmenudisplay,
|
||||||
self.logsdisplay, self.messagedisplay,
|
self.logsdisplay, self.messagedisplay,
|
||||||
self.playerinventorydisplay,
|
self.playerinventorydisplay,
|
||||||
self.storeinventorydisplay]
|
self.storeinventorydisplay, self.creditsdisplay]
|
||||||
self.update_game_components()
|
self.update_game_components()
|
||||||
|
|
||||||
def handle_display_action(self, action: DisplayActions, *params) -> None:
|
def handle_display_action(self, action: DisplayActions, *params) -> None:
|
||||||
|
"""
|
||||||
|
Handles the differents values of display action.
|
||||||
|
"""
|
||||||
if action == DisplayActions.REFRESH:
|
if action == DisplayActions.REFRESH:
|
||||||
self.refresh()
|
self.refresh()
|
||||||
elif action == DisplayActions.UPDATE:
|
elif action == DisplayActions.UPDATE:
|
||||||
|
@ -49,19 +55,18 @@ class DisplayManager:
|
||||||
self.handle_mouse_click(*params)
|
self.handle_mouse_click(*params)
|
||||||
|
|
||||||
def update_game_components(self) -> None:
|
def update_game_components(self) -> None:
|
||||||
|
"""
|
||||||
|
The game state was updated.
|
||||||
|
Trigger all displays of these modifications.
|
||||||
|
"""
|
||||||
for d in self.displays:
|
for d in self.displays:
|
||||||
d.pack = TexturePack.get_pack(self.game.settings.TEXTURE_PACK)
|
d.pack = TexturePack.get_pack(self.game.settings.TEXTURE_PACK)
|
||||||
self.mapdisplay.update_map(self.game.map)
|
d.update(self.game)
|
||||||
self.statsdisplay.update_player(self.game.player)
|
|
||||||
self.game.inventory_menu.update_player(self.game.player)
|
|
||||||
self.game.store_menu.update_merchant(self.game.player)
|
|
||||||
self.playerinventorydisplay.update_menu(self.game.inventory_menu)
|
|
||||||
self.storeinventorydisplay.update_menu(self.game.store_menu)
|
|
||||||
self.settingsmenudisplay.update_menu(self.game.settings_menu)
|
|
||||||
self.logsdisplay.update_logs(self.game.logs)
|
|
||||||
self.messagedisplay.update_message(self.game.message)
|
|
||||||
|
|
||||||
def handle_mouse_click(self, y: int, x: int) -> None:
|
def handle_mouse_click(self, y: int, x: int, attr: int) -> None:
|
||||||
|
"""
|
||||||
|
Handles the mouse clicks.
|
||||||
|
"""
|
||||||
displays = self.refresh()
|
displays = self.refresh()
|
||||||
display = None
|
display = None
|
||||||
for d in displays:
|
for d in displays:
|
||||||
|
@ -71,10 +76,14 @@ class DisplayManager:
|
||||||
# of that display
|
# of that display
|
||||||
display = d
|
display = d
|
||||||
if display:
|
if display:
|
||||||
display.handle_click(y - display.y, x - display.x, self.game)
|
display.handle_click(y - display.y, x - display.x, attr, self.game)
|
||||||
|
|
||||||
def refresh(self) -> List[Display]:
|
def refresh(self) -> List[Display]:
|
||||||
|
"""
|
||||||
|
Refreshes all components on the screen.
|
||||||
|
"""
|
||||||
displays = []
|
displays = []
|
||||||
|
pack = TexturePack.get_pack(self.game.settings.TEXTURE_PACK)
|
||||||
|
|
||||||
if self.game.state == GameMode.PLAY \
|
if self.game.state == GameMode.PLAY \
|
||||||
or self.game.state == GameMode.INVENTORY \
|
or self.game.state == GameMode.INVENTORY \
|
||||||
|
@ -97,27 +106,41 @@ class DisplayManager:
|
||||||
|
|
||||||
if self.game.state == GameMode.INVENTORY:
|
if self.game.state == GameMode.INVENTORY:
|
||||||
self.playerinventorydisplay.refresh(
|
self.playerinventorydisplay.refresh(
|
||||||
self.rows // 10, self.cols // 2,
|
self.rows // 10,
|
||||||
8 * self.rows // 10, 2 * self.cols // 5)
|
pack.tile_width * (self.cols // (2 * pack.tile_width)),
|
||||||
|
8 * self.rows // 10,
|
||||||
|
pack.tile_width * (2 * self.cols // (5 * pack.tile_width)))
|
||||||
displays.append(self.playerinventorydisplay)
|
displays.append(self.playerinventorydisplay)
|
||||||
elif self.game.state == GameMode.STORE:
|
elif self.game.state == GameMode.STORE:
|
||||||
self.storeinventorydisplay.refresh(
|
self.storeinventorydisplay.refresh(
|
||||||
self.rows // 10, self.cols // 2,
|
self.rows // 10,
|
||||||
8 * self.rows // 10, 2 * self.cols // 5)
|
pack.tile_width * (self.cols // (2 * pack.tile_width)),
|
||||||
|
8 * self.rows // 10,
|
||||||
|
pack.tile_width * (2 * self.cols // (5 * pack.tile_width)))
|
||||||
|
self.playerinventorydisplay.refresh(
|
||||||
|
self.rows // 10,
|
||||||
|
pack.tile_width * (self.cols // (10 * pack.tile_width)),
|
||||||
|
8 * self.rows // 10,
|
||||||
|
pack.tile_width * (2 * self.cols // (5 * pack.tile_width)))
|
||||||
displays.append(self.storeinventorydisplay)
|
displays.append(self.storeinventorydisplay)
|
||||||
|
displays.append(self.playerinventorydisplay)
|
||||||
elif self.game.state == GameMode.MAINMENU:
|
elif self.game.state == GameMode.MAINMENU:
|
||||||
self.mainmenudisplay.refresh(0, 0, self.rows, self.cols)
|
self.mainmenudisplay.refresh(0, 0, self.rows, self.cols)
|
||||||
displays.append(self.mainmenudisplay)
|
displays.append(self.mainmenudisplay)
|
||||||
elif self.game.state == GameMode.SETTINGS:
|
elif self.game.state == GameMode.SETTINGS:
|
||||||
self.settingsmenudisplay.refresh(0, 0, self.rows, self.cols)
|
self.settingsmenudisplay.refresh(0, 0, self.rows, self.cols)
|
||||||
displays.append(self.settingsmenudisplay)
|
displays.append(self.settingsmenudisplay)
|
||||||
|
elif self.game.state == GameMode.CREDITS:
|
||||||
|
self.creditsdisplay.refresh(0, 0, self.rows, self.cols)
|
||||||
|
displays.append(self.creditsdisplay)
|
||||||
|
|
||||||
if self.game.message:
|
if self.game.message:
|
||||||
height, width = 0, 0
|
height, width = 0, 0
|
||||||
for line in self.game.message.split("\n"):
|
for line in self.game.message.split("\n"):
|
||||||
height += 1
|
height += 1
|
||||||
width = max(width, len(line))
|
width = max(width, len(line))
|
||||||
y, x = (self.rows - height) // 2, (self.cols - width) // 2
|
y = pack.tile_width * (self.rows - height) // (2 * pack.tile_width)
|
||||||
|
x = pack.tile_width * ((self.cols - width) // (2 * pack.tile_width))
|
||||||
self.messagedisplay.refresh(y, x, height, width)
|
self.messagedisplay.refresh(y, x, height, width)
|
||||||
displays.append(self.messagedisplay)
|
displays.append(self.messagedisplay)
|
||||||
|
|
||||||
|
@ -127,7 +150,7 @@ class DisplayManager:
|
||||||
|
|
||||||
def resize_window(self) -> bool:
|
def resize_window(self) -> bool:
|
||||||
"""
|
"""
|
||||||
If the window got resized, ensure that the screen size got updated.
|
When the window is resized, ensures that the screen size is updated.
|
||||||
"""
|
"""
|
||||||
y, x = self.screen.getmaxyx() if self.screen else (0, 0)
|
y, x = self.screen.getmaxyx() if self.screen else (0, 0)
|
||||||
if self.screen and curses.is_term_resized(self.rows,
|
if self.screen and curses.is_term_resized(self.rows,
|
||||||
|
@ -138,8 +161,16 @@ class DisplayManager:
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def rows(self) -> int:
|
def rows(self) -> int:
|
||||||
|
"""
|
||||||
|
Overwrites the native curses attribute of the same name,
|
||||||
|
for testing purposes.
|
||||||
|
"""
|
||||||
return curses.LINES if self.screen else 42
|
return curses.LINES if self.screen else 42
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def cols(self) -> int:
|
def cols(self) -> int:
|
||||||
|
"""
|
||||||
|
Overwrites the native curses attribute of the same name,
|
||||||
|
for testing purposes.
|
||||||
|
"""
|
||||||
return curses.COLS if self.screen else 42
|
return curses.COLS if self.screen else 42
|
||||||
|
|
|
@ -2,17 +2,23 @@
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from squirrelbattle.display.display import Display
|
from squirrelbattle.display.display import Display
|
||||||
|
from squirrelbattle.game import Game
|
||||||
from squirrelbattle.interfaces import Logs
|
from squirrelbattle.interfaces import Logs
|
||||||
|
|
||||||
|
|
||||||
class LogsDisplay(Display):
|
class LogsDisplay(Display):
|
||||||
|
"""
|
||||||
|
A class to handle the display of the logs.
|
||||||
|
"""
|
||||||
|
|
||||||
|
logs: Logs
|
||||||
|
|
||||||
def __init__(self, *args) -> None:
|
def __init__(self, *args) -> None:
|
||||||
super().__init__(*args)
|
super().__init__(*args)
|
||||||
self.pad = self.newpad(self.rows, self.cols)
|
self.pad = self.newpad(self.rows, self.cols)
|
||||||
|
|
||||||
def update_logs(self, logs: Logs) -> None:
|
def update(self, game: Game) -> None:
|
||||||
self.logs = logs
|
self.logs = game.logs
|
||||||
|
|
||||||
def display(self) -> None:
|
def display(self) -> None:
|
||||||
messages = self.logs.messages[-self.height:]
|
messages = self.logs.messages[-self.height:]
|
||||||
|
|
|
@ -3,27 +3,42 @@
|
||||||
|
|
||||||
from squirrelbattle.interfaces import Map
|
from squirrelbattle.interfaces import Map
|
||||||
from .display import Display
|
from .display import Display
|
||||||
|
from ..game import Game
|
||||||
|
|
||||||
|
|
||||||
class MapDisplay(Display):
|
class MapDisplay(Display):
|
||||||
|
"""
|
||||||
|
A class to handle the display of the map.
|
||||||
|
"""
|
||||||
|
|
||||||
|
map: Map
|
||||||
|
|
||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
super().__init__(*args)
|
super().__init__(*args)
|
||||||
|
|
||||||
def update_map(self, m: Map) -> None:
|
def update(self, game: Game) -> None:
|
||||||
self.map = m
|
self.map = game.map
|
||||||
self.pad = self.newpad(m.height, self.pack.tile_width * m.width + 1)
|
self.pad = self.newpad(self.map.height,
|
||||||
|
self.pack.tile_width * self.map.width + 1)
|
||||||
|
|
||||||
def update_pad(self) -> None:
|
def update_pad(self) -> None:
|
||||||
self.init_pair(1, self.pack.tile_fg_color, self.pack.tile_bg_color)
|
for j in range(len(self.map.tiles)):
|
||||||
self.init_pair(2, self.pack.entity_fg_color, self.pack.entity_bg_color)
|
for i in range(len(self.map.tiles[j])):
|
||||||
self.addstr(self.pad, 0, 0, self.map.draw_string(self.pack),
|
if not self.map.seen_tiles[j][i]:
|
||||||
self.color_pair(1))
|
continue
|
||||||
|
fg, bg = self.map.tiles[j][i].visible_color(self.pack) if \
|
||||||
|
self.map.visibility[j][i] else \
|
||||||
|
self.map.tiles[j][i].hidden_color(self.pack)
|
||||||
|
self.addstr(self.pad, j, self.pack.tile_width * i,
|
||||||
|
self.map.tiles[j][i].char(self.pack), fg, bg)
|
||||||
for e in self.map.entities:
|
for e in self.map.entities:
|
||||||
self.addstr(self.pad, e.y, self.pack.tile_width * e.x,
|
if self.map.visibility[e.y][e.x]:
|
||||||
self.pack[e.name.upper()], self.color_pair(2))
|
self.addstr(self.pad, e.y, self.pack.tile_width * e.x,
|
||||||
|
self.pack[e.name.upper()],
|
||||||
|
self.pack.entity_fg_color,
|
||||||
|
self.pack.entity_bg_color)
|
||||||
|
|
||||||
# Display Path map for deubg purposes
|
# Display Path map for debug purposes
|
||||||
# from squirrelbattle.entities.player import Player
|
# from squirrelbattle.entities.player import Player
|
||||||
# players = [ p for p in self.map.entities if isinstance(p,Player) ]
|
# players = [ p for p in self.map.entities if isinstance(p,Player) ]
|
||||||
# player = players[0] if len(players) > 0 else None
|
# player = players[0] if len(players) > 0 else None
|
||||||
|
@ -42,7 +57,8 @@ class MapDisplay(Display):
|
||||||
# else:
|
# else:
|
||||||
# character = '←'
|
# character = '←'
|
||||||
# self.addstr(self.pad, y, self.pack.tile_width * x,
|
# self.addstr(self.pad, y, self.pack.tile_width * x,
|
||||||
# character, self.color_pair(1))
|
# character, self.pack.tile_fg_color,
|
||||||
|
# self.pack.tile_bg_color)
|
||||||
|
|
||||||
def display(self) -> None:
|
def display(self) -> None:
|
||||||
y, x = self.map.currenty, self.pack.tile_width * self.map.currentx
|
y, x = self.map.currenty, self.pack.tile_width * self.map.currentx
|
||||||
|
|
|
@ -2,11 +2,13 @@
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
import curses
|
import curses
|
||||||
|
from random import randint
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from squirrelbattle.menus import Menu, MainMenu
|
from squirrelbattle.menus import Menu, MainMenu, SettingsMenu, StoreMenu
|
||||||
from .display import Display, Box
|
from .display import Box, Display
|
||||||
from ..enums import KeyValues
|
from ..entities.player import Player
|
||||||
|
from ..enums import KeyValues, GameMode
|
||||||
from ..game import Game
|
from ..game import Game
|
||||||
from ..resources import ResourceManager
|
from ..resources import ResourceManager
|
||||||
from ..translations import gettext as _
|
from ..translations import gettext as _
|
||||||
|
@ -14,8 +16,9 @@ from ..translations import gettext as _
|
||||||
|
|
||||||
class MenuDisplay(Display):
|
class MenuDisplay(Display):
|
||||||
"""
|
"""
|
||||||
A class to display the menu objects
|
A class to display the menu objects.
|
||||||
"""
|
"""
|
||||||
|
menu: Menu
|
||||||
position: int
|
position: int
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
@ -30,9 +33,9 @@ class MenuDisplay(Display):
|
||||||
|
|
||||||
def update_pad(self) -> None:
|
def update_pad(self) -> None:
|
||||||
for i in range(self.trueheight):
|
for i in range(self.trueheight):
|
||||||
self.addstr(self.pad, i, 0, " " + self.values[i])
|
self.addstr(self.pad, i, 0, " " + self.values[i])
|
||||||
# set a marker on the selected line
|
# set a marker on the selected line
|
||||||
self.addstr(self.pad, self.menu.position, 0, ">")
|
self.addstr(self.pad, self.menu.position, 0, " >")
|
||||||
|
|
||||||
def display(self) -> None:
|
def display(self) -> None:
|
||||||
cornery = 0 if self.height - 2 >= self.menu.position - 1 \
|
cornery = 0 if self.height - 2 >= self.menu.position - 1 \
|
||||||
|
@ -43,11 +46,11 @@ class MenuDisplay(Display):
|
||||||
self.menubox.refresh(self.y, self.x, self.height, self.width)
|
self.menubox.refresh(self.y, self.x, self.height, self.width)
|
||||||
self.pad.erase()
|
self.pad.erase()
|
||||||
self.update_pad()
|
self.update_pad()
|
||||||
self.refresh_pad(self.pad, cornery, 0, self.y + 1, self.x + 2,
|
self.refresh_pad(self.pad, cornery, 0, self.y + 1, self.x + 1,
|
||||||
self.height - 2 + self.y,
|
self.height - 2 + self.y,
|
||||||
self.width - 2 + self.x)
|
self.width - 2 + self.x)
|
||||||
|
|
||||||
def handle_click(self, y: int, x: int, game: Game) -> None:
|
def handle_click(self, y: int, x: int, attr: int, game: Game) -> None:
|
||||||
"""
|
"""
|
||||||
We can select a menu item with the mouse.
|
We can select a menu item with the mouse.
|
||||||
"""
|
"""
|
||||||
|
@ -77,8 +80,13 @@ class MenuDisplay(Display):
|
||||||
|
|
||||||
class SettingsMenuDisplay(MenuDisplay):
|
class SettingsMenuDisplay(MenuDisplay):
|
||||||
"""
|
"""
|
||||||
A class to display specifically a settingsmenu object
|
A class to display specifically a settingsmenu object.
|
||||||
"""
|
"""
|
||||||
|
menu: SettingsMenu
|
||||||
|
|
||||||
|
def update(self, game: Game) -> None:
|
||||||
|
self.update_menu(game.settings_menu)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def values(self) -> List[str]:
|
def values(self) -> List[str]:
|
||||||
return [_(a[1][1]) + (" : "
|
return [_(a[1][1]) + (" : "
|
||||||
|
@ -90,7 +98,7 @@ class SettingsMenuDisplay(MenuDisplay):
|
||||||
|
|
||||||
class MainMenuDisplay(Display):
|
class MainMenuDisplay(Display):
|
||||||
"""
|
"""
|
||||||
A class to display specifically a mainmenu object
|
A class to display specifically a mainmenu object.
|
||||||
"""
|
"""
|
||||||
def __init__(self, menu: MainMenu, *args):
|
def __init__(self, menu: MainMenu, *args):
|
||||||
super().__init__(*args)
|
super().__init__(*args)
|
||||||
|
@ -102,13 +110,18 @@ class MainMenuDisplay(Display):
|
||||||
self.pad = self.newpad(max(self.rows, len(self.title) + 30),
|
self.pad = self.newpad(max(self.rows, len(self.title) + 30),
|
||||||
max(len(self.title[0]) + 5, self.cols))
|
max(len(self.title[0]) + 5, self.cols))
|
||||||
|
|
||||||
|
self.fg_color = curses.COLOR_WHITE
|
||||||
|
|
||||||
self.menudisplay = MenuDisplay(self.screen, self.pack)
|
self.menudisplay = MenuDisplay(self.screen, self.pack)
|
||||||
self.menudisplay.update_menu(self.menu)
|
self.menudisplay.update_menu(self.menu)
|
||||||
|
|
||||||
def display(self) -> None:
|
def display(self) -> None:
|
||||||
for i in range(len(self.title)):
|
for i in range(len(self.title)):
|
||||||
self.addstr(self.pad, 4 + i, max(self.width // 2
|
self.addstr(self.pad, 4 + i, max(self.width // 2
|
||||||
- len(self.title[0]) // 2 - 1, 0), self.title[i])
|
- len(self.title[0]) // 2 - 1, 0), self.title[i],
|
||||||
|
self.fg_color)
|
||||||
|
msg = _("Credits")
|
||||||
|
self.addstr(self.pad, self.height - 1, self.width - 1 - len(msg), msg)
|
||||||
self.refresh_pad(self.pad, 0, 0, self.y, self.x,
|
self.refresh_pad(self.pad, 0, 0, self.y, self.x,
|
||||||
self.height + self.y - 1,
|
self.height + self.y - 1,
|
||||||
self.width + self.x - 1)
|
self.width + self.x - 1)
|
||||||
|
@ -118,26 +131,56 @@ class MainMenuDisplay(Display):
|
||||||
menuy, menux, min(self.menudisplay.preferred_height,
|
menuy, menux, min(self.menudisplay.preferred_height,
|
||||||
self.height - menuy), menuwidth)
|
self.height - menuy), menuwidth)
|
||||||
|
|
||||||
def handle_click(self, y: int, x: int, game: Game) -> None:
|
def update(self, game: Game) -> None:
|
||||||
|
self.menudisplay.update_menu(game.main_menu)
|
||||||
|
|
||||||
|
def handle_click(self, y: int, x: int, attr: int, game: Game) -> None:
|
||||||
menuwidth = min(self.menudisplay.preferred_width, self.width)
|
menuwidth = min(self.menudisplay.preferred_width, self.width)
|
||||||
menuy, menux = len(self.title) + 8, self.width // 2 - menuwidth // 2 - 1
|
menuy, menux = len(self.title) + 8, self.width // 2 - menuwidth // 2 - 1
|
||||||
menuheight = min(self.menudisplay.preferred_height, self.height - menuy)
|
menuheight = min(self.menudisplay.preferred_height, self.height - menuy)
|
||||||
|
|
||||||
if menuy <= y < menuy + menuheight and menux <= x < menux + menuwidth:
|
if menuy <= y < menuy + menuheight and menux <= x < menux + menuwidth:
|
||||||
self.menudisplay.handle_click(y - menuy, x - menux, game)
|
self.menudisplay.handle_click(y - menuy, x - menux, attr, game)
|
||||||
|
|
||||||
|
if y <= len(self.title):
|
||||||
|
self.fg_color = randint(0, 1000), randint(0, 1000), randint(0, 1000)
|
||||||
|
|
||||||
|
if y == self.height - 1 and x >= self.width - 1 - len(_("Credits")):
|
||||||
|
game.state = GameMode.CREDITS
|
||||||
|
|
||||||
|
|
||||||
class PlayerInventoryDisplay(MenuDisplay):
|
class PlayerInventoryDisplay(MenuDisplay):
|
||||||
message = _("== INVENTORY ==")
|
"""
|
||||||
|
A class to handle the display of the player's inventory.
|
||||||
|
"""
|
||||||
|
player: Player = None
|
||||||
|
selected: bool = True
|
||||||
|
store_mode: bool = False
|
||||||
|
|
||||||
|
def update(self, game: Game) -> None:
|
||||||
|
self.player = game.player
|
||||||
|
self.update_menu(game.inventory_menu)
|
||||||
|
self.store_mode = game.state == GameMode.STORE
|
||||||
|
self.selected = game.state == GameMode.INVENTORY \
|
||||||
|
or (self.store_mode and not game.is_in_store_menu)
|
||||||
|
|
||||||
def update_pad(self) -> None:
|
def update_pad(self) -> None:
|
||||||
self.addstr(self.pad, 0, (self.width - len(self.message)) // 2,
|
self.menubox.update_title(_("INVENTORY"))
|
||||||
self.message, curses.A_BOLD | curses.A_ITALIC)
|
|
||||||
for i, item in enumerate(self.menu.values):
|
for i, item in enumerate(self.menu.values):
|
||||||
rep = self.pack[item.name.upper()]
|
rep = self.pack[item.name.upper()]
|
||||||
selection = f"[{rep}]" if i == self.menu.position else f" {rep} "
|
selection = f"[{rep}]" if i == self.menu.position \
|
||||||
self.addstr(self.pad, 2 + i, 0, selection
|
and self.selected else f" {rep} "
|
||||||
+ " " + item.translated_name.capitalize())
|
self.addstr(self.pad, i + 1, 0, selection
|
||||||
|
+ " " + item.translated_name.capitalize()
|
||||||
|
+ (f" ({item.description})" if item.description else "")
|
||||||
|
+ (": " + str(item.price) + " Hazels"
|
||||||
|
if self.store_mode else ""))
|
||||||
|
|
||||||
|
if self.store_mode:
|
||||||
|
price = f"{self.pack.HAZELNUT} {self.player.hazel} Hazels"
|
||||||
|
width = len(price) + (self.pack.tile_width - 1)
|
||||||
|
self.addstr(self.pad, self.height - 3, self.width - width - 2,
|
||||||
|
price, italic=True)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def truewidth(self) -> int:
|
def truewidth(self) -> int:
|
||||||
|
@ -147,27 +190,42 @@ class PlayerInventoryDisplay(MenuDisplay):
|
||||||
def trueheight(self) -> int:
|
def trueheight(self) -> int:
|
||||||
return 2 + super().trueheight
|
return 2 + super().trueheight
|
||||||
|
|
||||||
def handle_click(self, y: int, x: int, game: Game) -> None:
|
def handle_click(self, y: int, x: int, attr: int, game: Game) -> None:
|
||||||
"""
|
"""
|
||||||
We can select a menu item with the mouse.
|
We can select a menu item with the mouse.
|
||||||
"""
|
"""
|
||||||
self.menu.position = max(0, min(len(self.menu.values) - 1, y - 3))
|
self.menu.position = max(0, min(len(self.menu.values) - 1, y - 2))
|
||||||
|
game.is_in_store_menu = False
|
||||||
game.handle_key_pressed(KeyValues.ENTER)
|
game.handle_key_pressed(KeyValues.ENTER)
|
||||||
|
|
||||||
|
|
||||||
class StoreInventoryDisplay(MenuDisplay):
|
class StoreInventoryDisplay(MenuDisplay):
|
||||||
message = _("== STALL ==")
|
"""
|
||||||
|
A class to handle the display of a merchant's inventory.
|
||||||
|
"""
|
||||||
|
menu: StoreMenu
|
||||||
|
selected: bool = False
|
||||||
|
|
||||||
|
def update(self, game: Game) -> None:
|
||||||
|
self.update_menu(game.store_menu)
|
||||||
|
self.selected = game.is_in_store_menu
|
||||||
|
|
||||||
def update_pad(self) -> None:
|
def update_pad(self) -> None:
|
||||||
self.addstr(self.pad, 0, (self.width - len(self.message)) // 2,
|
self.menubox.update_title(_("STALL"))
|
||||||
self.message, curses.A_BOLD | curses.A_ITALIC)
|
|
||||||
for i, item in enumerate(self.menu.values):
|
for i, item in enumerate(self.menu.values):
|
||||||
rep = self.pack[item.name.upper()]
|
rep = self.pack[item.name.upper()]
|
||||||
selection = f"[{rep}]" if i == self.menu.position else f" {rep} "
|
selection = f"[{rep}]" if i == self.menu.position \
|
||||||
self.addstr(self.pad, 2 + i, 0, selection
|
and self.selected else f" {rep} "
|
||||||
|
self.addstr(self.pad, i + 1, 0, selection
|
||||||
+ " " + item.translated_name.capitalize()
|
+ " " + item.translated_name.capitalize()
|
||||||
|
+ (f" ({item.description})" if item.description else "")
|
||||||
+ ": " + str(item.price) + " Hazels")
|
+ ": " + str(item.price) + " Hazels")
|
||||||
|
|
||||||
|
price = f"{self.pack.HAZELNUT} {self.menu.merchant.hazel} Hazels"
|
||||||
|
width = len(price) + (self.pack.tile_width - 1)
|
||||||
|
self.addstr(self.pad, self.height - 3, self.width - width - 2, price,
|
||||||
|
italic=True)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def truewidth(self) -> int:
|
def truewidth(self) -> int:
|
||||||
return max(1, self.height if hasattr(self, "height") else 10)
|
return max(1, self.height if hasattr(self, "height") else 10)
|
||||||
|
@ -176,9 +234,10 @@ class StoreInventoryDisplay(MenuDisplay):
|
||||||
def trueheight(self) -> int:
|
def trueheight(self) -> int:
|
||||||
return 2 + super().trueheight
|
return 2 + super().trueheight
|
||||||
|
|
||||||
def handle_click(self, y: int, x: int, game: Game) -> None:
|
def handle_click(self, y: int, x: int, attr: int, game: Game) -> None:
|
||||||
"""
|
"""
|
||||||
We can select a menu item with the mouse.
|
We can select a menu item with the mouse.
|
||||||
"""
|
"""
|
||||||
self.menu.position = max(0, min(len(self.menu.values) - 1, y - 3))
|
self.menu.position = max(0, min(len(self.menu.values) - 1, y - 2))
|
||||||
|
game.is_in_store_menu = True
|
||||||
game.handle_key_pressed(KeyValues.ENTER)
|
game.handle_key_pressed(KeyValues.ENTER)
|
||||||
|
|
|
@ -3,11 +3,12 @@
|
||||||
import curses
|
import curses
|
||||||
|
|
||||||
from squirrelbattle.display.display import Box, Display
|
from squirrelbattle.display.display import Box, Display
|
||||||
|
from squirrelbattle.game import Game
|
||||||
|
|
||||||
|
|
||||||
class MessageDisplay(Display):
|
class MessageDisplay(Display):
|
||||||
"""
|
"""
|
||||||
Display a message in a popup.
|
A class to handle the display of popup messages.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
|
@ -17,15 +18,15 @@ class MessageDisplay(Display):
|
||||||
self.message = ""
|
self.message = ""
|
||||||
self.pad = self.newpad(1, 1)
|
self.pad = self.newpad(1, 1)
|
||||||
|
|
||||||
def update_message(self, msg: str) -> None:
|
def update(self, game: Game) -> None:
|
||||||
self.message = msg
|
self.message = game.message
|
||||||
|
|
||||||
def display(self) -> None:
|
def display(self) -> None:
|
||||||
self.box.refresh(self.y - 1, self.x - 2,
|
self.box.refresh(self.y - 1, self.x - 2,
|
||||||
self.height + 2, self.width + 4)
|
self.height + 2, self.width + 4)
|
||||||
self.box.display()
|
self.box.display()
|
||||||
self.pad.erase()
|
self.pad.erase()
|
||||||
self.addstr(self.pad, 0, 0, self.message, curses.A_BOLD)
|
self.addstr(self.pad, 0, 0, self.message, bold=True)
|
||||||
self.refresh_pad(self.pad, 0, 0, self.y, self.x,
|
self.refresh_pad(self.pad, 0, 0, self.y, self.x,
|
||||||
self.height + self.y - 1,
|
self.height + self.y - 1,
|
||||||
self.width + self.x - 1)
|
self.width + self.x - 1)
|
||||||
|
|
|
@ -3,32 +3,42 @@
|
||||||
|
|
||||||
import curses
|
import curses
|
||||||
|
|
||||||
|
from ..entities.items import Monocle
|
||||||
from ..entities.player import Player
|
from ..entities.player import Player
|
||||||
|
from ..game import Game
|
||||||
|
from ..interfaces import FightingEntity
|
||||||
from ..translations import gettext as _
|
from ..translations import gettext as _
|
||||||
from .display import Display
|
from .display import Display
|
||||||
|
|
||||||
|
|
||||||
class StatsDisplay(Display):
|
class StatsDisplay(Display):
|
||||||
|
"""
|
||||||
|
A class to handle the display of the stats of the player.
|
||||||
|
"""
|
||||||
|
game: Game
|
||||||
player: Player
|
player: Player
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.pad = self.newpad(self.rows, self.cols)
|
self.pad = self.newpad(self.rows, self.cols)
|
||||||
self.init_pair(3, curses.COLOR_RED, curses.COLOR_BLACK)
|
|
||||||
|
|
||||||
def update_player(self, p: Player) -> None:
|
def update(self, game: Game) -> None:
|
||||||
self.player = p
|
self.game = game
|
||||||
|
self.player = game.player
|
||||||
|
|
||||||
def update_pad(self) -> None:
|
def update_pad(self) -> None:
|
||||||
string2 = "Player -- LVL {}\nEXP {}/{}\nHP {}/{}"\
|
string2 = f"{_(self.player.name).capitalize()} " \
|
||||||
.format(self.player.level, self.player.current_xp,
|
f"-- LVL {self.player.level} -- " \
|
||||||
self.player.max_xp, self.player.health,
|
f"FLOOR {-self.player.map.floor}\n" \
|
||||||
self.player.maxhealth)
|
f"EXP {self.player.current_xp}/{self.player.max_xp}\n" \
|
||||||
|
f"HP {self.player.health}/{self.player.maxhealth}"
|
||||||
self.addstr(self.pad, 0, 0, string2)
|
self.addstr(self.pad, 0, 0, string2)
|
||||||
string3 = "STR {}\nINT {}\nCHR {}\nDEX {}\nCON {}"\
|
string3 = f"STR {self.player.strength}\n" \
|
||||||
.format(self.player.strength,
|
f"INT {self.player.intelligence}\n" \
|
||||||
self.player.intelligence, self.player.charisma,
|
f"CHR {self.player.charisma}\n" \
|
||||||
self.player.dexterity, self.player.constitution)
|
f"DEX {self.player.dexterity}\n" \
|
||||||
|
f"CON {self.player.constitution}\n" \
|
||||||
|
f"CRI {self.player.critical}%"
|
||||||
self.addstr(self.pad, 3, 0, string3)
|
self.addstr(self.pad, 3, 0, string3)
|
||||||
|
|
||||||
inventory_str = _("Inventory:") + " "
|
inventory_str = _("Inventory:") + " "
|
||||||
|
@ -44,15 +54,73 @@ class StatsDisplay(Display):
|
||||||
if count > 1:
|
if count > 1:
|
||||||
inventory_str += f"x{count} "
|
inventory_str += f"x{count} "
|
||||||
printed_items.append(item)
|
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} "
|
if self.player.equipped_main:
|
||||||
f"x{self.player.hazel}")
|
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:
|
if self.player.dead:
|
||||||
self.addstr(self.pad, 11, 0, _("YOU ARE DEAD"),
|
self.addstr(self.pad, 15, 0, _("YOU ARE DEAD"), curses.COLOR_RED,
|
||||||
curses.A_BOLD | curses.A_BLINK | curses.A_STANDOUT
|
bold=True, blink=True, standout=True)
|
||||||
| self.color_pair(3))
|
|
||||||
|
if self.player.map.tiles[self.player.y][self.player.x].is_ladder():
|
||||||
|
msg = _("Use {key} to use the ladder") \
|
||||||
|
.format(key=self.game.settings.KEY_LADDER.upper())
|
||||||
|
self.addstr(self.pad, self.height - 2, 0, msg,
|
||||||
|
italic=True, reverse=True)
|
||||||
|
|
||||||
|
self.update_entities_stats()
|
||||||
|
|
||||||
|
def update_entities_stats(self) -> None:
|
||||||
|
"""
|
||||||
|
Display information about a near entity if we have a monocle.
|
||||||
|
"""
|
||||||
|
for dy, dx in [(-1, 0), (0, -1), (0, 1), (1, 0)]:
|
||||||
|
for entity in self.player.map.find_entities(FightingEntity):
|
||||||
|
if entity == self.player:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if entity.y == self.player.y + dy \
|
||||||
|
and entity.x == self.player.x + dx:
|
||||||
|
if entity.is_friendly():
|
||||||
|
msg = _("Move to the friendly entity to talk to it") \
|
||||||
|
if self.game.waiting_for_friendly_key else \
|
||||||
|
_("Use {key} then move to talk to the entity") \
|
||||||
|
.format(key=self.game.settings.KEY_CHAT.upper())
|
||||||
|
self.addstr(self.pad, self.height - 1, 0, msg,
|
||||||
|
italic=True, reverse=True)
|
||||||
|
|
||||||
|
if isinstance(self.player.equipped_secondary, Monocle):
|
||||||
|
# Truth monocle
|
||||||
|
message = f"{entity.translated_name.capitalize()} " \
|
||||||
|
f"{self.pack[entity.name.upper()]}\n" \
|
||||||
|
f"STR {entity.strength}\n" \
|
||||||
|
f"INT {entity.intelligence}\n" \
|
||||||
|
f"CHR {entity.charisma}\n" \
|
||||||
|
f"DEX {entity.dexterity}\n" \
|
||||||
|
f"CON {entity.constitution}\n" \
|
||||||
|
f"CRI {entity.critical}%"
|
||||||
|
self.addstr(self.pad, 17, 0, message)
|
||||||
|
# Only display one entity
|
||||||
|
break
|
||||||
|
|
||||||
def display(self) -> None:
|
def display(self) -> None:
|
||||||
self.pad.erase()
|
self.pad.erase()
|
||||||
|
|
|
@ -2,33 +2,44 @@
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
import curses
|
import curses
|
||||||
from typing import Any
|
from typing import Any, Union, Tuple
|
||||||
|
|
||||||
|
|
||||||
class TexturePack:
|
class TexturePack:
|
||||||
|
"""
|
||||||
|
A class to handle displaying several textures.
|
||||||
|
"""
|
||||||
_packs = dict()
|
_packs = dict()
|
||||||
|
|
||||||
name: str
|
name: str
|
||||||
tile_width: int
|
tile_width: int
|
||||||
tile_fg_color: int
|
tile_fg_color: Union[int, Tuple[int, int, int]]
|
||||||
tile_bg_color: int
|
tile_fg_visible_color: Union[int, Tuple[int, int, int]]
|
||||||
entity_fg_color: int
|
tile_bg_color: Union[int, Tuple[int, int, int]]
|
||||||
entity_bg_color: int
|
entity_fg_color: Union[int, Tuple[int, int, int]]
|
||||||
|
entity_bg_color: Union[int, Tuple[int, int, int]]
|
||||||
|
|
||||||
BODY_SNATCH_POTION: str
|
BODY_SNATCH_POTION: str
|
||||||
BOMB: str
|
BOMB: str
|
||||||
HEART: str
|
CHESTPLATE: str
|
||||||
HEDGEHOG: str
|
EAGLE: str
|
||||||
EMPTY: str
|
EMPTY: str
|
||||||
FLOOR: str
|
FLOOR: str
|
||||||
HAZELNUT: str
|
HAZELNUT: str
|
||||||
|
HEART: str
|
||||||
|
HEDGEHOG: str
|
||||||
|
HELMET: str
|
||||||
MERCHANT: str
|
MERCHANT: str
|
||||||
PLAYER: str
|
PLAYER: str
|
||||||
RABBIT: str
|
RABBIT: str
|
||||||
|
RING_OF_CRITICAL_DAMAGE: str
|
||||||
|
RING_OF_MORE_EXPERIENCE: str
|
||||||
|
SHIELD: str
|
||||||
SUNFLOWER: str
|
SUNFLOWER: str
|
||||||
SWORD: str
|
SWORD: str
|
||||||
TEDDY_BEAR: str
|
TEDDY_BEAR: str
|
||||||
TIGER: str
|
TIGER: str
|
||||||
|
TRUMPET: str
|
||||||
WALL: str
|
WALL: str
|
||||||
|
|
||||||
ASCII_PACK: "TexturePack"
|
ASCII_PACK: "TexturePack"
|
||||||
|
@ -54,49 +65,72 @@ class TexturePack:
|
||||||
TexturePack.ASCII_PACK = TexturePack(
|
TexturePack.ASCII_PACK = TexturePack(
|
||||||
name="ascii",
|
name="ascii",
|
||||||
tile_width=1,
|
tile_width=1,
|
||||||
|
tile_fg_visible_color=(1000, 1000, 1000),
|
||||||
tile_fg_color=curses.COLOR_WHITE,
|
tile_fg_color=curses.COLOR_WHITE,
|
||||||
tile_bg_color=curses.COLOR_BLACK,
|
tile_bg_color=curses.COLOR_BLACK,
|
||||||
entity_fg_color=curses.COLOR_WHITE,
|
entity_fg_color=(1000, 1000, 1000),
|
||||||
entity_bg_color=curses.COLOR_BLACK,
|
entity_bg_color=curses.COLOR_BLACK,
|
||||||
|
|
||||||
BODY_SNATCH_POTION='S',
|
BODY_SNATCH_POTION='S',
|
||||||
BOMB='o',
|
BOMB='ç',
|
||||||
|
CHESTPLATE='(',
|
||||||
|
EAGLE='µ',
|
||||||
EMPTY=' ',
|
EMPTY=' ',
|
||||||
|
EXPLOSION='%',
|
||||||
FLOOR='.',
|
FLOOR='.',
|
||||||
|
LADDER='H',
|
||||||
HAZELNUT='¤',
|
HAZELNUT='¤',
|
||||||
HEART='❤',
|
HEART='❤',
|
||||||
HEDGEHOG='*',
|
HEDGEHOG='*',
|
||||||
|
HELMET='0',
|
||||||
MERCHANT='M',
|
MERCHANT='M',
|
||||||
|
MONOCLE='ô',
|
||||||
PLAYER='@',
|
PLAYER='@',
|
||||||
RABBIT='Y',
|
RABBIT='Y',
|
||||||
|
RING_OF_CRITICAL_DAMAGE='o',
|
||||||
|
RING_OF_MORE_EXPERIENCE='o',
|
||||||
|
SHIELD='D',
|
||||||
SUNFLOWER='I',
|
SUNFLOWER='I',
|
||||||
SWORD='\u2020',
|
SWORD='\u2020',
|
||||||
TEDDY_BEAR='8',
|
TEDDY_BEAR='8',
|
||||||
TIGER='n',
|
TIGER='n',
|
||||||
|
TRUMPET='/',
|
||||||
WALL='#',
|
WALL='#',
|
||||||
)
|
)
|
||||||
|
|
||||||
TexturePack.SQUIRREL_PACK = TexturePack(
|
TexturePack.SQUIRREL_PACK = TexturePack(
|
||||||
name="squirrel",
|
name="squirrel",
|
||||||
tile_width=2,
|
tile_width=2,
|
||||||
|
tile_fg_visible_color=(1000, 1000, 1000),
|
||||||
tile_fg_color=curses.COLOR_WHITE,
|
tile_fg_color=curses.COLOR_WHITE,
|
||||||
tile_bg_color=curses.COLOR_BLACK,
|
tile_bg_color=curses.COLOR_BLACK,
|
||||||
entity_fg_color=curses.COLOR_WHITE,
|
entity_fg_color=(1000, 1000, 1000),
|
||||||
entity_bg_color=curses.COLOR_WHITE,
|
entity_bg_color=(1000, 1000, 1000),
|
||||||
|
|
||||||
BODY_SNATCH_POTION='🔀',
|
BODY_SNATCH_POTION='🔀',
|
||||||
BOMB='💣',
|
BOMB='💣',
|
||||||
|
CHESTPLATE='🦺',
|
||||||
|
EAGLE='🦅',
|
||||||
EMPTY=' ',
|
EMPTY=' ',
|
||||||
|
EXPLOSION='💥',
|
||||||
FLOOR='██',
|
FLOOR='██',
|
||||||
|
LADDER=('🪜', curses.COLOR_WHITE, (1000, 1000, 1000),
|
||||||
|
curses.COLOR_WHITE, (1000, 1000, 1000)),
|
||||||
HAZELNUT='🌰',
|
HAZELNUT='🌰',
|
||||||
HEART='💜',
|
HEART='💜',
|
||||||
HEDGEHOG='🦔',
|
HEDGEHOG='🦔',
|
||||||
|
HELMET='⛑️ ',
|
||||||
PLAYER='🐿️ ️',
|
PLAYER='🐿️ ️',
|
||||||
MERCHANT='🦜',
|
MERCHANT='🦜',
|
||||||
|
MONOCLE='🧐',
|
||||||
RABBIT='🐇',
|
RABBIT='🐇',
|
||||||
|
RING_OF_CRITICAL_DAMAGE='💍',
|
||||||
|
RING_OF_MORE_EXPERIENCE='💍',
|
||||||
|
SHIELD='🛡️ ',
|
||||||
SUNFLOWER='🌻',
|
SUNFLOWER='🌻',
|
||||||
SWORD='🗡️',
|
SWORD='🗡️ ',
|
||||||
TEDDY_BEAR='🧸',
|
TEDDY_BEAR='🧸',
|
||||||
TIGER='🐅',
|
TIGER='🐅',
|
||||||
|
TRUMPET='🎺',
|
||||||
WALL='🧱',
|
WALL='🧱',
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,17 +1,18 @@
|
||||||
from ..interfaces import FriendlyEntity, InventoryHolder
|
from ..interfaces import FriendlyEntity, InventoryHolder, Map, FightingEntity
|
||||||
from ..translations import gettext as _
|
from ..translations import gettext as _
|
||||||
from .player import Player
|
from .player import Player
|
||||||
|
from .monsters import Monster
|
||||||
from .items import Item
|
from .items import Item
|
||||||
from random import choice
|
from random import choice, shuffle
|
||||||
|
|
||||||
|
|
||||||
class Merchant(InventoryHolder, FriendlyEntity):
|
class Merchant(InventoryHolder, FriendlyEntity):
|
||||||
"""
|
"""
|
||||||
The class for merchants in the dungeon
|
The class of merchants in the dungeon.
|
||||||
"""
|
"""
|
||||||
def keys(self) -> list:
|
def keys(self) -> list:
|
||||||
"""
|
"""
|
||||||
Returns a friendly entitie's specific attributes
|
Returns a friendly entitie's specific attributes.
|
||||||
"""
|
"""
|
||||||
return super().keys() + ["inventory", "hazel"]
|
return super().keys() + ["inventory", "hazel"]
|
||||||
|
|
||||||
|
@ -20,7 +21,6 @@ class Merchant(InventoryHolder, FriendlyEntity):
|
||||||
super().__init__(name=name, *args, **kwargs)
|
super().__init__(name=name, *args, **kwargs)
|
||||||
self.inventory = self.translate_inventory(inventory or [])
|
self.inventory = self.translate_inventory(inventory or [])
|
||||||
self.hazel = hazel
|
self.hazel = hazel
|
||||||
|
|
||||||
if not self.inventory:
|
if not self.inventory:
|
||||||
for i in range(5):
|
for i in range(5):
|
||||||
self.inventory.append(choice(Item.get_all_items())())
|
self.inventory.append(choice(Item.get_all_items())())
|
||||||
|
@ -28,23 +28,101 @@ class Merchant(InventoryHolder, FriendlyEntity):
|
||||||
def talk_to(self, player: Player) -> str:
|
def talk_to(self, player: Player) -> str:
|
||||||
"""
|
"""
|
||||||
This function is used to open the merchant's inventory in a menu,
|
This function is used to open the merchant's inventory in a menu,
|
||||||
and allow the player to buy/sell objects
|
and allows the player to buy/sell objects.
|
||||||
"""
|
"""
|
||||||
return _("I don't sell any squirrel")
|
return _("I don't sell any squirrel")
|
||||||
|
|
||||||
def change_hazel_balance(self, hz: int) -> None:
|
def change_hazel_balance(self, hz: int) -> None:
|
||||||
"""
|
"""
|
||||||
Change the number of hazel the merchant has by hz.
|
Changes the number of hazel the merchant has by hz.
|
||||||
"""
|
"""
|
||||||
self.hazel += hz
|
self.hazel += hz
|
||||||
|
|
||||||
|
|
||||||
class Sunflower(FriendlyEntity):
|
class Sunflower(FriendlyEntity):
|
||||||
"""
|
"""
|
||||||
A friendly sunflower
|
A friendly sunflower.
|
||||||
"""
|
"""
|
||||||
dialogue_option = [_("Flower power!!"), _("The sun is warm today")]
|
|
||||||
|
|
||||||
def __init__(self, maxhealth: int = 15,
|
def __init__(self, maxhealth: int = 15,
|
||||||
*args, **kwargs) -> None:
|
*args, **kwargs) -> None:
|
||||||
super().__init__(name="sunflower", maxhealth=maxhealth, *args, **kwargs)
|
super().__init__(name="sunflower", maxhealth=maxhealth, *args, **kwargs)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def dialogue_option(self) -> list:
|
||||||
|
"""
|
||||||
|
Lists all that a sunflower can say to the player.
|
||||||
|
"""
|
||||||
|
return [_("Flower power!!"), _("The sun is warm today")]
|
||||||
|
|
||||||
|
|
||||||
|
class Familiar(FightingEntity):
|
||||||
|
"""
|
||||||
|
A friendly familiar that helps the player defeat monsters.
|
||||||
|
"""
|
||||||
|
def __init__(self, maxhealth: int = 25,
|
||||||
|
*args, **kwargs) -> None:
|
||||||
|
super().__init__(maxhealth=maxhealth, *args, **kwargs)
|
||||||
|
self.target = None
|
||||||
|
|
||||||
|
# @property
|
||||||
|
# def dialogue_option(self) -> list:
|
||||||
|
# """
|
||||||
|
# Debug function (to see if used in the real game)
|
||||||
|
# """
|
||||||
|
# return [_("My target is"+str(self.target))]
|
||||||
|
|
||||||
|
def act(self, p: Player, m: Map) -> None:
|
||||||
|
"""
|
||||||
|
By default, the familiar tries to stay at distance at most 2 of the
|
||||||
|
player and if a monster comes in range 3, it focuses on the monster
|
||||||
|
and attacks it.
|
||||||
|
"""
|
||||||
|
if self.target is None:
|
||||||
|
# If the previous target is dead(or if there was no previous target)
|
||||||
|
# the familiar tries to get closer to the player.
|
||||||
|
self.target = p
|
||||||
|
elif self.target.dead:
|
||||||
|
self.target = p
|
||||||
|
if self.target == p:
|
||||||
|
# Look for monsters around the player to kill TOFIX : if monster is
|
||||||
|
# out of range, continue targetting player.
|
||||||
|
for entity in m.entities:
|
||||||
|
if (p.y - entity.y) ** 2 + (p.x - entity.x) ** 2 <= 9 and\
|
||||||
|
isinstance(entity, Monster):
|
||||||
|
self.target = entity
|
||||||
|
entity.paths = dict() # Allows the paths to be calculated.
|
||||||
|
break
|
||||||
|
|
||||||
|
# Familiars move according to a Dijkstra algorithm
|
||||||
|
# that targets their target.
|
||||||
|
# If they can not move and are already close to their target,
|
||||||
|
# they hit, except if their target is the player.
|
||||||
|
if self.target and (self.y, self.x) in self.target.paths:
|
||||||
|
# Moves to target player by choosing the best available path
|
||||||
|
for next_y, next_x in self.target.paths[(self.y, self.x)]:
|
||||||
|
moved = self.check_move(next_y, next_x, True)
|
||||||
|
if moved:
|
||||||
|
break
|
||||||
|
if self.distance_squared(self.target) <= 1 and \
|
||||||
|
not isinstance(self.target, Player):
|
||||||
|
self.map.logs.add_message(self.hit(self.target))
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
# Moves in a random direction
|
||||||
|
# If the direction is not available, tries another one
|
||||||
|
moves = [self.move_up, self.move_down,
|
||||||
|
self.move_left, self.move_right]
|
||||||
|
shuffle(moves)
|
||||||
|
for move in moves:
|
||||||
|
if move():
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
class Trumpet(Familiar):
|
||||||
|
"""
|
||||||
|
A class of familiars.
|
||||||
|
"""
|
||||||
|
def __init__(self, name: str = "trumpet", strength: int = 3,
|
||||||
|
maxhealth: int = 20, *args, **kwargs) -> None:
|
||||||
|
super().__init__(name=name, strength=strength,
|
||||||
|
maxhealth=maxhealth, *args, **kwargs)
|
||||||
|
|
|
@ -4,14 +4,13 @@
|
||||||
from random import choice, randint
|
from random import choice, randint
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from .player import Player
|
|
||||||
from ..interfaces import Entity, FightingEntity, Map, InventoryHolder
|
from ..interfaces import Entity, FightingEntity, Map, InventoryHolder
|
||||||
from ..translations import gettext as _
|
from ..translations import gettext as _
|
||||||
|
|
||||||
|
|
||||||
class Item(Entity):
|
class Item(Entity):
|
||||||
"""
|
"""
|
||||||
A class for items
|
A class for items.
|
||||||
"""
|
"""
|
||||||
held: bool
|
held: bool
|
||||||
held_by: Optional[InventoryHolder]
|
held_by: Optional[InventoryHolder]
|
||||||
|
@ -25,12 +24,19 @@ class Item(Entity):
|
||||||
self.held_by = held_by
|
self.held_by = held_by
|
||||||
self.price = price
|
self.price = price
|
||||||
|
|
||||||
|
@property
|
||||||
|
def description(self) -> str:
|
||||||
|
"""
|
||||||
|
In the inventory, indicate the usefulness of the item.
|
||||||
|
"""
|
||||||
|
return ""
|
||||||
|
|
||||||
def drop(self) -> None:
|
def drop(self) -> None:
|
||||||
"""
|
"""
|
||||||
The item is dropped from the inventory onto the floor
|
The item is dropped from the inventory onto the floor.
|
||||||
"""
|
"""
|
||||||
if self.held:
|
if self.held:
|
||||||
self.held_by.inventory.remove(self)
|
self.held_by.remove_from_inventory(self)
|
||||||
self.held_by.map.add_entity(self)
|
self.held_by.map.add_entity(self)
|
||||||
self.move(self.held_by.y, self.held_by.x)
|
self.move(self.held_by.y, self.held_by.x)
|
||||||
self.held = False
|
self.held = False
|
||||||
|
@ -45,19 +51,31 @@ class Item(Entity):
|
||||||
"""
|
"""
|
||||||
Indicates what should be done when the item is equipped.
|
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:
|
||||||
"""
|
"""
|
||||||
The item is taken from the floor and put into the inventory
|
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 = True
|
||||||
self.held_by = player
|
self.held_by = holder
|
||||||
self.held_by.map.remove_entity(self)
|
self.held_by.map.remove_entity(self)
|
||||||
player.add_to_inventory(self)
|
holder.add_to_inventory(self)
|
||||||
|
|
||||||
def save_state(self) -> dict:
|
def save_state(self) -> dict:
|
||||||
"""
|
"""
|
||||||
Saves the state of the entity into a dictionary
|
Saves the state of the item into a dictionary.
|
||||||
"""
|
"""
|
||||||
d = super().save_state()
|
d = super().save_state()
|
||||||
d["held"] = self.held
|
d["held"] = self.held
|
||||||
|
@ -65,13 +83,17 @@ class Item(Entity):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_all_items() -> list:
|
def get_all_items() -> list:
|
||||||
return [BodySnatchPotion, Bomb, Heart, Sword]
|
"""
|
||||||
|
Returns the list of all item classes.
|
||||||
|
"""
|
||||||
|
return [BodySnatchPotion, Chestplate, Bomb, Heart, Helmet, Monocle,
|
||||||
|
Shield, Sword, RingCritical, RingXP]
|
||||||
|
|
||||||
def be_sold(self, buyer: InventoryHolder, seller: InventoryHolder) -> bool:
|
def be_sold(self, buyer: InventoryHolder, seller: InventoryHolder) -> bool:
|
||||||
"""
|
"""
|
||||||
Does all necessary actions when an object is to be sold.
|
Does all necessary actions when an object is to be sold.
|
||||||
Is overwritten by some classes that cannot exist in the player's
|
Is overwritten by some classes that cannot exist in the player's
|
||||||
inventory
|
inventory.
|
||||||
"""
|
"""
|
||||||
if buyer.hazel >= self.price:
|
if buyer.hazel >= self.price:
|
||||||
self.hold(buyer)
|
self.hold(buyer)
|
||||||
|
@ -85,7 +107,7 @@ class Item(Entity):
|
||||||
|
|
||||||
class Heart(Item):
|
class Heart(Item):
|
||||||
"""
|
"""
|
||||||
A heart item to return health to the player
|
A heart item to return health to the player.
|
||||||
"""
|
"""
|
||||||
healing: int
|
healing: int
|
||||||
|
|
||||||
|
@ -94,16 +116,21 @@ class Heart(Item):
|
||||||
super().__init__(name=name, price=price, *args, **kwargs)
|
super().__init__(name=name, price=price, *args, **kwargs)
|
||||||
self.healing = healing
|
self.healing = healing
|
||||||
|
|
||||||
|
@property
|
||||||
|
def description(self) -> str:
|
||||||
|
return f"HP+{self.healing}"
|
||||||
|
|
||||||
def hold(self, entity: InventoryHolder) -> None:
|
def hold(self, entity: InventoryHolder) -> None:
|
||||||
"""
|
"""
|
||||||
When holding a heart, heal the player and don't put item in inventory.
|
When holding a heart, the player is healed and
|
||||||
|
the item is not put in the inventory.
|
||||||
"""
|
"""
|
||||||
entity.health = min(entity.maxhealth, entity.health + self.healing)
|
entity.health = min(entity.maxhealth, entity.health + self.healing)
|
||||||
entity.map.remove_entity(self)
|
entity.map.remove_entity(self)
|
||||||
|
|
||||||
def save_state(self) -> dict:
|
def save_state(self) -> dict:
|
||||||
"""
|
"""
|
||||||
Saves the state of the header into a dictionary
|
Saves the state of the heart into a dictionary.
|
||||||
"""
|
"""
|
||||||
d = super().save_state()
|
d = super().save_state()
|
||||||
d["healing"] = self.healing
|
d["healing"] = self.healing
|
||||||
|
@ -116,7 +143,7 @@ class Bomb(Item):
|
||||||
"""
|
"""
|
||||||
damage: int = 5
|
damage: int = 5
|
||||||
exploding: bool
|
exploding: bool
|
||||||
owner: Optional["Player"]
|
owner: Optional["InventoryHolder"]
|
||||||
tick: int
|
tick: int
|
||||||
|
|
||||||
def __init__(self, name: str = "bomb", damage: int = 5,
|
def __init__(self, name: str = "bomb", damage: int = 5,
|
||||||
|
@ -129,7 +156,7 @@ class Bomb(Item):
|
||||||
|
|
||||||
def use(self) -> None:
|
def use(self) -> None:
|
||||||
"""
|
"""
|
||||||
When the bomb is used, throw it and explodes it.
|
When the bomb is used, it is thrown and then it explodes.
|
||||||
"""
|
"""
|
||||||
if self.held:
|
if self.held:
|
||||||
self.owner = self.held_by
|
self.owner = self.held_by
|
||||||
|
@ -138,7 +165,7 @@ class Bomb(Item):
|
||||||
|
|
||||||
def act(self, m: Map) -> None:
|
def act(self, m: Map) -> None:
|
||||||
"""
|
"""
|
||||||
Special exploding action of the bomb
|
Special exploding action of the bomb.
|
||||||
"""
|
"""
|
||||||
if self.exploding:
|
if self.exploding:
|
||||||
if self.tick > 0:
|
if self.tick > 0:
|
||||||
|
@ -158,9 +185,13 @@ class Bomb(Item):
|
||||||
m.logs.add_message(log_message)
|
m.logs.add_message(log_message)
|
||||||
m.entities.remove(self)
|
m.entities.remove(self)
|
||||||
|
|
||||||
|
# Add sparkles where the bomb exploded.
|
||||||
|
explosion = Explosion(y=self.y, x=self.x)
|
||||||
|
self.map.add_entity(explosion)
|
||||||
|
|
||||||
def save_state(self) -> dict:
|
def save_state(self) -> dict:
|
||||||
"""
|
"""
|
||||||
Saves the state of the bomb into a dictionary
|
Saves the state of the bomb into a dictionary.
|
||||||
"""
|
"""
|
||||||
d = super().save_state()
|
d = super().save_state()
|
||||||
d["exploding"] = self.exploding
|
d["exploding"] = self.exploding
|
||||||
|
@ -168,6 +199,25 @@ class Bomb(Item):
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
|
||||||
|
class Explosion(Item):
|
||||||
|
"""
|
||||||
|
When a bomb explodes, the explosion is displayed.
|
||||||
|
"""
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(name="explosion", *args, **kwargs)
|
||||||
|
|
||||||
|
def act(self, m: Map) -> None:
|
||||||
|
"""
|
||||||
|
The bomb disappears after exploding.
|
||||||
|
"""
|
||||||
|
m.remove_entity(self)
|
||||||
|
|
||||||
|
def hold(self, player: InventoryHolder) -> None:
|
||||||
|
"""
|
||||||
|
The player can't hold an explosion.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
class Weapon(Item):
|
class Weapon(Item):
|
||||||
"""
|
"""
|
||||||
Non-throwable items that improve player damage
|
Non-throwable items that improve player damage
|
||||||
|
@ -178,6 +228,10 @@ class Weapon(Item):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.damage = damage
|
self.damage = damage
|
||||||
|
|
||||||
|
@property
|
||||||
|
def description(self) -> str:
|
||||||
|
return f"STR+{self.damage}" if self.damage else super().description
|
||||||
|
|
||||||
def save_state(self) -> dict:
|
def save_state(self) -> dict:
|
||||||
"""
|
"""
|
||||||
Saves the state of the weapon into a dictionary
|
Saves the state of the weapon into a dictionary
|
||||||
|
@ -186,14 +240,101 @@ class Weapon(Item):
|
||||||
d["damage"] = self.damage
|
d["damage"] = self.damage
|
||||||
return d
|
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):
|
class Sword(Weapon):
|
||||||
"""
|
"""
|
||||||
A basic 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)
|
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
|
||||||
|
|
||||||
|
@property
|
||||||
|
def description(self) -> str:
|
||||||
|
return f"CON+{self.constitution}" if self.constitution \
|
||||||
|
else super().description
|
||||||
|
|
||||||
|
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 = 16, *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 = 18, *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 = 30, *args, **kwargs):
|
||||||
|
super().__init__(name=name, constitution=constitution, price=price,
|
||||||
|
*args, **kwargs)
|
||||||
|
|
||||||
|
def equip(self) -> None:
|
||||||
|
if self.held_by.equipped_armor:
|
||||||
|
self.held_by.equipped_armor.unequip()
|
||||||
|
self.held_by.remove_from_inventory(self)
|
||||||
|
self.held_by.equipped_armor = self
|
||||||
|
|
||||||
|
|
||||||
class BodySnatchPotion(Item):
|
class BodySnatchPotion(Item):
|
||||||
|
@ -228,3 +369,93 @@ class BodySnatchPotion(Item):
|
||||||
self.held_by.recalculate_paths()
|
self.held_by.recalculate_paths()
|
||||||
|
|
||||||
self.held_by.inventory.remove(self)
|
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
|
||||||
|
|
||||||
|
@property
|
||||||
|
def description(self) -> str:
|
||||||
|
fields = [("MAX HP", self.maxhealth), ("STR", self.strength),
|
||||||
|
("INT", self.intelligence), ("CHR", self.charisma),
|
||||||
|
("DEX", self.dexterity), ("CON", self.constitution),
|
||||||
|
("CRI", self.critical), ("XP", self.experience)]
|
||||||
|
return ", ".join(f"{key}+{value}" for key, value in fields if value)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
class Monocle(Item):
|
||||||
|
def __init__(self, name: str = "monocle", price: int = 10,
|
||||||
|
*args, **kwargs):
|
||||||
|
super().__init__(name=name, price=price, *args, **kwargs)
|
||||||
|
|
|
@ -10,8 +10,8 @@ from ..interfaces import FightingEntity, Map
|
||||||
class Monster(FightingEntity):
|
class Monster(FightingEntity):
|
||||||
"""
|
"""
|
||||||
The class for all monsters in the dungeon.
|
The class for all monsters in the dungeon.
|
||||||
A monster must override this class, and the parameters are given
|
All specific monster classes overwrite this class,
|
||||||
in the __init__ function.
|
and the parameters are given in the __init__ function.
|
||||||
An example of the specification of a monster that has a strength of 4
|
An example of the specification of a monster that has a strength of 4
|
||||||
and 20 max HP:
|
and 20 max HP:
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ class Monster(FightingEntity):
|
||||||
super().__init__(name="my_monster", strength=strength,
|
super().__init__(name="my_monster", strength=strength,
|
||||||
maxhealth=maxhealth, *args, **kwargs)
|
maxhealth=maxhealth, *args, **kwargs)
|
||||||
|
|
||||||
With that way, attributes can be overwritten when the entity got created.
|
With that way, attributes can be overwritten when the entity is created.
|
||||||
"""
|
"""
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
@ -29,7 +29,7 @@ class Monster(FightingEntity):
|
||||||
def act(self, m: Map) -> None:
|
def act(self, m: Map) -> None:
|
||||||
"""
|
"""
|
||||||
By default, a monster will move randomly where it is possible
|
By default, a monster will move randomly where it is possible
|
||||||
And if a player is close to the monster, the monster run on the player.
|
If the player is closeby, the monster runs to the player.
|
||||||
"""
|
"""
|
||||||
target = None
|
target = None
|
||||||
for entity in m.entities:
|
for entity in m.entities:
|
||||||
|
@ -38,12 +38,12 @@ class Monster(FightingEntity):
|
||||||
target = entity
|
target = entity
|
||||||
break
|
break
|
||||||
|
|
||||||
# A Dijkstra algorithm has ran that targets the player.
|
# Monsters move according to a Dijkstra algorithm
|
||||||
# With that way, monsters can simply follow the path.
|
# that targets the player.
|
||||||
# If they can't move and they are already close to the player,
|
# If they can not move and are already close to the player,
|
||||||
# They hit.
|
# they hit.
|
||||||
if target and (self.y, self.x) in target.paths:
|
if target and (self.y, self.x) in target.paths:
|
||||||
# Move to target player by choosing the best avaliable path
|
# Moves to target player by choosing the best available path
|
||||||
for next_y, next_x in target.paths[(self.y, self.x)]:
|
for next_y, next_x in target.paths[(self.y, self.x)]:
|
||||||
moved = self.check_move(next_y, next_x, True)
|
moved = self.check_move(next_y, next_x, True)
|
||||||
if moved:
|
if moved:
|
||||||
|
@ -52,8 +52,8 @@ class Monster(FightingEntity):
|
||||||
self.map.logs.add_message(self.hit(target))
|
self.map.logs.add_message(self.hit(target))
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
# Move in a random direction
|
# Moves in a random direction
|
||||||
# If the direction is not available, try another one
|
# If the direction is not available, tries another one
|
||||||
moves = [self.move_up, self.move_down,
|
moves = [self.move_up, self.move_down,
|
||||||
self.move_left, self.move_right]
|
self.move_left, self.move_right]
|
||||||
shuffle(moves)
|
shuffle(moves)
|
||||||
|
@ -61,10 +61,17 @@ class Monster(FightingEntity):
|
||||||
if move():
|
if move():
|
||||||
break
|
break
|
||||||
|
|
||||||
|
def move(self, y: int, x: int) -> None:
|
||||||
|
"""
|
||||||
|
Overwrites the move function to recalculate paths.
|
||||||
|
"""
|
||||||
|
super().move(y, x)
|
||||||
|
self.recalculate_paths()
|
||||||
|
|
||||||
|
|
||||||
class Tiger(Monster):
|
class Tiger(Monster):
|
||||||
"""
|
"""
|
||||||
A tiger monster
|
A tiger monster.
|
||||||
"""
|
"""
|
||||||
def __init__(self, name: str = "tiger", strength: int = 2,
|
def __init__(self, name: str = "tiger", strength: int = 2,
|
||||||
maxhealth: int = 20, *args, **kwargs) -> None:
|
maxhealth: int = 20, *args, **kwargs) -> None:
|
||||||
|
@ -74,7 +81,7 @@ class Tiger(Monster):
|
||||||
|
|
||||||
class Hedgehog(Monster):
|
class Hedgehog(Monster):
|
||||||
"""
|
"""
|
||||||
A really mean hedgehog monster
|
A really mean hedgehog monster.
|
||||||
"""
|
"""
|
||||||
def __init__(self, name: str = "hedgehog", strength: int = 3,
|
def __init__(self, name: str = "hedgehog", strength: int = 3,
|
||||||
maxhealth: int = 10, *args, **kwargs) -> None:
|
maxhealth: int = 10, *args, **kwargs) -> None:
|
||||||
|
@ -84,19 +91,31 @@ class Hedgehog(Monster):
|
||||||
|
|
||||||
class Rabbit(Monster):
|
class Rabbit(Monster):
|
||||||
"""
|
"""
|
||||||
A rabbit monster
|
A rabbit monster.
|
||||||
"""
|
"""
|
||||||
def __init__(self, name: str = "rabbit", strength: int = 1,
|
def __init__(self, name: str = "rabbit", strength: int = 1,
|
||||||
maxhealth: int = 15, *args, **kwargs) -> None:
|
maxhealth: int = 15, critical: int = 30,
|
||||||
|
*args, **kwargs) -> None:
|
||||||
super().__init__(name=name, strength=strength,
|
super().__init__(name=name, strength=strength,
|
||||||
maxhealth=maxhealth, *args, **kwargs)
|
maxhealth=maxhealth, critical=critical,
|
||||||
|
*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class TeddyBear(Monster):
|
class TeddyBear(Monster):
|
||||||
"""
|
"""
|
||||||
A cute teddybear monster
|
A cute teddybear monster.
|
||||||
"""
|
"""
|
||||||
def __init__(self, name: str = "teddy_bear", strength: int = 0,
|
def __init__(self, name: str = "teddy_bear", strength: int = 0,
|
||||||
maxhealth: int = 50, *args, **kwargs) -> None:
|
maxhealth: int = 50, *args, **kwargs) -> None:
|
||||||
super().__init__(name=name, strength=strength,
|
super().__init__(name=name, strength=strength,
|
||||||
maxhealth=maxhealth, *args, **kwargs)
|
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)
|
||||||
|
|
|
@ -1,37 +1,54 @@
|
||||||
# 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 functools import reduce
|
|
||||||
from queue import PriorityQueue
|
|
||||||
from random import randint
|
from random import randint
|
||||||
from typing import Dict, Tuple
|
from typing import Dict, Optional, Tuple
|
||||||
|
|
||||||
|
from .items import Item
|
||||||
from ..interfaces import FightingEntity, InventoryHolder
|
from ..interfaces import FightingEntity, InventoryHolder
|
||||||
|
|
||||||
|
|
||||||
class Player(InventoryHolder, FightingEntity):
|
class Player(InventoryHolder, FightingEntity):
|
||||||
"""
|
"""
|
||||||
The class of the player
|
The class of the player.
|
||||||
"""
|
"""
|
||||||
current_xp: int = 0
|
current_xp: int = 0
|
||||||
max_xp: int = 10
|
max_xp: int = 10
|
||||||
|
xp_buff: float = 1
|
||||||
paths: Dict[Tuple[int, int], Tuple[int, int]]
|
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,
|
def __init__(self, name: str = "player", maxhealth: int = 20,
|
||||||
strength: int = 5, intelligence: int = 1, charisma: int = 1,
|
strength: int = 5, intelligence: int = 1, charisma: int = 1,
|
||||||
dexterity: int = 1, constitution: int = 1, level: int = 1,
|
dexterity: int = 1, constitution: int = 1, level: int = 1,
|
||||||
current_xp: int = 0, max_xp: int = 10, inventory: list = None,
|
current_xp: int = 0, max_xp: int = 10, inventory: list = None,
|
||||||
hazel: int = 42, *args, **kwargs) \
|
hazel: int = 42, equipped_main: Optional[Item] = None,
|
||||||
-> 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,
|
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, critical=critical, *args, **kwargs)
|
||||||
self.current_xp = current_xp
|
self.current_xp = current_xp
|
||||||
self.max_xp = max_xp
|
self.max_xp = max_xp
|
||||||
|
self.xp_buff = xp_buff
|
||||||
self.inventory = self.translate_inventory(inventory or [])
|
self.inventory = self.translate_inventory(inventory or [])
|
||||||
self.paths = dict()
|
self.paths = dict()
|
||||||
self.hazel = hazel
|
self.hazel = hazel
|
||||||
|
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:
|
def move(self, y: int, x: int) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -42,10 +59,11 @@ class Player(InventoryHolder, FightingEntity):
|
||||||
self.map.currenty = y
|
self.map.currenty = y
|
||||||
self.map.currentx = x
|
self.map.currentx = x
|
||||||
self.recalculate_paths()
|
self.recalculate_paths()
|
||||||
|
self.map.compute_visibility(self.y, self.x, self.vision)
|
||||||
|
|
||||||
def level_up(self) -> None:
|
def level_up(self) -> None:
|
||||||
"""
|
"""
|
||||||
Add levels to the player as much as it is possible.
|
Add as many levels as possible to the player.
|
||||||
"""
|
"""
|
||||||
while self.current_xp > self.max_xp:
|
while self.current_xp > self.max_xp:
|
||||||
self.level += 1
|
self.level += 1
|
||||||
|
@ -59,12 +77,27 @@ class Player(InventoryHolder, FightingEntity):
|
||||||
|
|
||||||
def add_xp(self, xp: int) -> None:
|
def add_xp(self, xp: int) -> None:
|
||||||
"""
|
"""
|
||||||
Add some experience to the player.
|
Adds some experience to the player.
|
||||||
If the required amount is reached, level up.
|
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()
|
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
|
# noinspection PyTypeChecker,PyUnresolvedReferences
|
||||||
def check_move(self, y: int, x: int, move_if_possible: bool = False) \
|
def check_move(self, y: int, x: int, move_if_possible: bool = False) \
|
||||||
-> bool:
|
-> bool:
|
||||||
|
@ -87,56 +120,6 @@ class Player(InventoryHolder, FightingEntity):
|
||||||
entity.hold(self)
|
entity.hold(self)
|
||||||
return super().check_move(y, x, move_if_possible)
|
return super().check_move(y, x, move_if_possible)
|
||||||
|
|
||||||
def recalculate_paths(self, max_distance: int = 8) -> None:
|
|
||||||
"""
|
|
||||||
Use Dijkstra algorithm to calculate best paths for monsters to go to
|
|
||||||
the player. Actually, the paths are computed for each tile adjacent to
|
|
||||||
the player then for each step the monsters use the best path avaliable.
|
|
||||||
"""
|
|
||||||
distances = []
|
|
||||||
predecessors = []
|
|
||||||
# four Dijkstras, one for each adjacent tile
|
|
||||||
for dir_y, dir_x in [(1, 0), (-1, 0), (0, 1), (0, -1)]:
|
|
||||||
queue = PriorityQueue()
|
|
||||||
new_y, new_x = self.y + dir_y, self.x + dir_x
|
|
||||||
if not 0 <= new_y < self.map.height or \
|
|
||||||
not 0 <= new_x < self.map.width or \
|
|
||||||
not self.map.tiles[new_y][new_x].can_walk():
|
|
||||||
continue
|
|
||||||
queue.put(((1, 0), (new_y, new_x)))
|
|
||||||
visited = [(self.y, self.x)]
|
|
||||||
distances.append({(self.y, self.x): (0, 0), (new_y, new_x): (1, 0)})
|
|
||||||
predecessors.append({(new_y, new_x): (self.y, self.x)})
|
|
||||||
while not queue.empty():
|
|
||||||
dist, (y, x) = queue.get()
|
|
||||||
if dist[0] >= max_distance or (y, x) in visited:
|
|
||||||
continue
|
|
||||||
visited.append((y, x))
|
|
||||||
for diff_y, diff_x in [(1, 0), (-1, 0), (0, 1), (0, -1)]:
|
|
||||||
new_y, new_x = y + diff_y, x + diff_x
|
|
||||||
if not 0 <= new_y < self.map.height or \
|
|
||||||
not 0 <= new_x < self.map.width or \
|
|
||||||
not self.map.tiles[new_y][new_x].can_walk():
|
|
||||||
continue
|
|
||||||
new_distance = (dist[0] + 1,
|
|
||||||
dist[1] + (not self.map.is_free(y, x)))
|
|
||||||
if not (new_y, new_x) in distances[-1] or \
|
|
||||||
distances[-1][(new_y, new_x)] > new_distance:
|
|
||||||
predecessors[-1][(new_y, new_x)] = (y, x)
|
|
||||||
distances[-1][(new_y, new_x)] = new_distance
|
|
||||||
queue.put((new_distance, (new_y, new_x)))
|
|
||||||
# For each tile that is reached by at least one Dijkstra, sort the
|
|
||||||
# different paths by distance to the player. For the technical bits :
|
|
||||||
# The reduce function is a fold starting on the first element of the
|
|
||||||
# iterable, and we associate the points to their distance, sort
|
|
||||||
# along the distance, then only keep the points.
|
|
||||||
self.paths = {}
|
|
||||||
for y, x in reduce(set.union,
|
|
||||||
[set(p.keys()) for p in predecessors], set()):
|
|
||||||
self.paths[(y, x)] = [p for d, p in sorted(
|
|
||||||
[(distances[i][(y, x)], predecessors[i][(y, x)])
|
|
||||||
for i in range(len(distances)) if (y, x) in predecessors[i]])]
|
|
||||||
|
|
||||||
def save_state(self) -> dict:
|
def save_state(self) -> dict:
|
||||||
"""
|
"""
|
||||||
Saves the state of the entity into a dictionary
|
Saves the state of the entity into a dictionary
|
||||||
|
@ -144,4 +127,12 @@ class Player(InventoryHolder, 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["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
|
return d
|
||||||
|
|
|
@ -21,20 +21,20 @@ class DisplayActions(Enum):
|
||||||
|
|
||||||
class GameMode(Enum):
|
class GameMode(Enum):
|
||||||
"""
|
"""
|
||||||
Game mode options
|
Game mode options.
|
||||||
"""
|
"""
|
||||||
MAINMENU = auto()
|
MAINMENU = auto()
|
||||||
PLAY = auto()
|
PLAY = auto()
|
||||||
SETTINGS = auto()
|
SETTINGS = auto()
|
||||||
INVENTORY = auto()
|
INVENTORY = auto()
|
||||||
STORE = auto()
|
STORE = auto()
|
||||||
|
CREDITS = auto()
|
||||||
|
|
||||||
|
|
||||||
class KeyValues(Enum):
|
class KeyValues(Enum):
|
||||||
"""
|
"""
|
||||||
Key values options used in the game
|
Key values options used in the game.
|
||||||
"""
|
"""
|
||||||
MOUSE = auto()
|
|
||||||
UP = auto()
|
UP = auto()
|
||||||
DOWN = auto()
|
DOWN = auto()
|
||||||
LEFT = auto()
|
LEFT = auto()
|
||||||
|
@ -46,11 +46,13 @@ class KeyValues(Enum):
|
||||||
DROP = auto()
|
DROP = auto()
|
||||||
SPACE = auto()
|
SPACE = auto()
|
||||||
CHAT = auto()
|
CHAT = auto()
|
||||||
|
WAIT = auto()
|
||||||
|
LADDER = auto()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def translate_key(key: str, settings: Settings) -> Optional["KeyValues"]:
|
def translate_key(key: str, settings: Settings) -> Optional["KeyValues"]:
|
||||||
"""
|
"""
|
||||||
Translate the raw string key into an enum value that we can use.
|
Translates the raw string key into an enum value that we can use.
|
||||||
"""
|
"""
|
||||||
if key in (settings.KEY_DOWN_SECONDARY,
|
if key in (settings.KEY_DOWN_SECONDARY,
|
||||||
settings.KEY_DOWN_PRIMARY):
|
settings.KEY_DOWN_PRIMARY):
|
||||||
|
@ -78,4 +80,8 @@ class KeyValues(Enum):
|
||||||
return KeyValues.SPACE
|
return KeyValues.SPACE
|
||||||
elif key == settings.KEY_CHAT:
|
elif key == settings.KEY_CHAT:
|
||||||
return KeyValues.CHAT
|
return KeyValues.CHAT
|
||||||
|
elif key == settings.KEY_WAIT:
|
||||||
|
return KeyValues.WAIT
|
||||||
|
elif key == settings.KEY_LADDER:
|
||||||
|
return KeyValues.LADDER
|
||||||
return None
|
return None
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
from json import JSONDecodeError
|
from json import JSONDecodeError
|
||||||
from random import randint
|
from random import randint
|
||||||
from typing import Any, Optional
|
from typing import Any, Optional, List
|
||||||
import curses
|
import curses
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
@ -23,7 +23,8 @@ class Game:
|
||||||
"""
|
"""
|
||||||
The game object controls all actions in the game.
|
The game object controls all actions in the game.
|
||||||
"""
|
"""
|
||||||
map: Map
|
maps: List[Map]
|
||||||
|
map_index: int
|
||||||
player: Player
|
player: Player
|
||||||
screen: Any
|
screen: Any
|
||||||
# display_actions is a display interface set by the bootstrapper
|
# display_actions is a display interface set by the bootstrapper
|
||||||
|
@ -31,10 +32,11 @@ class Game:
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
"""
|
"""
|
||||||
Init the game.
|
Initiates the game.
|
||||||
"""
|
"""
|
||||||
self.state = GameMode.MAINMENU
|
self.state = GameMode.MAINMENU
|
||||||
self.waiting_for_friendly_key = False
|
self.waiting_for_friendly_key = False
|
||||||
|
self.is_in_store_menu = True
|
||||||
self.settings = Settings()
|
self.settings = Settings()
|
||||||
self.settings.load_settings()
|
self.settings.load_settings()
|
||||||
self.settings.write_settings()
|
self.settings.write_settings()
|
||||||
|
@ -49,8 +51,11 @@ class Game:
|
||||||
|
|
||||||
def new_game(self) -> None:
|
def new_game(self) -> None:
|
||||||
"""
|
"""
|
||||||
Create a new game on the screen.
|
Creates a new game on the screen.
|
||||||
"""
|
"""
|
||||||
|
# TODO generate a new map procedurally
|
||||||
|
self.maps = []
|
||||||
|
self.map_index = 0
|
||||||
self.map = broguelike.Generator().run()
|
self.map = broguelike.Generator().run()
|
||||||
self.map.logs = self.logs
|
self.map.logs = self.logs
|
||||||
self.logs.clear()
|
self.logs.clear()
|
||||||
|
@ -60,20 +65,44 @@ class Game:
|
||||||
self.map.spawn_random_entities(randint(3, 10))
|
self.map.spawn_random_entities(randint(3, 10))
|
||||||
self.inventory_menu.update_player(self.player)
|
self.inventory_menu.update_player(self.player)
|
||||||
|
|
||||||
def run(self, screen: Any) -> None:
|
@property
|
||||||
|
def map(self) -> Map:
|
||||||
|
"""
|
||||||
|
Return the current map where the user is.
|
||||||
|
"""
|
||||||
|
return self.maps[self.map_index]
|
||||||
|
|
||||||
|
@map.setter
|
||||||
|
def map(self, m: Map) -> None:
|
||||||
|
"""
|
||||||
|
Redefine the current map.
|
||||||
|
"""
|
||||||
|
if len(self.maps) == self.map_index:
|
||||||
|
# Insert new map
|
||||||
|
self.maps.append(m)
|
||||||
|
# Redefine the current map
|
||||||
|
self.maps[self.map_index] = m
|
||||||
|
|
||||||
|
def run(self, screen: Any) -> None: # pragma no cover
|
||||||
"""
|
"""
|
||||||
Main infinite loop.
|
Main infinite loop.
|
||||||
We wait for the player's action, then we do what that should be done
|
We wait for the player's action, then we do what should be done
|
||||||
when the given key gets pressed.
|
when a key gets pressed.
|
||||||
"""
|
"""
|
||||||
while True: # pragma no cover
|
screen.refresh()
|
||||||
|
while True:
|
||||||
screen.erase()
|
screen.erase()
|
||||||
screen.refresh()
|
screen.noutrefresh()
|
||||||
self.display_actions(DisplayActions.REFRESH)
|
self.display_actions(DisplayActions.REFRESH)
|
||||||
key = screen.getkey()
|
curses.doupdate()
|
||||||
|
try:
|
||||||
|
key = screen.getkey()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
exit(0)
|
||||||
|
return
|
||||||
if key == "KEY_MOUSE":
|
if key == "KEY_MOUSE":
|
||||||
_ignored1, x, y, _ignored2, _ignored3 = curses.getmouse()
|
_ignored1, x, y, _ignored2, attr = curses.getmouse()
|
||||||
self.display_actions(DisplayActions.MOUSE, y, x)
|
self.display_actions(DisplayActions.MOUSE, y, x, attr)
|
||||||
else:
|
else:
|
||||||
self.handle_key_pressed(
|
self.handle_key_pressed(
|
||||||
KeyValues.translate_key(key, self.settings), key)
|
KeyValues.translate_key(key, self.settings), key)
|
||||||
|
@ -81,7 +110,7 @@ class Game:
|
||||||
def handle_key_pressed(self, key: Optional[KeyValues], raw_key: str = '')\
|
def handle_key_pressed(self, key: Optional[KeyValues], raw_key: str = '')\
|
||||||
-> None:
|
-> None:
|
||||||
"""
|
"""
|
||||||
Indicates what should be done when the given key is pressed,
|
Indicates what should be done when a given key is pressed,
|
||||||
according to the current game state.
|
according to the current game state.
|
||||||
"""
|
"""
|
||||||
if self.message:
|
if self.message:
|
||||||
|
@ -103,36 +132,95 @@ class Game:
|
||||||
self.settings_menu.handle_key_pressed(key, raw_key, self)
|
self.settings_menu.handle_key_pressed(key, raw_key, self)
|
||||||
elif self.state == GameMode.STORE:
|
elif self.state == GameMode.STORE:
|
||||||
self.handle_key_pressed_store(key)
|
self.handle_key_pressed_store(key)
|
||||||
|
elif self.state == GameMode.CREDITS:
|
||||||
|
self.state = GameMode.MAINMENU
|
||||||
self.display_actions(DisplayActions.REFRESH)
|
self.display_actions(DisplayActions.REFRESH)
|
||||||
|
|
||||||
def handle_key_pressed_play(self, key: KeyValues) -> None:
|
def handle_key_pressed_play(self, key: KeyValues) -> None: # noqa: C901
|
||||||
"""
|
"""
|
||||||
In play mode, arrows or zqsd move the main character.
|
In play mode, arrows or zqsd move the main character.
|
||||||
"""
|
"""
|
||||||
if key == KeyValues.UP:
|
if key == KeyValues.UP:
|
||||||
if self.player.move_up():
|
if self.player.move_up():
|
||||||
self.map.tick()
|
self.map.tick(self.player)
|
||||||
elif key == KeyValues.DOWN:
|
elif key == KeyValues.DOWN:
|
||||||
if self.player.move_down():
|
if self.player.move_down():
|
||||||
self.map.tick()
|
self.map.tick(self.player)
|
||||||
elif key == KeyValues.LEFT:
|
elif key == KeyValues.LEFT:
|
||||||
if self.player.move_left():
|
if self.player.move_left():
|
||||||
self.map.tick()
|
self.map.tick(self.player)
|
||||||
elif key == KeyValues.RIGHT:
|
elif key == KeyValues.RIGHT:
|
||||||
if self.player.move_right():
|
if self.player.move_right():
|
||||||
self.map.tick()
|
self.map.tick(self.player)
|
||||||
elif key == KeyValues.INVENTORY:
|
elif key == KeyValues.INVENTORY:
|
||||||
self.state = GameMode.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:
|
elif key == KeyValues.SPACE:
|
||||||
self.state = GameMode.MAINMENU
|
self.state = GameMode.MAINMENU
|
||||||
elif key == KeyValues.CHAT:
|
elif key == KeyValues.CHAT:
|
||||||
# Wait for the direction of the friendly entity
|
# Wait for the direction of the friendly entity
|
||||||
self.waiting_for_friendly_key = True
|
self.waiting_for_friendly_key = True
|
||||||
|
elif key == KeyValues.WAIT:
|
||||||
|
self.map.tick(self.player)
|
||||||
|
elif key == KeyValues.LADDER:
|
||||||
|
self.handle_ladder()
|
||||||
|
|
||||||
|
def handle_ladder(self) -> None:
|
||||||
|
"""
|
||||||
|
The player pressed the ladder key to switch map
|
||||||
|
"""
|
||||||
|
# On a ladder, we switch level
|
||||||
|
y, x = self.player.y, self.player.x
|
||||||
|
if not self.map.tiles[y][x].is_ladder():
|
||||||
|
return
|
||||||
|
|
||||||
|
# We move up on the ladder of the beginning,
|
||||||
|
# down at the end of the stage
|
||||||
|
move_down = y != self.map.start_y and x != self.map.start_x
|
||||||
|
old_map = self.map
|
||||||
|
self.map_index += 1 if move_down else -1
|
||||||
|
if self.map_index == -1:
|
||||||
|
self.map_index = 0
|
||||||
|
return
|
||||||
|
while self.map_index >= len(self.maps):
|
||||||
|
# TODO: generate a new map
|
||||||
|
self.maps.append(Map.load(ResourceManager.get_asset_path(
|
||||||
|
"example_map_2.txt")))
|
||||||
|
new_map = self.map
|
||||||
|
new_map.floor = self.map_index
|
||||||
|
old_map.remove_entity(self.player)
|
||||||
|
new_map.add_entity(self.player)
|
||||||
|
if move_down:
|
||||||
|
self.player.move(self.map.start_y, self.map.start_x)
|
||||||
|
self.logs.add_message(
|
||||||
|
_("The player climbs down to the floor {floor}.")
|
||||||
|
.format(floor=-self.map_index))
|
||||||
|
else:
|
||||||
|
# Find the ladder of the end of the game
|
||||||
|
ladder_y, ladder_x = -1, -1
|
||||||
|
for y in range(self.map.height):
|
||||||
|
for x in range(self.map.width):
|
||||||
|
if (y, x) != (self.map.start_y, self.map.start_x) \
|
||||||
|
and self.map.tiles[y][x].is_ladder():
|
||||||
|
ladder_y, ladder_x = y, x
|
||||||
|
break
|
||||||
|
self.player.move(ladder_y, ladder_x)
|
||||||
|
self.logs.add_message(
|
||||||
|
_("The player climbs up the floor {floor}.")
|
||||||
|
.format(floor=-self.map_index))
|
||||||
|
|
||||||
|
self.display_actions(DisplayActions.UPDATE)
|
||||||
|
|
||||||
def handle_friendly_entity_chat(self, key: KeyValues) -> None:
|
def handle_friendly_entity_chat(self, key: KeyValues) -> None:
|
||||||
"""
|
"""
|
||||||
If the player is talking to a friendly entity, we get the direction
|
If the player tries to talk to a friendly entity, the game waits for
|
||||||
where the entity is, then we interact with it.
|
a directional key to be pressed, verifies there is a friendly entity
|
||||||
|
in that direction and then lets the player interact with it.
|
||||||
"""
|
"""
|
||||||
if not self.waiting_for_friendly_key:
|
if not self.waiting_for_friendly_key:
|
||||||
return
|
return
|
||||||
|
@ -160,7 +248,9 @@ class Game:
|
||||||
self.logs.add_message(msg)
|
self.logs.add_message(msg)
|
||||||
if entity.is_merchant():
|
if entity.is_merchant():
|
||||||
self.state = GameMode.STORE
|
self.state = GameMode.STORE
|
||||||
|
self.is_in_store_menu = True
|
||||||
self.store_menu.update_merchant(entity)
|
self.store_menu.update_merchant(entity)
|
||||||
|
self.display_actions(DisplayActions.UPDATE)
|
||||||
|
|
||||||
def handle_key_pressed_inventory(self, key: KeyValues) -> None:
|
def handle_key_pressed_inventory(self, key: KeyValues) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -189,26 +279,37 @@ class Game:
|
||||||
"""
|
"""
|
||||||
In a store menu, we can buy items or close the menu.
|
In a store menu, we can buy items or close the menu.
|
||||||
"""
|
"""
|
||||||
if key == KeyValues.SPACE:
|
menu = self.store_menu if self.is_in_store_menu else self.inventory_menu
|
||||||
|
|
||||||
|
if key == KeyValues.SPACE or key == KeyValues.INVENTORY:
|
||||||
self.state = GameMode.PLAY
|
self.state = GameMode.PLAY
|
||||||
elif key == KeyValues.UP:
|
elif key == KeyValues.UP:
|
||||||
self.store_menu.go_up()
|
menu.go_up()
|
||||||
elif key == KeyValues.DOWN:
|
elif key == KeyValues.DOWN:
|
||||||
self.store_menu.go_down()
|
menu.go_down()
|
||||||
if self.store_menu.values and not self.player.dead:
|
elif key == KeyValues.LEFT:
|
||||||
|
self.is_in_store_menu = False
|
||||||
|
self.display_actions(DisplayActions.UPDATE)
|
||||||
|
elif key == KeyValues.RIGHT:
|
||||||
|
self.is_in_store_menu = True
|
||||||
|
self.display_actions(DisplayActions.UPDATE)
|
||||||
|
if menu.values and not self.player.dead:
|
||||||
if key == KeyValues.ENTER:
|
if key == KeyValues.ENTER:
|
||||||
item = self.store_menu.validate()
|
item = menu.validate()
|
||||||
flag = item.be_sold(self.player, self.store_menu.merchant)
|
owner = self.store_menu.merchant if self.is_in_store_menu \
|
||||||
|
else self.player
|
||||||
|
buyer = self.player if self.is_in_store_menu \
|
||||||
|
else self.store_menu.merchant
|
||||||
|
flag = item.be_sold(buyer, owner)
|
||||||
if not flag:
|
if not flag:
|
||||||
self.message = _("You do not have enough money")
|
self.message = _("The buyer does not have enough money")
|
||||||
self.display_actions(DisplayActions.UPDATE)
|
self.display_actions(DisplayActions.UPDATE)
|
||||||
# Ensure that the cursor has a good position
|
# Ensure that the cursor has a good position
|
||||||
self.store_menu.position = min(self.store_menu.position,
|
menu.position = min(menu.position, len(menu.values) - 1)
|
||||||
len(self.store_menu.values) - 1)
|
|
||||||
|
|
||||||
def handle_key_pressed_main_menu(self, key: KeyValues) -> None:
|
def handle_key_pressed_main_menu(self, key: KeyValues) -> None:
|
||||||
"""
|
"""
|
||||||
In the main menu, we can navigate through options.
|
In the main menu, we can navigate through different options.
|
||||||
"""
|
"""
|
||||||
if key == KeyValues.DOWN:
|
if key == KeyValues.DOWN:
|
||||||
self.main_menu.go_down()
|
self.main_menu.go_down()
|
||||||
|
@ -233,16 +334,18 @@ class Game:
|
||||||
|
|
||||||
def save_state(self) -> dict:
|
def save_state(self) -> dict:
|
||||||
"""
|
"""
|
||||||
Saves the game to a dictionary
|
Saves the game to a dictionary.
|
||||||
"""
|
"""
|
||||||
return self.map.save_state()
|
return dict(map_index=self.map_index,
|
||||||
|
maps=[m.save_state() for m in self.maps])
|
||||||
|
|
||||||
def load_state(self, d: dict) -> None:
|
def load_state(self, d: dict) -> None:
|
||||||
"""
|
"""
|
||||||
Loads the game from a dictionary
|
Loads the game from a dictionary.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
self.map.load_state(d)
|
self.map_index = d["map_index"]
|
||||||
|
self.maps = [Map().load_state(map_dict) for map_dict in d["maps"]]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
self.message = _("Some keys are missing in your save file.\n"
|
self.message = _("Some keys are missing in your save file.\n"
|
||||||
"Your save seems to be corrupt. It got deleted.")
|
"Your save seems to be corrupt. It got deleted.")
|
||||||
|
@ -259,11 +362,13 @@ class Game:
|
||||||
return
|
return
|
||||||
|
|
||||||
self.player = players[0]
|
self.player = players[0]
|
||||||
|
self.map.compute_visibility(self.player.y, self.player.x,
|
||||||
|
self.player.vision)
|
||||||
self.display_actions(DisplayActions.UPDATE)
|
self.display_actions(DisplayActions.UPDATE)
|
||||||
|
|
||||||
def load_game(self) -> None:
|
def load_game(self) -> None:
|
||||||
"""
|
"""
|
||||||
Loads the game from a file
|
Loads the game from a file.
|
||||||
"""
|
"""
|
||||||
file_path = ResourceManager.get_config_path("save.json")
|
file_path = ResourceManager.get_config_path("save.json")
|
||||||
if os.path.isfile(file_path):
|
if os.path.isfile(file_path):
|
||||||
|
@ -280,7 +385,7 @@ class Game:
|
||||||
|
|
||||||
def save_game(self) -> None:
|
def save_game(self) -> None:
|
||||||
"""
|
"""
|
||||||
Saves the game to a file
|
Saves the game to a file.
|
||||||
"""
|
"""
|
||||||
with open(ResourceManager.get_config_path("save.json"), "w") as f:
|
with open(ResourceManager.get_config_path("save.json"), "w") as f:
|
||||||
f.write(json.dumps(self.save_state()))
|
f.write(json.dumps(self.save_state()))
|
||||||
|
|
|
@ -2,10 +2,12 @@
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
from enum import Enum, auto
|
from enum import Enum, auto
|
||||||
from math import sqrt
|
from math import ceil, sqrt
|
||||||
from random import choice, randint
|
|
||||||
from typing import List, Optional, Any
|
|
||||||
from itertools import product
|
from itertools import product
|
||||||
|
from random import choice, choices, randint
|
||||||
|
from typing import List, Optional, Any, Dict, Tuple
|
||||||
|
from queue import PriorityQueue
|
||||||
|
from functools import reduce
|
||||||
|
|
||||||
from .display.texturepack import TexturePack
|
from .display.texturepack import TexturePack
|
||||||
from .translations import gettext as _
|
from .translations import gettext as _
|
||||||
|
@ -13,7 +15,7 @@ from .translations import gettext as _
|
||||||
|
|
||||||
class Logs:
|
class Logs:
|
||||||
"""
|
"""
|
||||||
The logs object stores the messages to display. It is encapsulating a list
|
The logs object stores the messages to display. It encapsulates a list
|
||||||
of such messages, to allow multiple pointers to keep track of it even if
|
of such messages, to allow multiple pointers to keep track of it even if
|
||||||
the list was to be reassigned.
|
the list was to be reassigned.
|
||||||
"""
|
"""
|
||||||
|
@ -31,16 +33,47 @@ class Logs:
|
||||||
self.messages = []
|
self.messages = []
|
||||||
|
|
||||||
|
|
||||||
|
class Slope():
|
||||||
|
X: int
|
||||||
|
Y: int
|
||||||
|
|
||||||
|
def __init__(self, y: int, x: int) -> None:
|
||||||
|
self.Y = y
|
||||||
|
self.X = x
|
||||||
|
|
||||||
|
def compare(self, other: "Slope") -> int:
|
||||||
|
y, x = other.Y, other.X
|
||||||
|
return self.Y * x - self.X * y
|
||||||
|
|
||||||
|
def __lt__(self, other: "Slope") -> bool:
|
||||||
|
return self.compare(other) < 0
|
||||||
|
|
||||||
|
def __eq__(self, other: "Slope") -> bool:
|
||||||
|
return self.compare(other) == 0
|
||||||
|
|
||||||
|
def __gt__(self, other: "Slope") -> bool:
|
||||||
|
return self.compare(other) > 0
|
||||||
|
|
||||||
|
def __le__(self, other: "Slope") -> bool:
|
||||||
|
return self.compare(other) <= 0
|
||||||
|
|
||||||
|
def __ge__(self, other: "Slope") -> bool:
|
||||||
|
return self.compare(other) >= 0
|
||||||
|
|
||||||
|
|
||||||
class Map:
|
class Map:
|
||||||
"""
|
"""
|
||||||
Object that represents a Map with its width, height
|
The Map object represents a with its width, height
|
||||||
and tiles, that have their custom properties.
|
and tiles, that have their custom properties.
|
||||||
"""
|
"""
|
||||||
|
floor: int
|
||||||
width: int
|
width: int
|
||||||
height: int
|
height: int
|
||||||
start_y: int
|
start_y: int
|
||||||
start_x: int
|
start_x: int
|
||||||
tiles: List[List["Tile"]]
|
tiles: List[List["Tile"]]
|
||||||
|
visibility: List[List[bool]]
|
||||||
|
seen_tiles: List[List[bool]]
|
||||||
entities: List["Entity"]
|
entities: List["Entity"]
|
||||||
logs: Logs
|
logs: Logs
|
||||||
# coordinates of the point that should be
|
# coordinates of the point that should be
|
||||||
|
@ -48,28 +81,36 @@ class Map:
|
||||||
currentx: int
|
currentx: int
|
||||||
currenty: int
|
currenty: int
|
||||||
|
|
||||||
def __init__(self, width: int, height: int, tiles: list,
|
def __init__(self, width: int = 0, height: int = 0, tiles: list = None,
|
||||||
start_y: int, start_x: int):
|
start_y: int = 0, start_x: int = 0):
|
||||||
|
self.floor = 0
|
||||||
self.width = width
|
self.width = width
|
||||||
self.height = height
|
self.height = height
|
||||||
self.start_y = start_y
|
self.start_y = start_y
|
||||||
self.start_x = start_x
|
self.start_x = start_x
|
||||||
self.currenty = start_y
|
self.currenty = start_y
|
||||||
self.currentx = start_x
|
self.currentx = start_x
|
||||||
self.tiles = tiles
|
self.tiles = tiles or []
|
||||||
|
self.visibility = [[False for _ in range(len(self.tiles[0]))]
|
||||||
|
for _ in range(len(self.tiles))]
|
||||||
|
self.seen_tiles = [[False for _ in range(len(tiles[0]))]
|
||||||
|
for _ in range(len(self.tiles))]
|
||||||
self.entities = []
|
self.entities = []
|
||||||
self.logs = Logs()
|
self.logs = Logs()
|
||||||
|
|
||||||
def add_entity(self, entity: "Entity") -> None:
|
def add_entity(self, entity: "Entity") -> None:
|
||||||
"""
|
"""
|
||||||
Register a new entity in the map.
|
Registers a new entity in the map.
|
||||||
"""
|
"""
|
||||||
self.entities.append(entity)
|
if entity.is_familiar():
|
||||||
|
self.entities.insert(1, entity)
|
||||||
|
else:
|
||||||
|
self.entities.append(entity)
|
||||||
entity.map = self
|
entity.map = self
|
||||||
|
|
||||||
def remove_entity(self, entity: "Entity") -> None:
|
def remove_entity(self, entity: "Entity") -> None:
|
||||||
"""
|
"""
|
||||||
Unregister an entity from the map.
|
Unregisters an entity from the map.
|
||||||
"""
|
"""
|
||||||
if entity in self.entities:
|
if entity in self.entities:
|
||||||
self.entities.remove(entity)
|
self.entities.remove(entity)
|
||||||
|
@ -89,7 +130,7 @@ class Map:
|
||||||
def entity_is_present(self, y: int, x: int) -> bool:
|
def entity_is_present(self, y: int, x: int) -> bool:
|
||||||
"""
|
"""
|
||||||
Indicates that the tile at the coordinates (y, x) contains a killable
|
Indicates that the tile at the coordinates (y, x) contains a killable
|
||||||
entity
|
entity.
|
||||||
"""
|
"""
|
||||||
return 0 <= y < self.height and 0 <= x < self.width and \
|
return 0 <= y < self.height and 0 <= x < self.width and \
|
||||||
any(entity.x == x and entity.y == y and entity.is_friendly()
|
any(entity.x == x and entity.y == y and entity.is_friendly()
|
||||||
|
@ -98,7 +139,8 @@ class Map:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def load(filename: str) -> "Map":
|
def load(filename: str) -> "Map":
|
||||||
"""
|
"""
|
||||||
Read a file that contains the content of a map, and build a Map object.
|
Reads a file that contains the content of a map,
|
||||||
|
and builds a Map object.
|
||||||
"""
|
"""
|
||||||
with open(filename, "r") as f:
|
with open(filename, "r") as f:
|
||||||
file = f.read()
|
file = f.read()
|
||||||
|
@ -107,7 +149,7 @@ class Map:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def load_from_string(content: str) -> "Map":
|
def load_from_string(content: str) -> "Map":
|
||||||
"""
|
"""
|
||||||
Load a map represented by its characters and build a Map object.
|
Loads a map represented by its characters and builds a Map object.
|
||||||
"""
|
"""
|
||||||
lines = content.split("\n")
|
lines = content.split("\n")
|
||||||
first_line = lines[0]
|
first_line = lines[0]
|
||||||
|
@ -123,7 +165,7 @@ class Map:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def load_dungeon_from_string(content: str) -> List[List["Tile"]]:
|
def load_dungeon_from_string(content: str) -> List[List["Tile"]]:
|
||||||
"""
|
"""
|
||||||
Transforms a string into the list of corresponding tiles
|
Transforms a string into the list of corresponding tiles.
|
||||||
"""
|
"""
|
||||||
lines = content.split("\n")
|
lines = content.split("\n")
|
||||||
tiles = [[Tile.from_ascii_char(c)
|
tiles = [[Tile.from_ascii_char(c)
|
||||||
|
@ -132,7 +174,7 @@ class Map:
|
||||||
|
|
||||||
def draw_string(self, pack: TexturePack) -> str:
|
def draw_string(self, pack: TexturePack) -> str:
|
||||||
"""
|
"""
|
||||||
Draw the current map as a string object that can be rendered
|
Draws the current map as a string object that can be rendered
|
||||||
in the window.
|
in the window.
|
||||||
"""
|
"""
|
||||||
return "\n".join("".join(tile.char(pack) for tile in line)
|
return "\n".join("".join(tile.char(pack) for tile in line)
|
||||||
|
@ -140,29 +182,153 @@ class Map:
|
||||||
|
|
||||||
def spawn_random_entities(self, count: int) -> None:
|
def spawn_random_entities(self, count: int) -> None:
|
||||||
"""
|
"""
|
||||||
Put randomly {count} entities on the map, where it is available.
|
Puts randomly {count} entities on the map, only on empty ground tiles.
|
||||||
"""
|
"""
|
||||||
for ignored in range(count):
|
for _ignored in range(count):
|
||||||
y, x = 0, 0
|
y, x = 0, 0
|
||||||
while True:
|
while True:
|
||||||
y, x = randint(0, self.height - 1), randint(0, self.width - 1)
|
y, x = randint(0, self.height - 1), randint(0, self.width - 1)
|
||||||
tile = self.tiles[y][x]
|
tile = self.tiles[y][x]
|
||||||
if tile.can_walk():
|
if tile.can_walk():
|
||||||
break
|
break
|
||||||
entity = choice(Entity.get_all_entity_classes())()
|
entity = choices(Entity.get_all_entity_classes(),
|
||||||
|
weights=Entity.get_weights(), k=1)[0]()
|
||||||
entity.move(y, x)
|
entity.move(y, x)
|
||||||
self.add_entity(entity)
|
self.add_entity(entity)
|
||||||
|
|
||||||
def tick(self) -> None:
|
def compute_visibility(self, y: int, x: int, max_range: int) -> None:
|
||||||
"""
|
"""
|
||||||
Trigger all entity events.
|
Sets the visible tiles to be the ones visible by an entity at point
|
||||||
|
(y, x), using a twaked shadow casting algorithm
|
||||||
|
"""
|
||||||
|
|
||||||
|
for line in self.visibility:
|
||||||
|
for i in range(len(line)):
|
||||||
|
line[i] = False
|
||||||
|
self.set_visible(0, 0, 0, (y, x))
|
||||||
|
for octant in range(8):
|
||||||
|
self.compute_visibility_octant(octant, (y, x), max_range, 1,
|
||||||
|
Slope(1, 1), Slope(0, 1))
|
||||||
|
|
||||||
|
def crop_top_visibility(self, octant: int, origin: Tuple[int, int],
|
||||||
|
x: int, top: Slope) -> int:
|
||||||
|
if top.X == 1:
|
||||||
|
top_y = x
|
||||||
|
else:
|
||||||
|
top_y = ceil(((x * 2 - 1) * top.Y + top.X) / (top.X * 2))
|
||||||
|
if self.is_wall(top_y, x, octant, origin):
|
||||||
|
top_y += top >= Slope(top_y * 2 + 1, x * 2) and not \
|
||||||
|
self.is_wall(top_y + 1, x, octant, origin)
|
||||||
|
else:
|
||||||
|
ax = x * 2
|
||||||
|
ax += self.is_wall(top_y + 1, x + 1, octant, origin)
|
||||||
|
top_y += top > Slope(top_y * 2 + 1, ax)
|
||||||
|
return top_y
|
||||||
|
|
||||||
|
def crop_bottom_visibility(self, octant: int, origin: Tuple[int, int],
|
||||||
|
x: int, bottom: Slope) -> int:
|
||||||
|
if bottom.Y == 0:
|
||||||
|
bottom_y = 0
|
||||||
|
else:
|
||||||
|
bottom_y = ceil(((x * 2 - 1) * bottom.Y + bottom.X)
|
||||||
|
/ (bottom.X * 2))
|
||||||
|
bottom_y += bottom >= Slope(bottom_y * 2 + 1, x * 2) and \
|
||||||
|
self.is_wall(bottom_y, x, octant, origin) and \
|
||||||
|
not self.is_wall(bottom_y + 1, x, octant, origin)
|
||||||
|
return bottom_y
|
||||||
|
|
||||||
|
def compute_visibility_octant(self, octant: int, origin: Tuple[int, int],
|
||||||
|
max_range: int, distance: int, top: Slope,
|
||||||
|
bottom: Slope) -> None:
|
||||||
|
for x in range(distance, max_range + 1):
|
||||||
|
top_y = self.crop_top_visibility(octant, origin, x, top)
|
||||||
|
bottom_y = self.crop_bottom_visibility(octant, origin, x, bottom)
|
||||||
|
was_opaque = -1
|
||||||
|
for y in range(top_y, bottom_y - 1, -1):
|
||||||
|
if x + y > max_range:
|
||||||
|
continue
|
||||||
|
is_opaque = self.is_wall(y, x, octant, origin)
|
||||||
|
is_visible = is_opaque\
|
||||||
|
or ((y != top_y or top > Slope(y * 4 - 1, x * 4 + 1))
|
||||||
|
and (y != bottom_y
|
||||||
|
or bottom < Slope(y * 4 + 1, x * 4 - 1)))
|
||||||
|
# is_visible = is_opaque\
|
||||||
|
# or ((y != top_y or top >= Slope(y, x))
|
||||||
|
# and (y != bottom_y or bottom <= Slope(y, x)))
|
||||||
|
if is_visible:
|
||||||
|
self.set_visible(y, x, octant, origin)
|
||||||
|
if x == max_range:
|
||||||
|
continue
|
||||||
|
if is_opaque and was_opaque == 0:
|
||||||
|
nx, ny = x * 2, y * 2 + 1
|
||||||
|
nx -= self.is_wall(y + 1, x, octant, origin)
|
||||||
|
if top > Slope(ny, nx):
|
||||||
|
if y == bottom_y:
|
||||||
|
bottom = Slope(ny, nx)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
self.compute_visibility_octant(
|
||||||
|
octant, origin, max_range, x + 1, top,
|
||||||
|
Slope(ny, nx))
|
||||||
|
elif y == bottom_y: # pragma: no cover
|
||||||
|
return
|
||||||
|
elif not is_opaque and was_opaque == 1:
|
||||||
|
nx, ny = x * 2, y * 2 + 1
|
||||||
|
nx += self.is_wall(y + 1, x + 1, octant, origin)
|
||||||
|
if bottom >= Slope(ny, nx): # pragma: no cover
|
||||||
|
return
|
||||||
|
top = Slope(ny, nx)
|
||||||
|
was_opaque = is_opaque
|
||||||
|
if was_opaque != 0:
|
||||||
|
break
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def translate_coord(y: int, x: int, octant: int,
|
||||||
|
origin: Tuple[int, int]) -> Tuple[int, int]:
|
||||||
|
ny, nx = origin
|
||||||
|
if octant == 0:
|
||||||
|
return ny - y, nx + x
|
||||||
|
elif octant == 1:
|
||||||
|
return ny - x, nx + y
|
||||||
|
elif octant == 2:
|
||||||
|
return ny - x, nx - y
|
||||||
|
elif octant == 3:
|
||||||
|
return ny - y, nx - x
|
||||||
|
elif octant == 4:
|
||||||
|
return ny + y, nx - x
|
||||||
|
elif octant == 5:
|
||||||
|
return ny + x, nx - y
|
||||||
|
elif octant == 6:
|
||||||
|
return ny + x, nx + y
|
||||||
|
elif octant == 7:
|
||||||
|
return ny + y, nx + x
|
||||||
|
|
||||||
|
def is_wall(self, y: int, x: int, octant: int,
|
||||||
|
origin: Tuple[int, int]) -> bool:
|
||||||
|
y, x = self.translate_coord(y, x, octant, origin)
|
||||||
|
return 0 <= y < len(self.tiles) and 0 <= x < len(self.tiles[0]) and \
|
||||||
|
self.tiles[y][x].is_wall()
|
||||||
|
|
||||||
|
def set_visible(self, y: int, x: int, octant: int,
|
||||||
|
origin: Tuple[int, int]) -> None:
|
||||||
|
y, x = self.translate_coord(y, x, octant, origin)
|
||||||
|
if 0 <= y < len(self.tiles) and 0 <= x < len(self.tiles[0]):
|
||||||
|
self.visibility[y][x] = True
|
||||||
|
self.seen_tiles[y][x] = True
|
||||||
|
|
||||||
|
def tick(self, p: Any) -> None:
|
||||||
|
"""
|
||||||
|
Triggers all entity events.
|
||||||
"""
|
"""
|
||||||
for entity in self.entities:
|
for entity in self.entities:
|
||||||
entity.act(self)
|
if entity.is_familiar():
|
||||||
|
entity.act(p, self)
|
||||||
|
else:
|
||||||
|
entity.act(self)
|
||||||
|
|
||||||
def save_state(self) -> dict:
|
def save_state(self) -> dict:
|
||||||
"""
|
"""
|
||||||
Saves the map's attributes to a dictionary
|
Saves the map's attributes to a dictionary.
|
||||||
"""
|
"""
|
||||||
d = dict()
|
d = dict()
|
||||||
d["width"] = self.width
|
d["width"] = self.width
|
||||||
|
@ -175,11 +341,12 @@ class Map:
|
||||||
for enti in self.entities:
|
for enti in self.entities:
|
||||||
d["entities"].append(enti.save_state())
|
d["entities"].append(enti.save_state())
|
||||||
d["map"] = self.draw_string(TexturePack.ASCII_PACK)
|
d["map"] = self.draw_string(TexturePack.ASCII_PACK)
|
||||||
|
d["seen_tiles"] = self.seen_tiles
|
||||||
return d
|
return d
|
||||||
|
|
||||||
def load_state(self, d: dict) -> None:
|
def load_state(self, d: dict) -> "Map":
|
||||||
"""
|
"""
|
||||||
Loads the map's attributes from a dictionary
|
Loads the map's attributes from a dictionary.
|
||||||
"""
|
"""
|
||||||
self.width = d["width"]
|
self.width = d["width"]
|
||||||
self.height = d["height"]
|
self.height = d["height"]
|
||||||
|
@ -188,11 +355,16 @@ class Map:
|
||||||
self.currentx = d["currentx"]
|
self.currentx = d["currentx"]
|
||||||
self.currenty = d["currenty"]
|
self.currenty = d["currenty"]
|
||||||
self.tiles = self.load_dungeon_from_string(d["map"])
|
self.tiles = self.load_dungeon_from_string(d["map"])
|
||||||
|
self.seen_tiles = d["seen_tiles"]
|
||||||
|
self.visibility = [[False for _ in range(len(self.tiles[0]))]
|
||||||
|
for _ in range(len(self.tiles))]
|
||||||
self.entities = []
|
self.entities = []
|
||||||
dictclasses = Entity.get_all_entity_classes_in_a_dict()
|
dictclasses = Entity.get_all_entity_classes_in_a_dict()
|
||||||
for entisave in d["entities"]:
|
for entisave in d["entities"]:
|
||||||
self.add_entity(dictclasses[entisave["type"]](**entisave))
|
self.add_entity(dictclasses[entisave["type"]](**entisave))
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def neighbourhood(grid: List[List["Tile"]], y: int, x: int,
|
def neighbourhood(grid: List[List["Tile"]], y: int, x: int,
|
||||||
large: bool = False, oob: bool = False) \
|
large: bool = False, oob: bool = False) \
|
||||||
|
@ -217,16 +389,17 @@ class Map:
|
||||||
|
|
||||||
class Tile(Enum):
|
class Tile(Enum):
|
||||||
"""
|
"""
|
||||||
The internal representation of the tiles of the map
|
The internal representation of the tiles of the map.
|
||||||
"""
|
"""
|
||||||
EMPTY = auto()
|
EMPTY = auto()
|
||||||
WALL = auto()
|
WALL = auto()
|
||||||
FLOOR = auto()
|
FLOOR = auto()
|
||||||
|
LADDER = auto()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_ascii_char(ch: str) -> "Tile":
|
def from_ascii_char(ch: str) -> "Tile":
|
||||||
"""
|
"""
|
||||||
Maps an ascii character to its equivalent in the texture pack
|
Maps an ascii character to its equivalent in the texture pack.
|
||||||
"""
|
"""
|
||||||
for tile in Tile:
|
for tile in Tile:
|
||||||
if tile.char(TexturePack.ASCII_PACK) == ch:
|
if tile.char(TexturePack.ASCII_PACK) == ch:
|
||||||
|
@ -236,9 +409,27 @@ class Tile(Enum):
|
||||||
def char(self, pack: TexturePack) -> str:
|
def char(self, pack: TexturePack) -> str:
|
||||||
"""
|
"""
|
||||||
Translates a Tile to the corresponding character according
|
Translates a Tile to the corresponding character according
|
||||||
to the texture pack
|
to the texture pack.
|
||||||
"""
|
"""
|
||||||
return getattr(pack, self.name)
|
val = getattr(pack, self.name)
|
||||||
|
return val[0] if isinstance(val, tuple) else val
|
||||||
|
|
||||||
|
def visible_color(self, pack: TexturePack) -> Tuple[int, int]:
|
||||||
|
"""
|
||||||
|
Retrieve the tuple (fg_color, bg_color) of the current Tile
|
||||||
|
if it is visible.
|
||||||
|
"""
|
||||||
|
val = getattr(pack, self.name)
|
||||||
|
return (val[2], val[4]) if isinstance(val, tuple) else \
|
||||||
|
(pack.tile_fg_visible_color, pack.tile_bg_color)
|
||||||
|
|
||||||
|
def hidden_color(self, pack: TexturePack) -> Tuple[int, int]:
|
||||||
|
"""
|
||||||
|
Retrieve the tuple (fg_color, bg_color) of the current Tile.
|
||||||
|
"""
|
||||||
|
val = getattr(pack, self.name)
|
||||||
|
return (val[1], val[3]) if isinstance(val, tuple) else \
|
||||||
|
(pack.tile_fg_color, pack.tile_bg_color)
|
||||||
|
|
||||||
def is_wall(self) -> bool:
|
def is_wall(self) -> bool:
|
||||||
"""
|
"""
|
||||||
|
@ -246,21 +437,28 @@ class Tile(Enum):
|
||||||
"""
|
"""
|
||||||
return self == Tile.WALL
|
return self == Tile.WALL
|
||||||
|
|
||||||
|
def is_ladder(self) -> bool:
|
||||||
|
"""
|
||||||
|
Is this Tile a ladder?
|
||||||
|
"""
|
||||||
|
return self == Tile.LADDER
|
||||||
|
|
||||||
def can_walk(self) -> bool:
|
def can_walk(self) -> bool:
|
||||||
"""
|
"""
|
||||||
Check if an entity (player or not) can move in this tile.
|
Checks if an entity (player or not) can move in this tile.
|
||||||
"""
|
"""
|
||||||
return not self.is_wall() and self != Tile.EMPTY
|
return not self.is_wall() and self != Tile.EMPTY
|
||||||
|
|
||||||
|
|
||||||
class Entity:
|
class Entity:
|
||||||
"""
|
"""
|
||||||
An Entity object represents any entity present on the map
|
An Entity object represents any entity present on the map.
|
||||||
"""
|
"""
|
||||||
y: int
|
y: int
|
||||||
x: int
|
x: int
|
||||||
name: str
|
name: str
|
||||||
map: Map
|
map: Map
|
||||||
|
paths: Dict[Tuple[int, int], Tuple[int, int]]
|
||||||
|
|
||||||
# noinspection PyShadowingBuiltins
|
# noinspection PyShadowingBuiltins
|
||||||
def __init__(self, y: int = 0, x: int = 0, name: Optional[str] = None,
|
def __init__(self, y: int = 0, x: int = 0, name: Optional[str] = None,
|
||||||
|
@ -269,11 +467,12 @@ class Entity:
|
||||||
self.x = x
|
self.x = x
|
||||||
self.name = name
|
self.name = name
|
||||||
self.map = map
|
self.map = map
|
||||||
|
self.paths = None
|
||||||
|
|
||||||
def check_move(self, y: int, x: int, move_if_possible: bool = False)\
|
def check_move(self, y: int, x: int, move_if_possible: bool = False)\
|
||||||
-> bool:
|
-> bool:
|
||||||
"""
|
"""
|
||||||
Checks if moving to (y,x) is authorized
|
Checks if moving to (y,x) is authorized.
|
||||||
"""
|
"""
|
||||||
free = self.map.is_free(y, x)
|
free = self.map.is_free(y, x)
|
||||||
if free and move_if_possible:
|
if free and move_if_possible:
|
||||||
|
@ -282,7 +481,7 @@ class Entity:
|
||||||
|
|
||||||
def move(self, y: int, x: int) -> bool:
|
def move(self, y: int, x: int) -> bool:
|
||||||
"""
|
"""
|
||||||
Moves an entity to (y,x) coordinates
|
Moves an entity to (y,x) coordinates.
|
||||||
"""
|
"""
|
||||||
self.y = y
|
self.y = y
|
||||||
self.x = x
|
self.x = x
|
||||||
|
@ -290,49 +489,100 @@ class Entity:
|
||||||
|
|
||||||
def move_up(self, force: bool = False) -> bool:
|
def move_up(self, force: bool = False) -> bool:
|
||||||
"""
|
"""
|
||||||
Moves the entity up one tile, if possible
|
Moves the entity up one tile, if possible.
|
||||||
"""
|
"""
|
||||||
return self.move(self.y - 1, self.x) if force else \
|
return self.move(self.y - 1, self.x) if force else \
|
||||||
self.check_move(self.y - 1, self.x, True)
|
self.check_move(self.y - 1, self.x, True)
|
||||||
|
|
||||||
def move_down(self, force: bool = False) -> bool:
|
def move_down(self, force: bool = False) -> bool:
|
||||||
"""
|
"""
|
||||||
Moves the entity down one tile, if possible
|
Moves the entity down one tile, if possible.
|
||||||
"""
|
"""
|
||||||
return self.move(self.y + 1, self.x) if force else \
|
return self.move(self.y + 1, self.x) if force else \
|
||||||
self.check_move(self.y + 1, self.x, True)
|
self.check_move(self.y + 1, self.x, True)
|
||||||
|
|
||||||
def move_left(self, force: bool = False) -> bool:
|
def move_left(self, force: bool = False) -> bool:
|
||||||
"""
|
"""
|
||||||
Moves the entity left one tile, if possible
|
Moves the entity left one tile, if possible.
|
||||||
"""
|
"""
|
||||||
return self.move(self.y, self.x - 1) if force else \
|
return self.move(self.y, self.x - 1) if force else \
|
||||||
self.check_move(self.y, self.x - 1, True)
|
self.check_move(self.y, self.x - 1, True)
|
||||||
|
|
||||||
def move_right(self, force: bool = False) -> bool:
|
def move_right(self, force: bool = False) -> bool:
|
||||||
"""
|
"""
|
||||||
Moves the entity right one tile, if possible
|
Moves the entity right one tile, if possible.
|
||||||
"""
|
"""
|
||||||
return self.move(self.y, self.x + 1) if force else \
|
return self.move(self.y, self.x + 1) if force else \
|
||||||
self.check_move(self.y, self.x + 1, True)
|
self.check_move(self.y, self.x + 1, True)
|
||||||
|
|
||||||
|
def recalculate_paths(self, max_distance: int = 12) -> None:
|
||||||
|
"""
|
||||||
|
Uses Dijkstra algorithm to calculate best paths for other entities to
|
||||||
|
go to this entity. If self.paths is None, does nothing.
|
||||||
|
"""
|
||||||
|
if self.paths is None:
|
||||||
|
return
|
||||||
|
distances = []
|
||||||
|
predecessors = []
|
||||||
|
# four Dijkstras, one for each adjacent tile
|
||||||
|
for dir_y, dir_x in [(1, 0), (-1, 0), (0, 1), (0, -1)]:
|
||||||
|
queue = PriorityQueue()
|
||||||
|
new_y, new_x = self.y + dir_y, self.x + dir_x
|
||||||
|
if not 0 <= new_y < self.map.height or \
|
||||||
|
not 0 <= new_x < self.map.width or \
|
||||||
|
not self.map.tiles[new_y][new_x].can_walk():
|
||||||
|
continue
|
||||||
|
queue.put(((1, 0), (new_y, new_x)))
|
||||||
|
visited = [(self.y, self.x)]
|
||||||
|
distances.append({(self.y, self.x): (0, 0), (new_y, new_x): (1, 0)})
|
||||||
|
predecessors.append({(new_y, new_x): (self.y, self.x)})
|
||||||
|
while not queue.empty():
|
||||||
|
dist, (y, x) = queue.get()
|
||||||
|
if dist[0] >= max_distance or (y, x) in visited:
|
||||||
|
continue
|
||||||
|
visited.append((y, x))
|
||||||
|
for diff_y, diff_x in [(1, 0), (-1, 0), (0, 1), (0, -1)]:
|
||||||
|
new_y, new_x = y + diff_y, x + diff_x
|
||||||
|
if not 0 <= new_y < self.map.height or \
|
||||||
|
not 0 <= new_x < self.map.width or \
|
||||||
|
not self.map.tiles[new_y][new_x].can_walk():
|
||||||
|
continue
|
||||||
|
new_distance = (dist[0] + 1,
|
||||||
|
dist[1] + (not self.map.is_free(y, x)))
|
||||||
|
if not (new_y, new_x) in distances[-1] or \
|
||||||
|
distances[-1][(new_y, new_x)] > new_distance:
|
||||||
|
predecessors[-1][(new_y, new_x)] = (y, x)
|
||||||
|
distances[-1][(new_y, new_x)] = new_distance
|
||||||
|
queue.put((new_distance, (new_y, new_x)))
|
||||||
|
# For each tile that is reached by at least one Dijkstra, sort the
|
||||||
|
# different paths by distance to the player. For the technical bits :
|
||||||
|
# The reduce function is a fold starting on the first element of the
|
||||||
|
# iterable, and we associate the points to their distance, sort
|
||||||
|
# along the distance, then only keep the points.
|
||||||
|
self.paths = {}
|
||||||
|
for y, x in reduce(set.union,
|
||||||
|
[set(p.keys()) for p in predecessors], set()):
|
||||||
|
self.paths[(y, x)] = [p for d, p in sorted(
|
||||||
|
[(distances[i][(y, x)], predecessors[i][(y, x)])
|
||||||
|
for i in range(len(distances)) if (y, x) in predecessors[i]])]
|
||||||
|
|
||||||
def act(self, m: Map) -> None:
|
def act(self, m: Map) -> None:
|
||||||
"""
|
"""
|
||||||
Define the action of the entity that is ran each tick.
|
Defines the action the entity will do at each tick.
|
||||||
By default, does nothing.
|
By default, does nothing.
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def distance_squared(self, other: "Entity") -> int:
|
def distance_squared(self, other: "Entity") -> int:
|
||||||
"""
|
"""
|
||||||
Get the square of the distance to another entity.
|
Gives the square of the distance to another entity.
|
||||||
Useful to check distances since square root takes time.
|
Useful to check distances since taking the square root takes time.
|
||||||
"""
|
"""
|
||||||
return (self.y - other.y) ** 2 + (self.x - other.x) ** 2
|
return (self.y - other.y) ** 2 + (self.x - other.x) ** 2
|
||||||
|
|
||||||
def distance(self, other: "Entity") -> float:
|
def distance(self, other: "Entity") -> float:
|
||||||
"""
|
"""
|
||||||
Get the cartesian distance to another entity.
|
Gives the cartesian distance to another entity.
|
||||||
"""
|
"""
|
||||||
return sqrt(self.distance_squared(other))
|
return sqrt(self.distance_squared(other))
|
||||||
|
|
||||||
|
@ -355,6 +605,13 @@ class Entity:
|
||||||
"""
|
"""
|
||||||
return isinstance(self, FriendlyEntity)
|
return isinstance(self, FriendlyEntity)
|
||||||
|
|
||||||
|
def is_familiar(self) -> bool:
|
||||||
|
"""
|
||||||
|
Is this entity a familiar?
|
||||||
|
"""
|
||||||
|
from squirrelbattle.entities.friendly import Familiar
|
||||||
|
return isinstance(self, Familiar)
|
||||||
|
|
||||||
def is_merchant(self) -> bool:
|
def is_merchant(self) -> bool:
|
||||||
"""
|
"""
|
||||||
Is this entity a merchant?
|
Is this entity a merchant?
|
||||||
|
@ -364,48 +621,71 @@ class Entity:
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def translated_name(self) -> str:
|
def translated_name(self) -> str:
|
||||||
|
"""
|
||||||
|
Translates the name of entities.
|
||||||
|
"""
|
||||||
return _(self.name.replace("_", " "))
|
return _(self.name.replace("_", " "))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_all_entity_classes() -> list:
|
def get_all_entity_classes() -> list:
|
||||||
"""
|
"""
|
||||||
Returns all entities subclasses
|
Returns all entities subclasses.
|
||||||
"""
|
"""
|
||||||
from squirrelbattle.entities.items import BodySnatchPotion, Bomb, Heart
|
from squirrelbattle.entities.items import BodySnatchPotion, Bomb, Heart
|
||||||
from squirrelbattle.entities.monsters import Tiger, Hedgehog, \
|
from squirrelbattle.entities.monsters import Tiger, Hedgehog, \
|
||||||
Rabbit, TeddyBear
|
Rabbit, TeddyBear, GiantSeaEagle
|
||||||
from squirrelbattle.entities.friendly import Merchant, Sunflower
|
from squirrelbattle.entities.friendly import Merchant, Sunflower, \
|
||||||
|
Trumpet
|
||||||
return [BodySnatchPotion, Bomb, Heart, Hedgehog, Rabbit, TeddyBear,
|
return [BodySnatchPotion, Bomb, Heart, Hedgehog, Rabbit, TeddyBear,
|
||||||
Sunflower, Tiger, Merchant]
|
Sunflower, Tiger, Merchant, GiantSeaEagle, Trumpet]
|
||||||
|
|
||||||
|
@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, 2]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_all_entity_classes_in_a_dict() -> dict:
|
def get_all_entity_classes_in_a_dict() -> dict:
|
||||||
"""
|
"""
|
||||||
Returns all entities subclasses in a dictionary
|
Returns all entities subclasses in a dictionary.
|
||||||
"""
|
"""
|
||||||
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, GiantSeaEagle
|
||||||
from squirrelbattle.entities.friendly import Merchant, Sunflower
|
from squirrelbattle.entities.friendly import Merchant, Sunflower, \
|
||||||
|
Trumpet
|
||||||
from squirrelbattle.entities.items import BodySnatchPotion, Bomb, \
|
from squirrelbattle.entities.items import BodySnatchPotion, Bomb, \
|
||||||
Heart, Sword
|
Heart, Monocle, Sword, Shield, Chestplate, Helmet, \
|
||||||
|
RingCritical, RingXP
|
||||||
return {
|
return {
|
||||||
"Tiger": Tiger,
|
|
||||||
"Bomb": Bomb,
|
"Bomb": Bomb,
|
||||||
|
"Chestplate": Chestplate,
|
||||||
"Heart": Heart,
|
"Heart": Heart,
|
||||||
"BodySnatchPotion": BodySnatchPotion,
|
"BodySnatchPotion": BodySnatchPotion,
|
||||||
|
"Eagle": GiantSeaEagle,
|
||||||
"Hedgehog": Hedgehog,
|
"Hedgehog": Hedgehog,
|
||||||
"Rabbit": Rabbit,
|
"Helmet": Helmet,
|
||||||
"TeddyBear": TeddyBear,
|
|
||||||
"Player": Player,
|
"Player": Player,
|
||||||
"Merchant": Merchant,
|
"Merchant": Merchant,
|
||||||
|
"Monocle": Monocle,
|
||||||
"Sunflower": Sunflower,
|
"Sunflower": Sunflower,
|
||||||
"Sword": Sword,
|
"Sword": Sword,
|
||||||
|
"Trumpet": Trumpet,
|
||||||
|
"Shield": Shield,
|
||||||
|
"TeddyBear": TeddyBear,
|
||||||
|
"Tiger": Tiger,
|
||||||
|
"Rabbit": Rabbit,
|
||||||
|
"RingCritical": RingCritical,
|
||||||
|
"RingXP": RingXP,
|
||||||
}
|
}
|
||||||
|
|
||||||
def save_state(self) -> dict:
|
def save_state(self) -> dict:
|
||||||
"""
|
"""
|
||||||
Saves the coordinates of the entity
|
Saves the coordinates of the entity.
|
||||||
"""
|
"""
|
||||||
d = dict()
|
d = dict()
|
||||||
d["x"] = self.x
|
d["x"] = self.x
|
||||||
|
@ -417,7 +697,7 @@ class Entity:
|
||||||
class FightingEntity(Entity):
|
class FightingEntity(Entity):
|
||||||
"""
|
"""
|
||||||
A FightingEntity is an entity that can fight, and thus has a health,
|
A FightingEntity is an entity that can fight, and thus has a health,
|
||||||
level and stats
|
level and stats.
|
||||||
"""
|
"""
|
||||||
maxhealth: int
|
maxhealth: int
|
||||||
health: int
|
health: int
|
||||||
|
@ -427,11 +707,12 @@ class FightingEntity(Entity):
|
||||||
dexterity: int
|
dexterity: int
|
||||||
constitution: int
|
constitution: int
|
||||||
level: int
|
level: int
|
||||||
|
critical: int
|
||||||
|
|
||||||
def __init__(self, maxhealth: int = 0, health: Optional[int] = None,
|
def __init__(self, maxhealth: int = 0, health: Optional[int] = None,
|
||||||
strength: int = 0, intelligence: int = 0, charisma: int = 0,
|
strength: int = 0, intelligence: int = 0, charisma: int = 0,
|
||||||
dexterity: int = 0, constitution: int = 0, level: int = 0,
|
dexterity: int = 0, constitution: int = 0, level: int = 0,
|
||||||
*args, **kwargs) -> None:
|
critical: int = 0, *args, **kwargs) -> None:
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.maxhealth = maxhealth
|
self.maxhealth = maxhealth
|
||||||
self.health = maxhealth if health is None else health
|
self.health = maxhealth if health is None else health
|
||||||
|
@ -441,49 +722,62 @@ class FightingEntity(Entity):
|
||||||
self.dexterity = dexterity
|
self.dexterity = dexterity
|
||||||
self.constitution = constitution
|
self.constitution = constitution
|
||||||
self.level = level
|
self.level = level
|
||||||
|
self.critical = critical
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def dead(self) -> bool:
|
def dead(self) -> bool:
|
||||||
|
"""
|
||||||
|
Is this entity dead ?
|
||||||
|
"""
|
||||||
return self.health <= 0
|
return self.health <= 0
|
||||||
|
|
||||||
def hit(self, opponent: "FightingEntity") -> str:
|
def hit(self, opponent: "FightingEntity") -> str:
|
||||||
"""
|
"""
|
||||||
Deals damage to the opponent, based on the stats
|
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}.")\
|
return _("{name} hits {opponent}.")\
|
||||||
.format(name=_(self.translated_name.capitalize()),
|
.format(name=_(self.translated_name.capitalize()),
|
||||||
opponent=_(opponent.translated_name)) + " " + \
|
opponent=_(opponent.translated_name)) + string + \
|
||||||
opponent.take_damage(self, self.strength)
|
opponent.take_damage(self, damage)
|
||||||
|
|
||||||
def take_damage(self, attacker: "Entity", amount: int) -> str:
|
def take_damage(self, attacker: "Entity", amount: int) -> str:
|
||||||
"""
|
"""
|
||||||
Take damage from the attacker, based on the stats
|
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:
|
if self.health <= 0:
|
||||||
self.die()
|
self.die()
|
||||||
return _("{name} takes {amount} damage.")\
|
return _("{name} takes {damage} damage.")\
|
||||||
.format(name=self.translated_name.capitalize(), amount=str(amount))\
|
.format(name=self.translated_name.capitalize(), damage=str(damage))\
|
||||||
+ (" " + _("{name} dies.")
|
+ (" " + _("{name} dies.")
|
||||||
.format(name=self.translated_name.capitalize())
|
.format(name=self.translated_name.capitalize())
|
||||||
if self.health <= 0 else "")
|
if self.health <= 0 else "")
|
||||||
|
|
||||||
def die(self) -> None:
|
def die(self) -> None:
|
||||||
"""
|
"""
|
||||||
If a fighting entity has no more health, it dies and is removed
|
If a fighting entity has no more health, it dies and is removed.
|
||||||
"""
|
"""
|
||||||
self.map.remove_entity(self)
|
self.map.remove_entity(self)
|
||||||
|
|
||||||
def keys(self) -> list:
|
def keys(self) -> list:
|
||||||
"""
|
"""
|
||||||
Returns a fighting entity's specific attributes
|
Returns a fighting entity's specific attributes.
|
||||||
"""
|
"""
|
||||||
return ["name", "maxhealth", "health", "level", "strength",
|
return ["name", "maxhealth", "health", "level", "strength",
|
||||||
"intelligence", "charisma", "dexterity", "constitution"]
|
"intelligence", "charisma", "dexterity", "constitution"]
|
||||||
|
|
||||||
def save_state(self) -> dict:
|
def save_state(self) -> dict:
|
||||||
"""
|
"""
|
||||||
Saves the state of the entity into a dictionary
|
Saves the state of the entity into a dictionary.
|
||||||
"""
|
"""
|
||||||
d = super().save_state()
|
d = super().save_state()
|
||||||
for name in self.keys():
|
for name in self.keys():
|
||||||
|
@ -493,18 +787,18 @@ class FightingEntity(Entity):
|
||||||
|
|
||||||
class FriendlyEntity(FightingEntity):
|
class FriendlyEntity(FightingEntity):
|
||||||
"""
|
"""
|
||||||
Friendly entities are living entities which do not attack the player
|
Friendly entities are living entities which do not attack the player.
|
||||||
"""
|
"""
|
||||||
dialogue_option: list
|
dialogue_option: list
|
||||||
|
|
||||||
def talk_to(self, player: Any) -> str:
|
def talk_to(self, player: Any) -> str:
|
||||||
a = randint(0, len(self.dialogue_option) - 1)
|
return _("{entity} said: {message}").format(
|
||||||
return "The " + self.translated_name \
|
entity=self.translated_name.capitalize(),
|
||||||
+ " said : " + self.dialogue_option[a]
|
message=choice(self.dialogue_option))
|
||||||
|
|
||||||
def keys(self) -> list:
|
def keys(self) -> list:
|
||||||
"""
|
"""
|
||||||
Returns a friendly entity's specific attributes
|
Returns a friendly entity's specific attributes.
|
||||||
"""
|
"""
|
||||||
return ["maxhealth", "health"]
|
return ["maxhealth", "health"]
|
||||||
|
|
||||||
|
@ -515,17 +809,17 @@ class InventoryHolder(Entity):
|
||||||
|
|
||||||
def translate_inventory(self, inventory: list) -> list:
|
def translate_inventory(self, inventory: list) -> list:
|
||||||
"""
|
"""
|
||||||
Translate the JSON-state of the inventory into a list of the items in
|
Translates the JSON save of the inventory into a list of the items in
|
||||||
the inventory.
|
the inventory.
|
||||||
"""
|
"""
|
||||||
for i in range(len(inventory)):
|
for i in range(len(inventory)):
|
||||||
if isinstance(inventory[i], dict):
|
if isinstance(inventory[i], dict):
|
||||||
inventory[i] = self.dict_to_inventory(inventory[i])
|
inventory[i] = self.dict_to_item(inventory[i])
|
||||||
return inventory
|
return inventory
|
||||||
|
|
||||||
def dict_to_inventory(self, item_dict: dict) -> Entity:
|
def dict_to_item(self, item_dict: dict) -> Entity:
|
||||||
"""
|
"""
|
||||||
Translate a dict object that contains the state of an item
|
Translates a dictionnary that contains the state of an item
|
||||||
into an item object.
|
into an item object.
|
||||||
"""
|
"""
|
||||||
entity_classes = self.get_all_entity_classes_in_a_dict()
|
entity_classes = self.get_all_entity_classes_in_a_dict()
|
||||||
|
@ -535,7 +829,7 @@ class InventoryHolder(Entity):
|
||||||
|
|
||||||
def save_state(self) -> dict:
|
def save_state(self) -> dict:
|
||||||
"""
|
"""
|
||||||
We save the inventory of the merchant formatted as JSON
|
The inventory of the merchant is saved in a JSON format.
|
||||||
"""
|
"""
|
||||||
d = super().save_state()
|
d = super().save_state()
|
||||||
d["hazel"] = self.hazel
|
d["hazel"] = self.hazel
|
||||||
|
@ -544,19 +838,21 @@ class InventoryHolder(Entity):
|
||||||
|
|
||||||
def add_to_inventory(self, obj: Any) -> None:
|
def add_to_inventory(self, obj: Any) -> None:
|
||||||
"""
|
"""
|
||||||
Adds an object to inventory
|
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:
|
def remove_from_inventory(self, obj: Any) -> None:
|
||||||
"""
|
"""
|
||||||
Removes an object from the inventory
|
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:
|
def change_hazel_balance(self, hz: int) -> None:
|
||||||
"""
|
"""
|
||||||
Change the number of hazel the entity has by hz. hz is negative
|
Changes the number of hazel the entity has by hz. hz is negative
|
||||||
when the player loses money and positive when he gains money
|
when the entity loses money and positive when it gains money.
|
||||||
"""
|
"""
|
||||||
self.hazel += hz
|
self.hazel += hz
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
# German translation of Squirrel Battle
|
# SOME DESCRIPTIVE TITLE.
|
||||||
# Copyright (C) YEAR ÿnérant, eichhornchen, nicomarg, charlse
|
# Copyright (C) YEAR ÿnérant, eichhornchen, nicomarg, charlse, ifugao
|
||||||
# This file is distributed under the same license as the squirrelbattle package.
|
# This file is distributed under the same license as the squirrelbattle package.
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
#
|
#
|
||||||
|
#, fuzzy
|
||||||
msgid ""
|
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-11 18:06+0100\n"
|
"POT-Creation-Date: 2021-01-08 15:15+0100\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
|
@ -15,52 +17,116 @@ msgstr ""
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
#: squirrelbattle/display/menudisplay.py:113
|
msgid "ring_of_critical_damage"
|
||||||
msgid "== INVENTORY =="
|
msgstr ""
|
||||||
msgstr "== BESTAND =="
|
|
||||||
|
|
||||||
#: squirrelbattle/display/menudisplay.py:134
|
msgid "ring_of_more_experience"
|
||||||
msgid "== STALL =="
|
msgstr ""
|
||||||
msgstr "== STAND =="
|
|
||||||
|
|
||||||
#: squirrelbattle/display/statsdisplay.py:34
|
#, python-brace-format
|
||||||
|
msgid "{name} takes {amount} damage."
|
||||||
|
msgstr "{name} nimmt {amount} Schadenspunkte."
|
||||||
|
|
||||||
|
#: squirrelbattle/display/creditsdisplay.py:28
|
||||||
|
#: squirrelbattle/display/menudisplay.py:123
|
||||||
|
#: squirrelbattle/display/menudisplay.py:148
|
||||||
|
msgid "Credits"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: squirrelbattle/display/creditsdisplay.py:32
|
||||||
|
msgid "Developers:"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: squirrelbattle/display/creditsdisplay.py:38
|
||||||
|
msgid "Translators:"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: squirrelbattle/display/menudisplay.py:168
|
||||||
|
msgid "INVENTORY"
|
||||||
|
msgstr "BESTAND"
|
||||||
|
|
||||||
|
#: squirrelbattle/display/menudisplay.py:214
|
||||||
|
msgid "STALL"
|
||||||
|
msgstr "STAND"
|
||||||
|
|
||||||
|
#: squirrelbattle/display/statsdisplay.py:44
|
||||||
msgid "Inventory:"
|
msgid "Inventory:"
|
||||||
msgstr "Bestand:"
|
msgstr "Bestand:"
|
||||||
|
|
||||||
#: squirrelbattle/display/statsdisplay.py:53
|
#: squirrelbattle/display/statsdisplay.py:61
|
||||||
|
msgid "Equipped main:"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: squirrelbattle/display/statsdisplay.py:65
|
||||||
|
msgid "Equipped secondary:"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: squirrelbattle/display/statsdisplay.py:70
|
||||||
|
msgid "Equipped chestplate:"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: squirrelbattle/display/statsdisplay.py:74
|
||||||
|
msgid "Equipped helmet:"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: squirrelbattle/display/statsdisplay.py:81
|
||||||
msgid "YOU ARE DEAD"
|
msgid "YOU ARE DEAD"
|
||||||
msgstr "SIE WURDEN GESTORBEN"
|
msgstr "SIE WURDEN GESTORBEN"
|
||||||
|
|
||||||
|
#: squirrelbattle/display/statsdisplay.py:85
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Use {key} to use the ladder"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: squirrelbattle/display/statsdisplay.py:94
|
||||||
|
msgid "Move to the friendly entity to talk to it"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: squirrelbattle/display/statsdisplay.py:96
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Use {key} then move to talk to the entity"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#. TODO
|
#. TODO
|
||||||
#: squirrelbattle/entities/friendly.py:33
|
#: squirrelbattle/entities/friendly.py:33
|
||||||
msgid "I don't sell any squirrel"
|
msgid "I don't sell any squirrel"
|
||||||
msgstr "Ich verkaufe keinen Eichhörnchen."
|
msgstr "Ich verkaufe keinen Eichhörnchen."
|
||||||
|
|
||||||
#: squirrelbattle/entities/friendly.py:46
|
#: squirrelbattle/entities/friendly.py:55
|
||||||
msgid "Flower power!!"
|
msgid "Flower power!!"
|
||||||
msgstr "Blumenmacht!!"
|
msgstr "Blumenmacht!!"
|
||||||
|
|
||||||
#: squirrelbattle/entities/friendly.py:46
|
#: squirrelbattle/entities/friendly.py:55
|
||||||
msgid "The sun is warm today"
|
msgid "The sun is warm today"
|
||||||
msgstr "Die Sonne ist warm heute"
|
msgstr "Die Sonne ist warm heute"
|
||||||
|
|
||||||
#. The bomb is exploding.
|
#. The bomb is exploding.
|
||||||
#. Each entity that is close to the bomb takes damages.
|
#. Each entity that is close to the bomb takes damages.
|
||||||
#. The player earn XP if the entity was killed.
|
#. The player earn XP if the entity was killed.
|
||||||
#: squirrelbattle/entities/items.py:151
|
#: squirrelbattle/entities/items.py:178
|
||||||
msgid "Bomb is exploding."
|
msgid "Bomb is exploding."
|
||||||
msgstr "Die Bombe explodiert."
|
msgstr "Die Bombe explodiert."
|
||||||
|
|
||||||
#: squirrelbattle/entities/items.py:224
|
#: squirrelbattle/entities/items.py:365
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "{player} exchanged its body with {entity}."
|
msgid "{player} exchanged its body with {entity}."
|
||||||
msgstr "{player} täuscht seinem Körper mit {entity} aus."
|
msgstr "{player} täuscht seinem Körper mit {entity} aus."
|
||||||
|
|
||||||
#: squirrelbattle/game.py:199 squirrelbattle/tests/game_test.py:537
|
#: squirrelbattle/game.py:200
|
||||||
msgid "You do not have enough money"
|
#, python-brace-format
|
||||||
msgstr ""
|
msgid "The player climbs down to the floor {floor}."
|
||||||
|
msgstr "Der Spieler klettert auf dem Stock {floor} hinunter."
|
||||||
|
|
||||||
#: squirrelbattle/game.py:243
|
#: squirrelbattle/game.py:213
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "The player climbs up the floor {floor}."
|
||||||
|
msgstr "Der Spieler klettert auf dem Stock {floor} hinoben."
|
||||||
|
|
||||||
|
#: squirrelbattle/game.py:304 squirrelbattle/tests/game_test.py:603
|
||||||
|
msgid "The buyer does not have enough money"
|
||||||
|
msgstr "Der Kaufer hat nicht genug Geld"
|
||||||
|
|
||||||
|
#: squirrelbattle/game.py:349
|
||||||
msgid ""
|
msgid ""
|
||||||
"Some keys are missing in your save file.\n"
|
"Some keys are missing in your save file.\n"
|
||||||
"Your save seems to be corrupt. It got deleted."
|
"Your save seems to be corrupt. It got deleted."
|
||||||
|
@ -68,7 +134,7 @@ msgstr ""
|
||||||
"In Ihrer Speicherdatei fehlen einige Schlüssel.\n"
|
"In Ihrer Speicherdatei fehlen einige Schlüssel.\n"
|
||||||
"Ihre Speicherung scheint korrupt zu sein. Es wird gelöscht."
|
"Ihre Speicherung scheint korrupt zu sein. Es wird gelöscht."
|
||||||
|
|
||||||
#: squirrelbattle/game.py:251
|
#: squirrelbattle/game.py:357
|
||||||
msgid ""
|
msgid ""
|
||||||
"No player was found on this map!\n"
|
"No player was found on this map!\n"
|
||||||
"Maybe you died?"
|
"Maybe you died?"
|
||||||
|
@ -76,7 +142,7 @@ msgstr ""
|
||||||
"Auf dieser Karte wurde kein Spieler gefunden!\n"
|
"Auf dieser Karte wurde kein Spieler gefunden!\n"
|
||||||
"Vielleicht sind Sie gestorben?"
|
"Vielleicht sind Sie gestorben?"
|
||||||
|
|
||||||
#: squirrelbattle/game.py:271
|
#: squirrelbattle/game.py:379
|
||||||
msgid ""
|
msgid ""
|
||||||
"The JSON file is not correct.\n"
|
"The JSON file is not correct.\n"
|
||||||
"Your save seems corrupted. It got deleted."
|
"Your save seems corrupted. It got deleted."
|
||||||
|
@ -84,27 +150,36 @@ msgstr ""
|
||||||
"Die JSON-Datei ist nicht korrekt.\n"
|
"Die JSON-Datei ist nicht korrekt.\n"
|
||||||
"Ihre Speicherung scheint korrumpiert. Sie wurde gelöscht."
|
"Ihre Speicherung scheint korrumpiert. Sie wurde gelöscht."
|
||||||
|
|
||||||
#: squirrelbattle/interfaces.py:429
|
#: squirrelbattle/interfaces.py:718
|
||||||
|
msgid "It's a critical hit!"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: squirrelbattle/interfaces.py:719
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "{name} hits {opponent}."
|
msgid "{name} hits {opponent}."
|
||||||
msgstr "{name} schlägt {opponent}."
|
msgstr "{name} schlägt {opponent}."
|
||||||
|
|
||||||
#: squirrelbattle/interfaces.py:441
|
#: squirrelbattle/interfaces.py:733
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "{name} takes {amount} damage."
|
msgid "{name} takes {damage} damage."
|
||||||
msgstr "{name} nimmt {amount} Schadenspunkte."
|
msgstr ""
|
||||||
|
|
||||||
#: squirrelbattle/interfaces.py:443
|
#: squirrelbattle/interfaces.py:735
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "{name} dies."
|
msgid "{name} dies."
|
||||||
msgstr "{name} stirbt."
|
msgstr "{name} stirbt."
|
||||||
|
|
||||||
|
#: squirrelbattle/interfaces.py:769
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "{entity} said: {message}"
|
||||||
|
msgstr "{entity} hat gesagt: {message}"
|
||||||
|
|
||||||
#: squirrelbattle/menus.py:73
|
#: squirrelbattle/menus.py:73
|
||||||
msgid "Back"
|
msgid "Back"
|
||||||
msgstr "Zurück"
|
msgstr "Zurück"
|
||||||
|
|
||||||
#: squirrelbattle/tests/game_test.py:314 squirrelbattle/tests/game_test.py:317
|
#: squirrelbattle/tests/game_test.py:368 squirrelbattle/tests/game_test.py:371
|
||||||
#: squirrelbattle/tests/game_test.py:320
|
#: squirrelbattle/tests/game_test.py:374 squirrelbattle/tests/game_test.py:377
|
||||||
#: 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,45 +261,93 @@ msgid "Key used to talk to a friendly entity"
|
||||||
msgstr "Taste um mit einer friedlicher Entität zu sprechen"
|
msgstr "Taste um mit einer friedlicher Entität zu sprechen"
|
||||||
|
|
||||||
#: squirrelbattle/tests/translations_test.py:55
|
#: squirrelbattle/tests/translations_test.py:55
|
||||||
|
msgid "Key used to wait"
|
||||||
|
msgstr "Wartentaste"
|
||||||
|
|
||||||
|
#: squirrelbattle/tests/translations_test.py:56
|
||||||
|
msgid "Key used to use ladders"
|
||||||
|
msgstr "Leitertaste"
|
||||||
|
|
||||||
|
#: squirrelbattle/tests/translations_test.py:58
|
||||||
msgid "Texture pack"
|
msgid "Texture pack"
|
||||||
msgstr "Textur-Packung"
|
msgstr "Textur-Packung"
|
||||||
|
|
||||||
#: squirrelbattle/tests/translations_test.py:56
|
#: squirrelbattle/tests/translations_test.py:59
|
||||||
msgid "Language"
|
msgid "Language"
|
||||||
msgstr "Sprache"
|
msgstr "Sprache"
|
||||||
|
|
||||||
#: squirrelbattle/tests/translations_test.py:59
|
#: squirrelbattle/tests/translations_test.py:62
|
||||||
msgid "player"
|
msgid "player"
|
||||||
msgstr "Spieler"
|
msgstr "Spieler"
|
||||||
|
|
||||||
#: squirrelbattle/tests/translations_test.py:61
|
#: squirrelbattle/tests/translations_test.py:64
|
||||||
msgid "tiger"
|
|
||||||
msgstr "Tiger"
|
|
||||||
|
|
||||||
#: squirrelbattle/tests/translations_test.py:62
|
|
||||||
msgid "hedgehog"
|
msgid "hedgehog"
|
||||||
msgstr "Igel"
|
msgstr "Igel"
|
||||||
|
|
||||||
#: squirrelbattle/tests/translations_test.py:63
|
#: squirrelbattle/tests/translations_test.py:65
|
||||||
|
msgid "merchant"
|
||||||
|
msgstr "Kaufmann"
|
||||||
|
|
||||||
|
#: squirrelbattle/tests/translations_test.py:66
|
||||||
msgid "rabbit"
|
msgid "rabbit"
|
||||||
msgstr "Kanninchen"
|
msgstr "Kanninchen"
|
||||||
|
|
||||||
#: squirrelbattle/tests/translations_test.py:64
|
#: squirrelbattle/tests/translations_test.py:67
|
||||||
|
msgid "sunflower"
|
||||||
|
msgstr "Sonnenblume"
|
||||||
|
|
||||||
|
#: squirrelbattle/tests/translations_test.py:68
|
||||||
msgid "teddy bear"
|
msgid "teddy bear"
|
||||||
msgstr "Teddybär"
|
msgstr "Teddybär"
|
||||||
|
|
||||||
#: squirrelbattle/tests/translations_test.py:66
|
#: squirrelbattle/tests/translations_test.py:69
|
||||||
|
msgid "tiger"
|
||||||
|
msgstr "Tiger"
|
||||||
|
|
||||||
|
#: squirrelbattle/tests/translations_test.py:70
|
||||||
|
msgid "eagle"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: squirrelbattle/tests/translations_test.py:72
|
||||||
msgid "body snatch potion"
|
msgid "body snatch potion"
|
||||||
msgstr "Leichenfleddererzaubertrank"
|
msgstr "Leichenfleddererzaubertrank"
|
||||||
|
|
||||||
#: squirrelbattle/tests/translations_test.py:67
|
#: squirrelbattle/tests/translations_test.py:73
|
||||||
msgid "bomb"
|
msgid "bomb"
|
||||||
msgstr "Bombe"
|
msgstr "Bombe"
|
||||||
|
|
||||||
#: squirrelbattle/tests/translations_test.py:68
|
#: squirrelbattle/tests/translations_test.py:74
|
||||||
|
msgid "explosion"
|
||||||
|
msgstr "Explosion"
|
||||||
|
|
||||||
|
#: squirrelbattle/tests/translations_test.py:75
|
||||||
msgid "heart"
|
msgid "heart"
|
||||||
msgstr "Herz"
|
msgstr "Herz"
|
||||||
|
|
||||||
#: squirrelbattle/tests/translations_test.py:69
|
#: squirrelbattle/tests/translations_test.py:76
|
||||||
msgid "sword"
|
msgid "sword"
|
||||||
msgstr "schwert"
|
msgstr "schwert"
|
||||||
|
|
||||||
|
#: squirrelbattle/tests/translations_test.py:77
|
||||||
|
msgid "helmet"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: squirrelbattle/tests/translations_test.py:78
|
||||||
|
msgid "chestplate"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: squirrelbattle/tests/translations_test.py:79
|
||||||
|
msgid "shield"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: squirrelbattle/tests/translations_test.py:80
|
||||||
|
msgid "ring of critical damage"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: squirrelbattle/tests/translations_test.py:82
|
||||||
|
msgid "ring of more experience"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: squirrelbattle/tests/translations_test.py:84
|
||||||
|
msgid "monocle"
|
||||||
|
msgstr ""
|
||||||
|
|
|
@ -1,49 +1,131 @@
|
||||||
# Spanish translation of Squirrel Battle
|
# SOME DESCRIPTIVE TITLE.
|
||||||
# Copyright (C) YEAR ÿnérant, eichhornchen, nicomarg, charlse
|
# Copyright (C) YEAR ÿnérant, eichhornchen, nicomarg, charlse, ifugao
|
||||||
# This file is distributed under the same license as the squirrelbattle package.
|
# This file is distributed under the same license as the squirrelbattle package.
|
||||||
# Translation by ifugaao
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
#
|
#
|
||||||
|
#, fuzzy
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: squirrelbattle 3.14.1\n"
|
"Project-Id-Version: squirrelbattle 3.14.1\n"
|
||||||
"Report-Msgid-Bugs-To: squirrel-battle@crans.org\n"
|
"Report-Msgid-Bugs-To: squirrel-battle@crans.org\n"
|
||||||
"POT-Creation-Date: 2020-12-05 14:46+0100\n"
|
"POT-Creation-Date: 2021-01-08 15:15+0100\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: ifugao\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
"Language: \n"
|
"Language: \n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
# Suggested in Weblate: == INVENTORIO ==
|
msgid "ring_of_critical_damage"
|
||||||
#: squirrelbattle/display/menudisplay.py:105
|
msgstr ""
|
||||||
msgid "== INVENTORY =="
|
|
||||||
msgstr "== INVENTORIO =="
|
|
||||||
|
|
||||||
# Suggested in Weblate: Inventorio :
|
msgid "ring_of_more_experience"
|
||||||
#: squirrelbattle/display/statsdisplay.py:34
|
msgstr ""
|
||||||
|
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "{name} takes {amount} damage."
|
||||||
|
msgstr "{name} recibe {amount} daño."
|
||||||
|
|
||||||
|
#: squirrelbattle/display/creditsdisplay.py:28
|
||||||
|
#: squirrelbattle/display/menudisplay.py:123
|
||||||
|
#: squirrelbattle/display/menudisplay.py:148
|
||||||
|
msgid "Credits"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: squirrelbattle/display/creditsdisplay.py:32
|
||||||
|
msgid "Developers:"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: squirrelbattle/display/creditsdisplay.py:38
|
||||||
|
msgid "Translators:"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: squirrelbattle/display/menudisplay.py:168
|
||||||
|
msgid "INVENTORY"
|
||||||
|
msgstr "INVENTORIO"
|
||||||
|
|
||||||
|
#: squirrelbattle/display/menudisplay.py:214
|
||||||
|
msgid "STALL"
|
||||||
|
msgstr "PUESTO"
|
||||||
|
|
||||||
|
#: squirrelbattle/display/statsdisplay.py:44
|
||||||
msgid "Inventory:"
|
msgid "Inventory:"
|
||||||
msgstr "Inventorio :"
|
msgstr "Inventorio :"
|
||||||
|
|
||||||
# Suggested in Weblate: ERES MUERTO
|
#: squirrelbattle/display/statsdisplay.py:61
|
||||||
#: squirrelbattle/display/statsdisplay.py:50
|
msgid "Equipped main:"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: squirrelbattle/display/statsdisplay.py:65
|
||||||
|
msgid "Equipped secondary:"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: squirrelbattle/display/statsdisplay.py:70
|
||||||
|
msgid "Equipped chestplate:"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: squirrelbattle/display/statsdisplay.py:74
|
||||||
|
msgid "Equipped helmet:"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: squirrelbattle/display/statsdisplay.py:81
|
||||||
msgid "YOU ARE DEAD"
|
msgid "YOU ARE DEAD"
|
||||||
msgstr "ERES MUERTO"
|
msgstr "ERES MUERTO"
|
||||||
|
|
||||||
|
#: squirrelbattle/display/statsdisplay.py:85
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Use {key} to use the ladder"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: squirrelbattle/display/statsdisplay.py:94
|
||||||
|
msgid "Move to the friendly entity to talk to it"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: squirrelbattle/display/statsdisplay.py:96
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Use {key} then move to talk to the entity"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: squirrelbattle/entities/friendly.py:33
|
||||||
|
msgid "I don't sell any squirrel"
|
||||||
|
msgstr "No vendo ninguna ardilla"
|
||||||
|
|
||||||
|
#: squirrelbattle/entities/friendly.py:55
|
||||||
|
msgid "Flower power!!"
|
||||||
|
msgstr "Poder de las flores!!"
|
||||||
|
|
||||||
|
#: squirrelbattle/entities/friendly.py:55
|
||||||
|
msgid "The sun is warm today"
|
||||||
|
msgstr "El sol está caliente hoy"
|
||||||
|
|
||||||
#. The bomb is exploding.
|
#. The bomb is exploding.
|
||||||
#. Each entity that is close to the bomb takes damages.
|
#. Each entity that is close to the bomb takes damages.
|
||||||
#. The player earn XP if the entity was killed.
|
#. The player earn XP if the entity was killed.
|
||||||
#: squirrelbattle/entities/items.py:128
|
#: squirrelbattle/entities/items.py:178
|
||||||
msgid "Bomb is exploding."
|
msgid "Bomb is exploding."
|
||||||
msgstr "La bomba está explotando."
|
msgstr "La bomba está explotando."
|
||||||
|
|
||||||
#: squirrelbattle/entities/items.py:172
|
#: squirrelbattle/entities/items.py:365
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "{player} exchanged its body with {entity}."
|
msgid "{player} exchanged its body with {entity}."
|
||||||
msgstr "{player} intercambió su cuerpo con {entity}."
|
msgstr "{player} intercambió su cuerpo con {entity}."
|
||||||
|
|
||||||
#: squirrelbattle/game.py:177
|
#: squirrelbattle/game.py:200
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "The player climbs down to the floor {floor}."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: squirrelbattle/game.py:213
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "The player climbs up the floor {floor}."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: squirrelbattle/game.py:304 squirrelbattle/tests/game_test.py:603
|
||||||
|
msgid "The buyer does not have enough money"
|
||||||
|
msgstr "El comprador no tiene suficiente dinero"
|
||||||
|
|
||||||
|
#: squirrelbattle/game.py:349
|
||||||
msgid ""
|
msgid ""
|
||||||
"Some keys are missing in your save file.\n"
|
"Some keys are missing in your save file.\n"
|
||||||
"Your save seems to be corrupt. It got deleted."
|
"Your save seems to be corrupt. It got deleted."
|
||||||
|
@ -51,7 +133,7 @@ msgstr ""
|
||||||
"Algunas claves faltan en su archivo de guarda.\n"
|
"Algunas claves faltan en su archivo de guarda.\n"
|
||||||
"Su guarda parece a ser corruptido. Fue eliminado."
|
"Su guarda parece a ser corruptido. Fue eliminado."
|
||||||
|
|
||||||
#: squirrelbattle/game.py:185
|
#: squirrelbattle/game.py:357
|
||||||
msgid ""
|
msgid ""
|
||||||
"No player was found on this map!\n"
|
"No player was found on this map!\n"
|
||||||
"Maybe you died?"
|
"Maybe you died?"
|
||||||
|
@ -59,7 +141,7 @@ msgstr ""
|
||||||
"No jugador encontrado sobre la carta !\n"
|
"No jugador encontrado sobre la carta !\n"
|
||||||
"¿ Quizas murió ?"
|
"¿ Quizas murió ?"
|
||||||
|
|
||||||
#: squirrelbattle/game.py:205
|
#: squirrelbattle/game.py:379
|
||||||
msgid ""
|
msgid ""
|
||||||
"The JSON file is not correct.\n"
|
"The JSON file is not correct.\n"
|
||||||
"Your save seems corrupted. It got deleted."
|
"Your save seems corrupted. It got deleted."
|
||||||
|
@ -67,28 +149,36 @@ msgstr ""
|
||||||
"El JSON archivo no es correcto.\n"
|
"El JSON archivo no es correcto.\n"
|
||||||
"Su guarda parece corrupta. Fue eliminada."
|
"Su guarda parece corrupta. Fue eliminada."
|
||||||
|
|
||||||
#: squirrelbattle/interfaces.py:400
|
#: squirrelbattle/interfaces.py:718
|
||||||
|
msgid "It's a critical hit!"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: squirrelbattle/interfaces.py:719
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "{name} hits {opponent}."
|
msgid "{name} hits {opponent}."
|
||||||
msgstr "{name} golpea a {opponent}."
|
msgstr "{name} golpea a {opponent}."
|
||||||
|
|
||||||
#: squirrelbattle/interfaces.py:412
|
#: squirrelbattle/interfaces.py:733
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "{name} takes {amount} damage."
|
msgid "{name} takes {damage} damage."
|
||||||
msgstr "{name} recibe {amount} daño."
|
msgstr ""
|
||||||
|
|
||||||
#: squirrelbattle/interfaces.py:414
|
#: squirrelbattle/interfaces.py:735
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "{name} dies."
|
msgid "{name} dies."
|
||||||
msgstr "{name} se muere."
|
msgstr "{name} se muere."
|
||||||
|
|
||||||
#: squirrelbattle/menus.py:72
|
#: squirrelbattle/interfaces.py:769
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "{entity} said: {message}"
|
||||||
|
msgstr "{entity} dijo : {message}"
|
||||||
|
|
||||||
|
#: squirrelbattle/menus.py:73
|
||||||
msgid "Back"
|
msgid "Back"
|
||||||
msgstr "Volver"
|
msgstr "Volver"
|
||||||
|
|
||||||
#: squirrelbattle/tests/game_test.py:300,
|
#: squirrelbattle/tests/game_test.py:368 squirrelbattle/tests/game_test.py:371
|
||||||
#: squirrelbattle/tests/game_test.py:303,
|
#: squirrelbattle/tests/game_test.py:374 squirrelbattle/tests/game_test.py:377
|
||||||
#: 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 "Nuevo partido"
|
msgstr "Nuevo partido"
|
||||||
|
@ -166,41 +256,97 @@ msgid "Key used to drop an item in the inventory"
|
||||||
msgstr "Tecla para dejar un objeto del inventorio"
|
msgstr "Tecla para dejar un objeto del inventorio"
|
||||||
|
|
||||||
#: squirrelbattle/tests/translations_test.py:53
|
#: squirrelbattle/tests/translations_test.py:53
|
||||||
|
msgid "Key used to talk to a friendly entity"
|
||||||
|
msgstr "Tecla para hablar con una entidad amiga"
|
||||||
|
|
||||||
|
#: squirrelbattle/tests/translations_test.py:55
|
||||||
|
msgid "Key used to wait"
|
||||||
|
msgstr "Tecla para espera"
|
||||||
|
|
||||||
|
#: squirrelbattle/tests/translations_test.py:56
|
||||||
|
msgid "Key used to use ladders"
|
||||||
|
msgstr "Tecla para el uso de las escaleras"
|
||||||
|
|
||||||
|
#: squirrelbattle/tests/translations_test.py:58
|
||||||
msgid "Texture pack"
|
msgid "Texture pack"
|
||||||
msgstr "Paquete de texturas"
|
msgstr "Paquete de texturas"
|
||||||
|
|
||||||
#: squirrelbattle/tests/translations_test.py:54
|
#: squirrelbattle/tests/translations_test.py:59
|
||||||
msgid "Language"
|
msgid "Language"
|
||||||
msgstr "Languaje"
|
msgstr "Languaje"
|
||||||
|
|
||||||
#: squirrelbattle/tests/translations_test.py:57
|
#: squirrelbattle/tests/translations_test.py:62
|
||||||
msgid "player"
|
msgid "player"
|
||||||
msgstr "jugador"
|
msgstr "jugador"
|
||||||
|
|
||||||
#: squirrelbattle/tests/translations_test.py:59
|
#: squirrelbattle/tests/translations_test.py:64
|
||||||
msgid "tiger"
|
|
||||||
msgstr "tigre"
|
|
||||||
|
|
||||||
#: squirrelbattle/tests/translations_test.py:60
|
|
||||||
msgid "hedgehog"
|
msgid "hedgehog"
|
||||||
msgstr "erizo"
|
msgstr "erizo"
|
||||||
|
|
||||||
#: squirrelbattle/tests/translations_test.py:61
|
#: squirrelbattle/tests/translations_test.py:65
|
||||||
|
msgid "merchant"
|
||||||
|
msgstr "comerciante"
|
||||||
|
|
||||||
|
#: squirrelbattle/tests/translations_test.py:66
|
||||||
msgid "rabbit"
|
msgid "rabbit"
|
||||||
msgstr "conejo"
|
msgstr "conejo"
|
||||||
|
|
||||||
#: squirrelbattle/tests/translations_test.py:62
|
#: squirrelbattle/tests/translations_test.py:67
|
||||||
|
msgid "sunflower"
|
||||||
|
msgstr "girasol"
|
||||||
|
|
||||||
|
#: squirrelbattle/tests/translations_test.py:68
|
||||||
msgid "teddy bear"
|
msgid "teddy bear"
|
||||||
msgstr "osito de peluche"
|
msgstr "osito de peluche"
|
||||||
|
|
||||||
#: squirrelbattle/tests/translations_test.py:64
|
#: squirrelbattle/tests/translations_test.py:69
|
||||||
|
msgid "tiger"
|
||||||
|
msgstr "tigre"
|
||||||
|
|
||||||
|
#: squirrelbattle/tests/translations_test.py:70
|
||||||
|
msgid "eagle"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: squirrelbattle/tests/translations_test.py:72
|
||||||
msgid "body snatch potion"
|
msgid "body snatch potion"
|
||||||
msgstr "poción de intercambio"
|
msgstr "poción de intercambio"
|
||||||
|
|
||||||
#: squirrelbattle/tests/translations_test.py:65
|
#: squirrelbattle/tests/translations_test.py:73
|
||||||
msgid "bomb"
|
msgid "bomb"
|
||||||
msgstr "bomba"
|
msgstr "bomba"
|
||||||
|
|
||||||
#: squirrelbattle/tests/translations_test.py:66
|
#: squirrelbattle/tests/translations_test.py:74
|
||||||
|
msgid "explosion"
|
||||||
|
msgstr "explosión"
|
||||||
|
|
||||||
|
#: squirrelbattle/tests/translations_test.py:75
|
||||||
msgid "heart"
|
msgid "heart"
|
||||||
msgstr "corazón"
|
msgstr "corazón"
|
||||||
|
|
||||||
|
#: squirrelbattle/tests/translations_test.py:76
|
||||||
|
msgid "sword"
|
||||||
|
msgstr "espada"
|
||||||
|
|
||||||
|
#: squirrelbattle/tests/translations_test.py:77
|
||||||
|
msgid "helmet"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: squirrelbattle/tests/translations_test.py:78
|
||||||
|
msgid "chestplate"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: squirrelbattle/tests/translations_test.py:79
|
||||||
|
msgid "shield"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: squirrelbattle/tests/translations_test.py:80
|
||||||
|
msgid "ring of critical damage"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: squirrelbattle/tests/translations_test.py:82
|
||||||
|
msgid "ring of more experience"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: squirrelbattle/tests/translations_test.py:84
|
||||||
|
msgid "monocle"
|
||||||
|
msgstr ""
|
||||||
|
|
|
@ -1,67 +1,126 @@
|
||||||
# French translation of Squirrel Battle
|
# SOME DESCRIPTIVE TITLE.
|
||||||
# Copyright (C) YEAR ÿnérant, eichhornchen, nicomarg, charlse
|
# Copyright (C) YEAR ÿnérant, eichhornchen, nicomarg, charlse, ifugao
|
||||||
# This file is distributed under the same license as the squirrelbattle package.
|
# This file is distributed under the same license as the squirrelbattle package.
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
#
|
#
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
msgid ""
|
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-11 18:06+0100\n"
|
"POT-Creation-Date: 2021-01-08 15:15+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"
|
||||||
"Language: fr\n"
|
"Language: \n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
#: squirrelbattle/display/menudisplay.py:113
|
#, python-brace-format
|
||||||
msgid "== INVENTORY =="
|
msgid "{name} takes {amount} damage."
|
||||||
msgstr "== INVENTAIRE =="
|
msgstr "{name} prend {amount} points de dégât."
|
||||||
|
|
||||||
#: squirrelbattle/display/menudisplay.py:134
|
#: squirrelbattle/display/creditsdisplay.py:28
|
||||||
msgid "== STALL =="
|
#: squirrelbattle/display/menudisplay.py:123
|
||||||
msgstr "== STAND =="
|
#: squirrelbattle/display/menudisplay.py:148
|
||||||
|
msgid "Credits"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
#: squirrelbattle/display/statsdisplay.py:34
|
#: squirrelbattle/display/creditsdisplay.py:32
|
||||||
|
msgid "Developers:"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: squirrelbattle/display/creditsdisplay.py:38
|
||||||
|
msgid "Translators:"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: squirrelbattle/display/menudisplay.py:168
|
||||||
|
msgid "INVENTORY"
|
||||||
|
msgstr "INVENTAIRE"
|
||||||
|
|
||||||
|
#: squirrelbattle/display/menudisplay.py:214
|
||||||
|
msgid "STALL"
|
||||||
|
msgstr "STAND"
|
||||||
|
|
||||||
|
#: squirrelbattle/display/statsdisplay.py:44
|
||||||
msgid "Inventory:"
|
msgid "Inventory:"
|
||||||
msgstr "Inventaire :"
|
msgstr "Inventaire :"
|
||||||
|
|
||||||
#: squirrelbattle/display/statsdisplay.py:53
|
#: squirrelbattle/display/statsdisplay.py:61
|
||||||
|
msgid "Equipped main:"
|
||||||
|
msgstr "Équipement principal :"
|
||||||
|
|
||||||
|
#: squirrelbattle/display/statsdisplay.py:65
|
||||||
|
msgid "Equipped secondary:"
|
||||||
|
msgstr "Équipement secondaire :"
|
||||||
|
|
||||||
|
#: squirrelbattle/display/statsdisplay.py:70
|
||||||
|
msgid "Equipped chestplate:"
|
||||||
|
msgstr "Plastron équipé :"
|
||||||
|
|
||||||
|
#: squirrelbattle/display/statsdisplay.py:74
|
||||||
|
msgid "Equipped helmet:"
|
||||||
|
msgstr "Casque équipé :"
|
||||||
|
|
||||||
|
#: squirrelbattle/display/statsdisplay.py:81
|
||||||
msgid "YOU ARE DEAD"
|
msgid "YOU ARE DEAD"
|
||||||
msgstr "VOUS ÊTES MORT"
|
msgstr "VOUS ÊTES MORT"
|
||||||
|
|
||||||
|
#: squirrelbattle/display/statsdisplay.py:85
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Use {key} to use the ladder"
|
||||||
|
msgstr "Appuyez sur {key} pour utiliser l'échelle"
|
||||||
|
|
||||||
|
#: squirrelbattle/display/statsdisplay.py:94
|
||||||
|
msgid "Move to the friendly entity to talk to it"
|
||||||
|
msgstr "Avancez vers l'entité pour lui parler"
|
||||||
|
|
||||||
|
#: squirrelbattle/display/statsdisplay.py:96
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "Use {key} then move to talk to the entity"
|
||||||
|
msgstr "Appuyez sur {key} puis déplacez-vous pour parler"
|
||||||
|
|
||||||
#. TODO
|
#. TODO
|
||||||
#: squirrelbattle/entities/friendly.py:33
|
#: squirrelbattle/entities/friendly.py:33
|
||||||
msgid "I don't sell any squirrel"
|
msgid "I don't sell any squirrel"
|
||||||
msgstr "Je ne vends pas d'écureuil"
|
msgstr "Je ne vends pas d'écureuil"
|
||||||
|
|
||||||
#: squirrelbattle/entities/friendly.py:46
|
#: squirrelbattle/entities/friendly.py:55
|
||||||
msgid "Flower power!!"
|
msgid "Flower power!!"
|
||||||
msgstr "Pouvoir des fleurs !"
|
msgstr "Pouvoir des fleurs !!"
|
||||||
|
|
||||||
#: squirrelbattle/entities/friendly.py:46
|
#: squirrelbattle/entities/friendly.py:55
|
||||||
msgid "The sun is warm today"
|
msgid "The sun is warm today"
|
||||||
msgstr "Le soleil est chaud aujourd'hui"
|
msgstr "Le soleil est chaud aujourd'hui"
|
||||||
|
|
||||||
#. The bomb is exploding.
|
#. The bomb is exploding.
|
||||||
#. Each entity that is close to the bomb takes damages.
|
#. Each entity that is close to the bomb takes damages.
|
||||||
#. The player earn XP if the entity was killed.
|
#. The player earn XP if the entity was killed.
|
||||||
#: squirrelbattle/entities/items.py:151
|
#: squirrelbattle/entities/items.py:178
|
||||||
msgid "Bomb is exploding."
|
msgid "Bomb is exploding."
|
||||||
msgstr "La bombe explose."
|
msgstr "La bombe explose."
|
||||||
|
|
||||||
#: squirrelbattle/entities/items.py:224
|
#: squirrelbattle/entities/items.py:365
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "{player} exchanged its body with {entity}."
|
msgid "{player} exchanged its body with {entity}."
|
||||||
msgstr "{player} a échangé son corps avec {entity}."
|
msgstr "{player} a échangé son corps avec {entity}."
|
||||||
|
|
||||||
#: squirrelbattle/game.py:199 squirrelbattle/tests/game_test.py:537
|
#: squirrelbattle/game.py:200
|
||||||
msgid "You do not have enough money"
|
#, python-brace-format
|
||||||
msgstr ""
|
msgid "The player climbs down to the floor {floor}."
|
||||||
|
msgstr "Le joueur descend à l'étage {floor}."
|
||||||
|
|
||||||
#: squirrelbattle/game.py:243
|
#: squirrelbattle/game.py:213
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "The player climbs up the floor {floor}."
|
||||||
|
msgstr "Le joueur monte à l'étage {floor}."
|
||||||
|
|
||||||
|
#: squirrelbattle/game.py:304 squirrelbattle/tests/game_test.py:603
|
||||||
|
msgid "The buyer does not have enough money"
|
||||||
|
msgstr "L'acheteur n'a pas assez d'argent"
|
||||||
|
|
||||||
|
#: squirrelbattle/game.py:349
|
||||||
msgid ""
|
msgid ""
|
||||||
"Some keys are missing in your save file.\n"
|
"Some keys are missing in your save file.\n"
|
||||||
"Your save seems to be corrupt. It got deleted."
|
"Your save seems to be corrupt. It got deleted."
|
||||||
|
@ -69,7 +128,7 @@ msgstr ""
|
||||||
"Certaines clés de votre ficher de sauvegarde sont manquantes.\n"
|
"Certaines clés de votre ficher de sauvegarde sont manquantes.\n"
|
||||||
"Votre sauvegarde semble corrompue. Elle a été supprimée."
|
"Votre sauvegarde semble corrompue. Elle a été supprimée."
|
||||||
|
|
||||||
#: squirrelbattle/game.py:251
|
#: squirrelbattle/game.py:357
|
||||||
msgid ""
|
msgid ""
|
||||||
"No player was found on this map!\n"
|
"No player was found on this map!\n"
|
||||||
"Maybe you died?"
|
"Maybe you died?"
|
||||||
|
@ -77,7 +136,7 @@ msgstr ""
|
||||||
"Aucun joueur n'a été trouvé sur la carte !\n"
|
"Aucun joueur n'a été trouvé sur la carte !\n"
|
||||||
"Peut-être êtes-vous mort ?"
|
"Peut-être êtes-vous mort ?"
|
||||||
|
|
||||||
#: squirrelbattle/game.py:271
|
#: squirrelbattle/game.py:379
|
||||||
msgid ""
|
msgid ""
|
||||||
"The JSON file is not correct.\n"
|
"The JSON file is not correct.\n"
|
||||||
"Your save seems corrupted. It got deleted."
|
"Your save seems corrupted. It got deleted."
|
||||||
|
@ -85,27 +144,36 @@ msgstr ""
|
||||||
"Le fichier JSON de sauvegarde est incorrect.\n"
|
"Le fichier JSON de sauvegarde est incorrect.\n"
|
||||||
"Votre sauvegarde semble corrompue. Elle a été supprimée."
|
"Votre sauvegarde semble corrompue. Elle a été supprimée."
|
||||||
|
|
||||||
#: squirrelbattle/interfaces.py:429
|
#: squirrelbattle/interfaces.py:718
|
||||||
|
msgid "It's a critical hit!"
|
||||||
|
msgstr "C'est un coup critique !"
|
||||||
|
|
||||||
|
#: squirrelbattle/interfaces.py:719
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "{name} hits {opponent}."
|
msgid "{name} hits {opponent}."
|
||||||
msgstr "{name} frappe {opponent}."
|
msgstr "{name} frappe {opponent}."
|
||||||
|
|
||||||
#: squirrelbattle/interfaces.py:441
|
#: squirrelbattle/interfaces.py:733
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "{name} takes {amount} damage."
|
msgid "{name} takes {damage} damage."
|
||||||
msgstr "{name} prend {amount} points de dégât."
|
msgstr "{name} prend {damage} dégâts."
|
||||||
|
|
||||||
#: squirrelbattle/interfaces.py:443
|
#: squirrelbattle/interfaces.py:735
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgid "{name} dies."
|
msgid "{name} dies."
|
||||||
msgstr "{name} meurt."
|
msgstr "{name} meurt."
|
||||||
|
|
||||||
|
#: squirrelbattle/interfaces.py:769
|
||||||
|
#, python-brace-format
|
||||||
|
msgid "{entity} said: {message}"
|
||||||
|
msgstr "{entity} a dit : {message}"
|
||||||
|
|
||||||
#: squirrelbattle/menus.py:73
|
#: squirrelbattle/menus.py:73
|
||||||
msgid "Back"
|
msgid "Back"
|
||||||
msgstr "Retour"
|
msgstr "Retour"
|
||||||
|
|
||||||
#: squirrelbattle/tests/game_test.py:314 squirrelbattle/tests/game_test.py:317
|
#: squirrelbattle/tests/game_test.py:368 squirrelbattle/tests/game_test.py:371
|
||||||
#: squirrelbattle/tests/game_test.py:320
|
#: squirrelbattle/tests/game_test.py:374 squirrelbattle/tests/game_test.py:377
|
||||||
#: squirrelbattle/tests/translations_test.py:16
|
#: squirrelbattle/tests/translations_test.py:16
|
||||||
msgid "New game"
|
msgid "New game"
|
||||||
msgstr "Nouvelle partie"
|
msgstr "Nouvelle partie"
|
||||||
|
@ -187,45 +255,93 @@ msgid "Key used to talk to a friendly entity"
|
||||||
msgstr "Touche pour parler à une entité pacifique"
|
msgstr "Touche pour parler à une entité pacifique"
|
||||||
|
|
||||||
#: squirrelbattle/tests/translations_test.py:55
|
#: squirrelbattle/tests/translations_test.py:55
|
||||||
|
msgid "Key used to wait"
|
||||||
|
msgstr "Touche pour attendre"
|
||||||
|
|
||||||
|
#: squirrelbattle/tests/translations_test.py:56
|
||||||
|
msgid "Key used to use ladders"
|
||||||
|
msgstr "Touche pour utiliser les échelles"
|
||||||
|
|
||||||
|
#: squirrelbattle/tests/translations_test.py:58
|
||||||
msgid "Texture pack"
|
msgid "Texture pack"
|
||||||
msgstr "Pack de textures"
|
msgstr "Pack de textures"
|
||||||
|
|
||||||
#: squirrelbattle/tests/translations_test.py:56
|
#: squirrelbattle/tests/translations_test.py:59
|
||||||
msgid "Language"
|
msgid "Language"
|
||||||
msgstr "Langue"
|
msgstr "Langue"
|
||||||
|
|
||||||
#: squirrelbattle/tests/translations_test.py:59
|
#: squirrelbattle/tests/translations_test.py:62
|
||||||
msgid "player"
|
msgid "player"
|
||||||
msgstr "joueur"
|
msgstr "joueur"
|
||||||
|
|
||||||
#: squirrelbattle/tests/translations_test.py:61
|
#: squirrelbattle/tests/translations_test.py:64
|
||||||
msgid "tiger"
|
|
||||||
msgstr "tigre"
|
|
||||||
|
|
||||||
#: squirrelbattle/tests/translations_test.py:62
|
|
||||||
msgid "hedgehog"
|
msgid "hedgehog"
|
||||||
msgstr "hérisson"
|
msgstr "hérisson"
|
||||||
|
|
||||||
#: squirrelbattle/tests/translations_test.py:63
|
#: squirrelbattle/tests/translations_test.py:65
|
||||||
|
msgid "merchant"
|
||||||
|
msgstr "marchand"
|
||||||
|
|
||||||
|
#: squirrelbattle/tests/translations_test.py:66
|
||||||
msgid "rabbit"
|
msgid "rabbit"
|
||||||
msgstr "lapin"
|
msgstr "lapin"
|
||||||
|
|
||||||
#: squirrelbattle/tests/translations_test.py:64
|
#: squirrelbattle/tests/translations_test.py:67
|
||||||
|
msgid "sunflower"
|
||||||
|
msgstr "tournesol"
|
||||||
|
|
||||||
|
#: squirrelbattle/tests/translations_test.py:68
|
||||||
msgid "teddy bear"
|
msgid "teddy bear"
|
||||||
msgstr "nounours"
|
msgstr "nounours"
|
||||||
|
|
||||||
#: squirrelbattle/tests/translations_test.py:66
|
#: squirrelbattle/tests/translations_test.py:69
|
||||||
|
msgid "tiger"
|
||||||
|
msgstr "tigre"
|
||||||
|
|
||||||
|
#: squirrelbattle/tests/translations_test.py:70
|
||||||
|
msgid "eagle"
|
||||||
|
msgstr "pygargue"
|
||||||
|
|
||||||
|
#: squirrelbattle/tests/translations_test.py:72
|
||||||
msgid "body snatch potion"
|
msgid "body snatch potion"
|
||||||
msgstr "potion d'arrachage de corps"
|
msgstr "potion d'arrachage de corps"
|
||||||
|
|
||||||
#: squirrelbattle/tests/translations_test.py:67
|
#: squirrelbattle/tests/translations_test.py:73
|
||||||
msgid "bomb"
|
msgid "bomb"
|
||||||
msgstr "bombe"
|
msgstr "bombe"
|
||||||
|
|
||||||
#: squirrelbattle/tests/translations_test.py:68
|
#: squirrelbattle/tests/translations_test.py:74
|
||||||
|
msgid "explosion"
|
||||||
|
msgstr "explosion"
|
||||||
|
|
||||||
|
#: squirrelbattle/tests/translations_test.py:75
|
||||||
msgid "heart"
|
msgid "heart"
|
||||||
msgstr "cœur"
|
msgstr "cœur"
|
||||||
|
|
||||||
#: squirrelbattle/tests/translations_test.py:69
|
#: squirrelbattle/tests/translations_test.py:76
|
||||||
msgid "sword"
|
msgid "sword"
|
||||||
msgstr "épée"
|
msgstr "épée"
|
||||||
|
|
||||||
|
#: squirrelbattle/tests/translations_test.py:77
|
||||||
|
msgid "helmet"
|
||||||
|
msgstr "casque"
|
||||||
|
|
||||||
|
#: squirrelbattle/tests/translations_test.py:78
|
||||||
|
msgid "chestplate"
|
||||||
|
msgstr "plastron"
|
||||||
|
|
||||||
|
#: squirrelbattle/tests/translations_test.py:79
|
||||||
|
msgid "shield"
|
||||||
|
msgstr "bouclier"
|
||||||
|
|
||||||
|
#: squirrelbattle/tests/translations_test.py:80
|
||||||
|
msgid "ring of critical damage"
|
||||||
|
msgstr "anneau de coup critique"
|
||||||
|
|
||||||
|
#: squirrelbattle/tests/translations_test.py:82
|
||||||
|
msgid "ring of more experience"
|
||||||
|
msgstr "anneau de plus d'expérience"
|
||||||
|
|
||||||
|
#: squirrelbattle/tests/translations_test.py:84
|
||||||
|
msgid "monocle"
|
||||||
|
msgstr "monocle"
|
||||||
|
|
|
@ -14,7 +14,7 @@ from .translations import gettext as _, Translator
|
||||||
|
|
||||||
class Menu:
|
class Menu:
|
||||||
"""
|
"""
|
||||||
A Menu object is the logical representation of a menu in the game
|
A Menu object is the logical representation of a menu in the game.
|
||||||
"""
|
"""
|
||||||
values: list
|
values: list
|
||||||
|
|
||||||
|
@ -23,26 +23,26 @@ class Menu:
|
||||||
|
|
||||||
def go_up(self) -> None:
|
def go_up(self) -> None:
|
||||||
"""
|
"""
|
||||||
Moves the pointer of the menu on the previous value
|
Moves the pointer of the menu on the previous value.
|
||||||
"""
|
"""
|
||||||
self.position = max(0, self.position - 1)
|
self.position = max(0, self.position - 1)
|
||||||
|
|
||||||
def go_down(self) -> None:
|
def go_down(self) -> None:
|
||||||
"""
|
"""
|
||||||
Moves the pointer of the menu on the next value
|
Moves the pointer of the menu on the next value.
|
||||||
"""
|
"""
|
||||||
self.position = min(len(self.values) - 1, self.position + 1)
|
self.position = min(len(self.values) - 1, self.position + 1)
|
||||||
|
|
||||||
def validate(self) -> Any:
|
def validate(self) -> Any:
|
||||||
"""
|
"""
|
||||||
Selects the value that is pointed by the menu pointer
|
Selects the value that is pointed by the menu pointer.
|
||||||
"""
|
"""
|
||||||
return self.values[self.position]
|
return self.values[self.position]
|
||||||
|
|
||||||
|
|
||||||
class MainMenuValues(Enum):
|
class MainMenuValues(Enum):
|
||||||
"""
|
"""
|
||||||
Values of the main menu
|
Values of the main menu.
|
||||||
"""
|
"""
|
||||||
START = "New game"
|
START = "New game"
|
||||||
RESUME = "Resume"
|
RESUME = "Resume"
|
||||||
|
@ -57,14 +57,14 @@ class MainMenuValues(Enum):
|
||||||
|
|
||||||
class MainMenu(Menu):
|
class MainMenu(Menu):
|
||||||
"""
|
"""
|
||||||
A special instance of a menu : the main menu
|
A special instance of a menu : the main menu.
|
||||||
"""
|
"""
|
||||||
values = [e for e in MainMenuValues]
|
values = [e for e in MainMenuValues]
|
||||||
|
|
||||||
|
|
||||||
class SettingsMenu(Menu):
|
class SettingsMenu(Menu):
|
||||||
"""
|
"""
|
||||||
A special instance of a menu : the settings menu
|
A special instance of a menu : the settings menu.
|
||||||
"""
|
"""
|
||||||
waiting_for_key: bool = False
|
waiting_for_key: bool = False
|
||||||
|
|
||||||
|
@ -75,7 +75,7 @@ class SettingsMenu(Menu):
|
||||||
def handle_key_pressed(self, key: Optional[KeyValues], raw_key: str,
|
def handle_key_pressed(self, key: Optional[KeyValues], raw_key: str,
|
||||||
game: Any) -> None:
|
game: Any) -> None:
|
||||||
"""
|
"""
|
||||||
In the setting menu, we van select a setting and change it
|
In the setting menu, we can select a setting and change it.
|
||||||
"""
|
"""
|
||||||
if not self.waiting_for_key:
|
if not self.waiting_for_key:
|
||||||
# Navigate normally through the menu.
|
# Navigate normally through the menu.
|
||||||
|
@ -121,22 +121,40 @@ class SettingsMenu(Menu):
|
||||||
|
|
||||||
|
|
||||||
class InventoryMenu(Menu):
|
class InventoryMenu(Menu):
|
||||||
|
"""
|
||||||
|
A special instance of a menu : the menu for the inventory of the player.
|
||||||
|
"""
|
||||||
player: Player
|
player: Player
|
||||||
|
|
||||||
def update_player(self, player: Player) -> None:
|
def update_player(self, player: Player) -> None:
|
||||||
|
"""
|
||||||
|
Updates the player.
|
||||||
|
"""
|
||||||
self.player = player
|
self.player = player
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def values(self) -> list:
|
def values(self) -> list:
|
||||||
|
"""
|
||||||
|
Returns the values of the menu.
|
||||||
|
"""
|
||||||
return self.player.inventory
|
return self.player.inventory
|
||||||
|
|
||||||
|
|
||||||
class StoreMenu(Menu):
|
class StoreMenu(Menu):
|
||||||
merchant: Merchant
|
"""
|
||||||
|
A special instance of a menu : the menu for the inventory of a merchant.
|
||||||
|
"""
|
||||||
|
merchant: Merchant = None
|
||||||
|
|
||||||
def update_merchant(self, merchant: Merchant) -> None:
|
def update_merchant(self, merchant: Merchant) -> None:
|
||||||
|
"""
|
||||||
|
Updates the merchant.
|
||||||
|
"""
|
||||||
self.merchant = merchant
|
self.merchant = merchant
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def values(self) -> list:
|
def values(self) -> list:
|
||||||
return self.merchant.inventory
|
"""
|
||||||
|
Returns the values of the menu.
|
||||||
|
"""
|
||||||
|
return self.merchant.inventory if self.merchant else []
|
||||||
|
|
|
@ -13,9 +13,10 @@ from .translations import gettext as _
|
||||||
class Settings:
|
class Settings:
|
||||||
"""
|
"""
|
||||||
This class stores the settings of the game.
|
This class stores the settings of the game.
|
||||||
Settings can be get by using for example settings.TEXTURE_PACK directly.
|
Settings can be obtained by using for example settings.TEXTURE_PACK
|
||||||
The comment can be get by using settings.get_comment('TEXTURE_PACK').
|
directly.
|
||||||
We can define the setting by simply use settings.TEXTURE_PACK = 'new_key'
|
The comment can be obtained by using settings.get_comment('TEXTURE_PACK').
|
||||||
|
We can set the setting by simply using settings.TEXTURE_PACK = 'new_key'
|
||||||
"""
|
"""
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.KEY_UP_PRIMARY = ['z', 'Main key to move up']
|
self.KEY_UP_PRIMARY = ['z', 'Main key to move up']
|
||||||
|
@ -32,6 +33,8 @@ class Settings:
|
||||||
self.KEY_EQUIP = ['e', 'Key used to equip an item in the inventory']
|
self.KEY_EQUIP = ['e', 'Key used to equip an item in the inventory']
|
||||||
self.KEY_DROP = ['r', 'Key used to drop an item in the inventory']
|
self.KEY_DROP = ['r', 'Key used to drop an item in the inventory']
|
||||||
self.KEY_CHAT = ['t', 'Key used to talk to a friendly entity']
|
self.KEY_CHAT = ['t', 'Key used to talk to a friendly entity']
|
||||||
|
self.KEY_WAIT = ['w', 'Key used to wait']
|
||||||
|
self.KEY_LADDER = ['<', 'Key used to use ladders']
|
||||||
self.TEXTURE_PACK = ['ascii', 'Texture pack']
|
self.TEXTURE_PACK = ['ascii', 'Texture pack']
|
||||||
self.LOCALE = [locale.getlocale()[0][:2], 'Language']
|
self.LOCALE = [locale.getlocale()[0][:2], 'Language']
|
||||||
|
|
||||||
|
@ -49,7 +52,7 @@ class Settings:
|
||||||
|
|
||||||
def get_comment(self, item: str) -> str:
|
def get_comment(self, item: str) -> str:
|
||||||
"""
|
"""
|
||||||
Retrieve the comment of a setting.
|
Retrieves the comment relative to a setting.
|
||||||
"""
|
"""
|
||||||
if item in self.settings_keys:
|
if item in self.settings_keys:
|
||||||
return _(object.__getattribute__(self, item)[1])
|
return _(object.__getattribute__(self, item)[1])
|
||||||
|
@ -60,21 +63,22 @@ class Settings:
|
||||||
@property
|
@property
|
||||||
def settings_keys(self) -> Generator[str, Any, None]:
|
def settings_keys(self) -> Generator[str, Any, None]:
|
||||||
"""
|
"""
|
||||||
Get the list of all parameters.
|
Gets the list of all parameters.
|
||||||
"""
|
"""
|
||||||
return (key for key in self.__dict__)
|
return (key for key in self.__dict__)
|
||||||
|
|
||||||
def loads_from_string(self, json_str: str) -> None:
|
def loads_from_string(self, json_str: str) -> None:
|
||||||
"""
|
"""
|
||||||
Dump settings
|
Loads settings.
|
||||||
"""
|
"""
|
||||||
d = json.loads(json_str)
|
d = json.loads(json_str)
|
||||||
for key in d:
|
for key in d:
|
||||||
setattr(self, key, d[key])
|
if hasattr(self, key):
|
||||||
|
setattr(self, key, d[key])
|
||||||
|
|
||||||
def dumps_to_string(self) -> str:
|
def dumps_to_string(self) -> str:
|
||||||
"""
|
"""
|
||||||
Dump settings
|
Dumps settings.
|
||||||
"""
|
"""
|
||||||
d = dict()
|
d = dict()
|
||||||
for key in self.settings_keys:
|
for key in self.settings_keys:
|
||||||
|
|
|
@ -8,7 +8,7 @@ from types import TracebackType
|
||||||
class TermManager: # pragma: no cover
|
class TermManager: # pragma: no cover
|
||||||
"""
|
"""
|
||||||
The TermManager object initializes the terminal, returns a screen object and
|
The TermManager object initializes the terminal, returns a screen object and
|
||||||
de-initializes the terminal after use
|
de-initializes the terminal after use.
|
||||||
"""
|
"""
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.screen = curses.initscr()
|
self.screen = curses.initscr()
|
||||||
|
@ -21,7 +21,7 @@ class TermManager: # pragma: no cover
|
||||||
# make cursor invisible
|
# make cursor invisible
|
||||||
curses.curs_set(False)
|
curses.curs_set(False)
|
||||||
# Catch mouse events
|
# Catch mouse events
|
||||||
curses.mousemask(True)
|
curses.mousemask(curses.ALL_MOUSE_EVENTS | curses.REPORT_MOUSE_POSITION)
|
||||||
# Enable colors
|
# Enable colors
|
||||||
curses.start_color()
|
curses.start_color()
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
# Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse
|
# Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
import random
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from squirrelbattle.entities.items import BodySnatchPotion, Bomb, Heart, Item
|
from squirrelbattle.entities.items import BodySnatchPotion, Bomb, Heart, Item, \
|
||||||
from squirrelbattle.entities.monsters import Tiger, Hedgehog, Rabbit, TeddyBear
|
Explosion
|
||||||
|
from squirrelbattle.entities.monsters import Tiger, Hedgehog, Rabbit,\
|
||||||
|
TeddyBear, GiantSeaEagle
|
||||||
|
from squirrelbattle.entities.friendly import Trumpet
|
||||||
from squirrelbattle.entities.player import Player
|
from squirrelbattle.entities.player import Player
|
||||||
from squirrelbattle.interfaces import Entity, Map
|
from squirrelbattle.interfaces import Entity, Map
|
||||||
from squirrelbattle.resources import ResourceManager
|
from squirrelbattle.resources import ResourceManager
|
||||||
|
@ -13,16 +16,17 @@ from squirrelbattle.resources import ResourceManager
|
||||||
class TestEntities(unittest.TestCase):
|
class TestEntities(unittest.TestCase):
|
||||||
def setUp(self) -> None:
|
def setUp(self) -> None:
|
||||||
"""
|
"""
|
||||||
Load example map that can be used in tests.
|
Loads example map that can be used in tests.
|
||||||
"""
|
"""
|
||||||
self.map = Map.load(ResourceManager.get_asset_path("example_map.txt"))
|
self.map = Map.load(ResourceManager.get_asset_path("example_map.txt"))
|
||||||
self.player = Player()
|
self.player = Player()
|
||||||
|
self.player.constitution = 0
|
||||||
self.map.add_entity(self.player)
|
self.map.add_entity(self.player)
|
||||||
self.player.move(self.map.start_y, self.map.start_x)
|
self.player.move(self.map.start_y, self.map.start_x)
|
||||||
|
|
||||||
def test_basic_entities(self) -> None:
|
def test_basic_entities(self) -> None:
|
||||||
"""
|
"""
|
||||||
Test some random stuff with basic entities.
|
Tests some random stuff with basic entities.
|
||||||
"""
|
"""
|
||||||
entity = Entity()
|
entity = Entity()
|
||||||
entity.move(42, 64)
|
entity.move(42, 64)
|
||||||
|
@ -37,7 +41,7 @@ class TestEntities(unittest.TestCase):
|
||||||
|
|
||||||
def test_fighting_entities(self) -> None:
|
def test_fighting_entities(self) -> None:
|
||||||
"""
|
"""
|
||||||
Test some random stuff with fighting entities.
|
Tests some random stuff with fighting entities.
|
||||||
"""
|
"""
|
||||||
entity = Tiger()
|
entity = Tiger()
|
||||||
self.map.add_entity(entity)
|
self.map.add_entity(entity)
|
||||||
|
@ -53,20 +57,21 @@ class TestEntities(unittest.TestCase):
|
||||||
self.assertTrue(entity.dead)
|
self.assertTrue(entity.dead)
|
||||||
|
|
||||||
entity = Rabbit()
|
entity = Rabbit()
|
||||||
|
entity.critical = 0
|
||||||
self.map.add_entity(entity)
|
self.map.add_entity(entity)
|
||||||
entity.move(15, 44)
|
entity.move(15, 44)
|
||||||
# Move randomly
|
# Move randomly
|
||||||
self.map.tick()
|
self.map.tick(self.player)
|
||||||
self.assertFalse(entity.y == 15 and entity.x == 44)
|
self.assertFalse(entity.y == 15 and entity.x == 44)
|
||||||
|
|
||||||
# Move to the player
|
# Move to the player
|
||||||
entity.move(3, 6)
|
entity.move(3, 6)
|
||||||
self.map.tick()
|
self.map.tick(self.player)
|
||||||
self.assertTrue(entity.y == 2 and entity.x == 6)
|
self.assertTrue(entity.y == 2 and entity.x == 6)
|
||||||
|
|
||||||
# Rabbit should fight
|
# Rabbit should fight
|
||||||
old_health = self.player.health
|
old_health = self.player.health
|
||||||
self.map.tick()
|
self.map.tick(self.player)
|
||||||
self.assertTrue(entity.y == 2 and entity.x == 6)
|
self.assertTrue(entity.y == 2 and entity.x == 6)
|
||||||
self.assertEqual(old_health - entity.strength, self.player.health)
|
self.assertEqual(old_health - entity.strength, self.player.health)
|
||||||
self.assertEqual(self.map.logs.messages[-1],
|
self.assertEqual(self.map.logs.messages[-1],
|
||||||
|
@ -74,6 +79,7 @@ class TestEntities(unittest.TestCase):
|
||||||
{self.player.name.capitalize()} takes {entity.strength} damage.")
|
{self.player.name.capitalize()} takes {entity.strength} damage.")
|
||||||
|
|
||||||
# Fight the rabbit
|
# Fight the rabbit
|
||||||
|
self.player.critical = 0
|
||||||
old_health = entity.health
|
old_health = entity.health
|
||||||
self.player.move_down()
|
self.player.move_down()
|
||||||
self.assertEqual(entity.health, old_health - self.player.strength)
|
self.assertEqual(entity.health, old_health - self.player.strength)
|
||||||
|
@ -88,9 +94,50 @@ class TestEntities(unittest.TestCase):
|
||||||
self.assertTrue(entity.dead)
|
self.assertTrue(entity.dead)
|
||||||
self.assertGreaterEqual(self.player.current_xp, 3)
|
self.assertGreaterEqual(self.player.current_xp, 3)
|
||||||
|
|
||||||
|
# Test the familiars
|
||||||
|
fam = Trumpet()
|
||||||
|
entity = Rabbit()
|
||||||
|
self.map.add_entity(entity)
|
||||||
|
self.map.add_entity(fam)
|
||||||
|
self.player.move(1, 6)
|
||||||
|
entity.move(2, 6)
|
||||||
|
fam.move(2, 7)
|
||||||
|
|
||||||
|
# Test fighting
|
||||||
|
entity.health = 2
|
||||||
|
entity.paths = []
|
||||||
|
entity.recalculate_paths()
|
||||||
|
fam.target = entity
|
||||||
|
self.map.tick(self.player)
|
||||||
|
self.assertTrue(entity.dead)
|
||||||
|
|
||||||
|
# Test finding a new target
|
||||||
|
entity2 = Rabbit()
|
||||||
|
self.map.add_entity(entity2)
|
||||||
|
entity2.move(2, 6)
|
||||||
|
self.map.tick(self.player)
|
||||||
|
self.assertTrue(fam.target == entity2)
|
||||||
|
self.map.remove_entity(entity2)
|
||||||
|
|
||||||
|
# Test following the player and finding the player as target
|
||||||
|
self.player.move(5, 5)
|
||||||
|
fam.move(4, 5)
|
||||||
|
fam.target = None
|
||||||
|
self.player.move_down()
|
||||||
|
self.map.tick(self.player)
|
||||||
|
self.assertTrue(fam.target == self.player)
|
||||||
|
self.assertEqual(fam.y, 5)
|
||||||
|
self.assertEqual(fam.x, 5)
|
||||||
|
|
||||||
|
# Test random move
|
||||||
|
fam.move(13, 20)
|
||||||
|
fam.target = self.player
|
||||||
|
self.map.tick(self.player)
|
||||||
|
self.assertTrue(fam.x != 20 or fam.y != 13)
|
||||||
|
|
||||||
def test_items(self) -> None:
|
def test_items(self) -> None:
|
||||||
"""
|
"""
|
||||||
Test some random stuff with items.
|
Tests some random stuff with items.
|
||||||
"""
|
"""
|
||||||
item = Item()
|
item = Item()
|
||||||
self.map.add_entity(item)
|
self.map.add_entity(item)
|
||||||
|
@ -111,7 +158,7 @@ class TestEntities(unittest.TestCase):
|
||||||
|
|
||||||
def test_bombs(self) -> None:
|
def test_bombs(self) -> None:
|
||||||
"""
|
"""
|
||||||
Test some random stuff with bombs.
|
Tests some random stuff with bombs.
|
||||||
"""
|
"""
|
||||||
item = Bomb()
|
item = Bomb()
|
||||||
hedgehog = Hedgehog()
|
hedgehog = Hedgehog()
|
||||||
|
@ -132,16 +179,30 @@ class TestEntities(unittest.TestCase):
|
||||||
self.assertEqual(item.y, 42)
|
self.assertEqual(item.y, 42)
|
||||||
self.assertEqual(item.x, 42)
|
self.assertEqual(item.x, 42)
|
||||||
# Wait for the explosion
|
# Wait for the explosion
|
||||||
for ignored in range(5):
|
for _ignored in range(5):
|
||||||
item.act(self.map)
|
item.act(self.map)
|
||||||
self.assertTrue(hedgehog.dead)
|
self.assertTrue(hedgehog.dead)
|
||||||
self.assertTrue(teddy_bear.dead)
|
self.assertTrue(teddy_bear.dead)
|
||||||
bomb_state = item.save_state()
|
bomb_state = item.save_state()
|
||||||
self.assertEqual(bomb_state["damage"], item.damage)
|
self.assertEqual(bomb_state["damage"], item.damage)
|
||||||
|
explosions = self.map.find_entities(Explosion)
|
||||||
|
self.assertTrue(explosions)
|
||||||
|
explosion = explosions[0]
|
||||||
|
self.assertEqual(explosion.y, item.y)
|
||||||
|
self.assertEqual(explosion.x, item.x)
|
||||||
|
|
||||||
|
# The player can't hold the explosion
|
||||||
|
explosion.hold(self.player)
|
||||||
|
self.assertNotIn(explosion, self.player.inventory)
|
||||||
|
self.assertFalse(explosion.held)
|
||||||
|
|
||||||
|
# The explosion disappears after one tick
|
||||||
|
explosion.act(self.map)
|
||||||
|
self.assertNotIn(explosion, self.map.entities)
|
||||||
|
|
||||||
def test_hearts(self) -> None:
|
def test_hearts(self) -> None:
|
||||||
"""
|
"""
|
||||||
Test some random stuff with hearts.
|
Tests some random stuff with hearts.
|
||||||
"""
|
"""
|
||||||
item = Heart()
|
item = Heart()
|
||||||
self.map.add_entity(item)
|
self.map.add_entity(item)
|
||||||
|
@ -156,7 +217,7 @@ class TestEntities(unittest.TestCase):
|
||||||
|
|
||||||
def test_body_snatch_potion(self) -> None:
|
def test_body_snatch_potion(self) -> None:
|
||||||
"""
|
"""
|
||||||
Test some random stuff with body snatch potions.
|
Tests some random stuff with body snatch potions.
|
||||||
"""
|
"""
|
||||||
item = BodySnatchPotion()
|
item = BodySnatchPotion()
|
||||||
self.map.add_entity(item)
|
self.map.add_entity(item)
|
||||||
|
@ -174,7 +235,7 @@ class TestEntities(unittest.TestCase):
|
||||||
|
|
||||||
def test_players(self) -> None:
|
def test_players(self) -> None:
|
||||||
"""
|
"""
|
||||||
Test some random stuff with players.
|
Tests some random stuff with players.
|
||||||
"""
|
"""
|
||||||
player = Player()
|
player = Player()
|
||||||
self.map.add_entity(player)
|
self.map.add_entity(player)
|
||||||
|
@ -204,3 +265,17 @@ class TestEntities(unittest.TestCase):
|
||||||
|
|
||||||
player_state = player.save_state()
|
player_state = player.save_state()
|
||||||
self.assertEqual(player_state["current_xp"], 10)
|
self.assertEqual(player_state["current_xp"], 10)
|
||||||
|
|
||||||
|
def test_critical_hit(self) -> None:
|
||||||
|
"""
|
||||||
|
Ensure that critical hits are working.
|
||||||
|
"""
|
||||||
|
random.seed(2) # Next random.randint(1, 100) will output 8
|
||||||
|
self.player.critical = 10
|
||||||
|
sea_eagle = GiantSeaEagle()
|
||||||
|
self.map.add_entity(sea_eagle)
|
||||||
|
sea_eagle.move(2, 6)
|
||||||
|
old_health = sea_eagle.health
|
||||||
|
self.player.hit(sea_eagle)
|
||||||
|
self.assertEqual(sea_eagle.health,
|
||||||
|
old_health - self.player.strength * 4)
|
||||||
|
|
|
@ -1,6 +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
|
||||||
|
|
||||||
|
import curses
|
||||||
import os
|
import os
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
@ -8,11 +9,13 @@ from ..bootstrap import Bootstrap
|
||||||
from ..display.display import Display
|
from ..display.display import Display
|
||||||
from ..display.display_manager import DisplayManager
|
from ..display.display_manager import DisplayManager
|
||||||
from ..entities.friendly import Merchant, Sunflower
|
from ..entities.friendly import Merchant, Sunflower
|
||||||
from ..entities.items import Bomb, Heart, Sword
|
from ..entities.items import Bomb, Heart, Sword, Explosion, Shield, Helmet, \
|
||||||
|
Chestplate, RingCritical, Monocle
|
||||||
|
from ..entities.monsters import GiantSeaEagle
|
||||||
from ..entities.player import Player
|
from ..entities.player import Player
|
||||||
from ..enums import DisplayActions
|
from ..enums import DisplayActions
|
||||||
from ..game import Game, KeyValues, GameMode
|
from ..game import Game, KeyValues, GameMode
|
||||||
from ..interfaces import Tile, Map
|
from ..interfaces import Map, Tile
|
||||||
from ..menus import MainMenuValues
|
from ..menus import MainMenuValues
|
||||||
from ..resources import ResourceManager
|
from ..resources import ResourceManager
|
||||||
from ..settings import Settings
|
from ..settings import Settings
|
||||||
|
@ -22,21 +25,21 @@ from ..translations import gettext as _, Translator
|
||||||
class TestGame(unittest.TestCase):
|
class TestGame(unittest.TestCase):
|
||||||
def setUp(self) -> None:
|
def setUp(self) -> None:
|
||||||
"""
|
"""
|
||||||
Setup game.
|
Sets the game up.
|
||||||
"""
|
"""
|
||||||
self.game = Game()
|
self.game = Game()
|
||||||
self.game.new_game()
|
self.game.new_game()
|
||||||
self.game.map = Map.load(ResourceManager.get_asset_path(
|
self.game.map = Map.load(
|
||||||
"example_map.txt"))
|
ResourceManager.get_asset_path("example_map.txt"))
|
||||||
self.game.player.move(self.game.map.start_y, self.game.map.start_x)
|
|
||||||
self.game.map.add_entity(self.game.player)
|
self.game.map.add_entity(self.game.player)
|
||||||
|
self.game.player.move(self.game.map.start_y, self.game.map.start_x)
|
||||||
self.game.logs.add_message("Hello World !")
|
self.game.logs.add_message("Hello World !")
|
||||||
display = DisplayManager(None, self.game)
|
display = DisplayManager(None, self.game)
|
||||||
self.game.display_actions = display.handle_display_action
|
self.game.display_actions = display.handle_display_action
|
||||||
|
|
||||||
def test_load_game(self) -> None:
|
def test_load_game(self) -> None:
|
||||||
"""
|
"""
|
||||||
Save a game and reload it.
|
Saves a game and reloads it.
|
||||||
"""
|
"""
|
||||||
bomb = Bomb()
|
bomb = Bomb()
|
||||||
self.game.map.add_entity(bomb)
|
self.game.map.add_entity(bomb)
|
||||||
|
@ -63,6 +66,7 @@ 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)
|
||||||
|
self.assertIsNone(self.game.message)
|
||||||
|
|
||||||
# Ensure that the bomb is loaded
|
# Ensure that the bomb is loaded
|
||||||
self.assertTrue(self.game.player.inventory)
|
self.assertTrue(self.game.player.inventory)
|
||||||
|
@ -90,7 +94,7 @@ class TestGame(unittest.TestCase):
|
||||||
|
|
||||||
def test_bootstrap_fail(self) -> None:
|
def test_bootstrap_fail(self) -> None:
|
||||||
"""
|
"""
|
||||||
Ensure that the test can't play the game,
|
Ensures that the test can't play the game,
|
||||||
because there is no associated shell.
|
because there is no associated shell.
|
||||||
Yeah, that's only for coverage.
|
Yeah, that's only for coverage.
|
||||||
"""
|
"""
|
||||||
|
@ -99,7 +103,7 @@ class TestGame(unittest.TestCase):
|
||||||
|
|
||||||
def test_key_translation(self) -> None:
|
def test_key_translation(self) -> None:
|
||||||
"""
|
"""
|
||||||
Test key bindings.
|
Tests key bindings.
|
||||||
"""
|
"""
|
||||||
self.game.settings = Settings()
|
self.game.settings = Settings()
|
||||||
|
|
||||||
|
@ -145,6 +149,12 @@ class TestGame(unittest.TestCase):
|
||||||
self.assertEqual(KeyValues.translate_key(
|
self.assertEqual(KeyValues.translate_key(
|
||||||
self.game.settings.KEY_DROP, self.game.settings),
|
self.game.settings.KEY_DROP, self.game.settings),
|
||||||
KeyValues.DROP)
|
KeyValues.DROP)
|
||||||
|
self.assertEqual(KeyValues.translate_key(
|
||||||
|
self.game.settings.KEY_WAIT, self.game.settings),
|
||||||
|
KeyValues.WAIT)
|
||||||
|
self.assertEqual(KeyValues.translate_key(
|
||||||
|
self.game.settings.KEY_LADDER, self.game.settings),
|
||||||
|
KeyValues.LADDER)
|
||||||
self.assertEqual(KeyValues.translate_key(' ', self.game.settings),
|
self.assertEqual(KeyValues.translate_key(' ', self.game.settings),
|
||||||
KeyValues.SPACE)
|
KeyValues.SPACE)
|
||||||
self.assertEqual(KeyValues.translate_key('plop', self.game.settings),
|
self.assertEqual(KeyValues.translate_key('plop', self.game.settings),
|
||||||
|
@ -152,7 +162,7 @@ class TestGame(unittest.TestCase):
|
||||||
|
|
||||||
def test_key_press(self) -> None:
|
def test_key_press(self) -> None:
|
||||||
"""
|
"""
|
||||||
Press a key and see what is done.
|
Presses a key and asserts what is done is correct.
|
||||||
"""
|
"""
|
||||||
self.assertEqual(self.game.state, GameMode.MAINMENU)
|
self.assertEqual(self.game.state, GameMode.MAINMENU)
|
||||||
self.assertEqual(self.game.main_menu.validate(),
|
self.assertEqual(self.game.main_menu.validate(),
|
||||||
|
@ -238,17 +248,28 @@ class TestGame(unittest.TestCase):
|
||||||
self.assertEqual(new_y, y)
|
self.assertEqual(new_y, y)
|
||||||
self.assertEqual(new_x, x - 1)
|
self.assertEqual(new_x, x - 1)
|
||||||
|
|
||||||
|
explosion = Explosion()
|
||||||
|
self.game.map.add_entity(explosion)
|
||||||
|
self.assertIn(explosion, self.game.map.entities)
|
||||||
|
self.game.handle_key_pressed(KeyValues.WAIT)
|
||||||
|
self.assertNotIn(explosion, self.game.map.entities)
|
||||||
|
|
||||||
self.game.handle_key_pressed(KeyValues.SPACE)
|
self.game.handle_key_pressed(KeyValues.SPACE)
|
||||||
self.assertEqual(self.game.state, GameMode.MAINMENU)
|
self.assertEqual(self.game.state, GameMode.MAINMENU)
|
||||||
|
|
||||||
def test_mouse_click(self) -> None:
|
def test_mouse_click(self) -> None:
|
||||||
"""
|
"""
|
||||||
Simulate mouse clicks.
|
Simulates mouse clicks.
|
||||||
"""
|
"""
|
||||||
self.game.state = GameMode.MAINMENU
|
self.game.state = GameMode.MAINMENU
|
||||||
|
|
||||||
|
# Change the color of the artwork
|
||||||
|
self.game.display_actions(DisplayActions.MOUSE, 0, 10,
|
||||||
|
curses.BUTTON1_CLICKED)
|
||||||
|
|
||||||
# Settings menu
|
# Settings menu
|
||||||
self.game.display_actions(DisplayActions.MOUSE, 25, 21)
|
self.game.display_actions(DisplayActions.MOUSE, 25, 21,
|
||||||
|
curses.BUTTON1_CLICKED)
|
||||||
self.assertEqual(self.game.main_menu.position, 4)
|
self.assertEqual(self.game.main_menu.position, 4)
|
||||||
self.assertEqual(self.game.state, GameMode.SETTINGS)
|
self.assertEqual(self.game.state, GameMode.SETTINGS)
|
||||||
|
|
||||||
|
@ -260,17 +281,19 @@ class TestGame(unittest.TestCase):
|
||||||
self.game.state = GameMode.INVENTORY
|
self.game.state = GameMode.INVENTORY
|
||||||
|
|
||||||
# Click nowhere
|
# Click nowhere
|
||||||
self.game.display_actions(DisplayActions.MOUSE, 0, 0)
|
self.game.display_actions(DisplayActions.MOUSE, 0, 0,
|
||||||
|
curses.BUTTON1_CLICKED)
|
||||||
self.assertEqual(self.game.state, GameMode.INVENTORY)
|
self.assertEqual(self.game.state, GameMode.INVENTORY)
|
||||||
|
|
||||||
# Click on the second item
|
# Click on the second item
|
||||||
self.game.display_actions(DisplayActions.MOUSE, 8, 25)
|
self.game.display_actions(DisplayActions.MOUSE, 8, 25,
|
||||||
|
curses.BUTTON1_CLICKED)
|
||||||
self.assertEqual(self.game.state, GameMode.INVENTORY)
|
self.assertEqual(self.game.state, GameMode.INVENTORY)
|
||||||
self.assertEqual(self.game.inventory_menu.position, 1)
|
self.assertEqual(self.game.inventory_menu.position, 1)
|
||||||
|
|
||||||
def test_new_game(self) -> None:
|
def test_new_game(self) -> None:
|
||||||
"""
|
"""
|
||||||
Ensure that the start button starts a new game.
|
Ensures that the start button starts a new game.
|
||||||
"""
|
"""
|
||||||
old_map = self.game.map
|
old_map = self.game.map
|
||||||
old_player = self.game.player
|
old_player = self.game.player
|
||||||
|
@ -293,7 +316,7 @@ class TestGame(unittest.TestCase):
|
||||||
|
|
||||||
def test_settings_menu(self) -> None:
|
def test_settings_menu(self) -> None:
|
||||||
"""
|
"""
|
||||||
Ensure that the settings menu is working properly.
|
Ensures that the settings menu is working properly.
|
||||||
"""
|
"""
|
||||||
self.game.settings = Settings()
|
self.game.settings = Settings()
|
||||||
|
|
||||||
|
@ -305,13 +328,13 @@ class TestGame(unittest.TestCase):
|
||||||
self.game.handle_key_pressed(KeyValues.ENTER)
|
self.game.handle_key_pressed(KeyValues.ENTER)
|
||||||
self.assertEqual(self.game.state, GameMode.SETTINGS)
|
self.assertEqual(self.game.state, GameMode.SETTINGS)
|
||||||
|
|
||||||
# Define the "move up" key to 'w'
|
# Define the "move up" key to 'h'
|
||||||
self.assertFalse(self.game.settings_menu.waiting_for_key)
|
self.assertFalse(self.game.settings_menu.waiting_for_key)
|
||||||
self.game.handle_key_pressed(KeyValues.ENTER)
|
self.game.handle_key_pressed(KeyValues.ENTER)
|
||||||
self.assertTrue(self.game.settings_menu.waiting_for_key)
|
self.assertTrue(self.game.settings_menu.waiting_for_key)
|
||||||
self.game.handle_key_pressed(None, 'w')
|
self.game.handle_key_pressed(None, 'h')
|
||||||
self.assertFalse(self.game.settings_menu.waiting_for_key)
|
self.assertFalse(self.game.settings_menu.waiting_for_key)
|
||||||
self.assertEqual(self.game.settings.KEY_UP_PRIMARY, 'w')
|
self.assertEqual(self.game.settings.KEY_UP_PRIMARY, 'h')
|
||||||
|
|
||||||
# Navigate to "move left"
|
# Navigate to "move left"
|
||||||
self.game.handle_key_pressed(KeyValues.DOWN)
|
self.game.handle_key_pressed(KeyValues.DOWN)
|
||||||
|
@ -332,7 +355,7 @@ class TestGame(unittest.TestCase):
|
||||||
self.assertEqual(self.game.settings.KEY_LEFT_PRIMARY, 'a')
|
self.assertEqual(self.game.settings.KEY_LEFT_PRIMARY, 'a')
|
||||||
|
|
||||||
# Navigate to "texture pack"
|
# Navigate to "texture pack"
|
||||||
for ignored in range(10):
|
for ignored in range(12):
|
||||||
self.game.handle_key_pressed(KeyValues.DOWN)
|
self.game.handle_key_pressed(KeyValues.DOWN)
|
||||||
|
|
||||||
# Change texture pack
|
# Change texture pack
|
||||||
|
@ -379,7 +402,7 @@ class TestGame(unittest.TestCase):
|
||||||
|
|
||||||
def test_dead_screen(self) -> None:
|
def test_dead_screen(self) -> None:
|
||||||
"""
|
"""
|
||||||
Kill player and render dead screen.
|
Kills the player and renders the dead message on the fake screen.
|
||||||
"""
|
"""
|
||||||
self.game.state = GameMode.PLAY
|
self.game.state = GameMode.PLAY
|
||||||
# Kill player
|
# Kill player
|
||||||
|
@ -395,13 +418,14 @@ class TestGame(unittest.TestCase):
|
||||||
|
|
||||||
def test_not_implemented(self) -> None:
|
def test_not_implemented(self) -> None:
|
||||||
"""
|
"""
|
||||||
Check that some functions are not implemented, only for coverage.
|
Checks that some functions are not implemented, only for coverage.
|
||||||
"""
|
"""
|
||||||
self.assertRaises(NotImplementedError, Display.display, None)
|
self.assertRaises(NotImplementedError, Display.display, None)
|
||||||
|
self.assertRaises(NotImplementedError, Display.update, None, self.game)
|
||||||
|
|
||||||
def test_messages(self) -> None:
|
def test_messages(self) -> None:
|
||||||
"""
|
"""
|
||||||
Display error messages.
|
Displays error messages.
|
||||||
"""
|
"""
|
||||||
self.game.message = "I am an error"
|
self.game.message = "I am an error"
|
||||||
self.game.display_actions(DisplayActions.UPDATE)
|
self.game.display_actions(DisplayActions.UPDATE)
|
||||||
|
@ -411,7 +435,7 @@ class TestGame(unittest.TestCase):
|
||||||
|
|
||||||
def test_inventory_menu(self) -> None:
|
def test_inventory_menu(self) -> None:
|
||||||
"""
|
"""
|
||||||
Open the inventory menu and interact with items.
|
Opens the inventory menu and interacts with items.
|
||||||
"""
|
"""
|
||||||
self.game.state = GameMode.PLAY
|
self.game.state = GameMode.PLAY
|
||||||
# Open and close the inventory
|
# Open and close the inventory
|
||||||
|
@ -472,7 +496,7 @@ class TestGame(unittest.TestCase):
|
||||||
|
|
||||||
def test_talk_to_sunflowers(self) -> None:
|
def test_talk_to_sunflowers(self) -> None:
|
||||||
"""
|
"""
|
||||||
Interact with sunflowers
|
Interacts with sunflowers.
|
||||||
"""
|
"""
|
||||||
self.game.state = GameMode.PLAY
|
self.game.state = GameMode.PLAY
|
||||||
|
|
||||||
|
@ -504,8 +528,8 @@ class TestGame(unittest.TestCase):
|
||||||
self.assertEqual(self.game.state, GameMode.PLAY)
|
self.assertEqual(self.game.state, GameMode.PLAY)
|
||||||
self.assertTrue(self.game.logs.messages)
|
self.assertTrue(self.game.logs.messages)
|
||||||
# Ensure that the message is a good message
|
# Ensure that the message is a good message
|
||||||
self.assertIn(self.game.logs.messages[1][21:],
|
self.assertTrue(any(self.game.logs.messages[1].endswith(msg)
|
||||||
Sunflower.dialogue_option)
|
for msg in Sunflower().dialogue_option))
|
||||||
|
|
||||||
# Test all directions to detect the friendly entity
|
# Test all directions to detect the friendly entity
|
||||||
self.game.player.move(sunflower.y + 1, sunflower.x)
|
self.game.player.move(sunflower.y + 1, sunflower.x)
|
||||||
|
@ -523,7 +547,7 @@ class TestGame(unittest.TestCase):
|
||||||
|
|
||||||
def test_talk_to_merchant(self) -> None:
|
def test_talk_to_merchant(self) -> None:
|
||||||
"""
|
"""
|
||||||
Interact with merchants
|
Interacts with merchants.
|
||||||
"""
|
"""
|
||||||
self.game.state = GameMode.PLAY
|
self.game.state = GameMode.PLAY
|
||||||
|
|
||||||
|
@ -545,21 +569,28 @@ class TestGame(unittest.TestCase):
|
||||||
# Navigate in the menu
|
# 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.game.handle_key_pressed(KeyValues.DOWN)
|
||||||
|
self.game.handle_key_pressed(KeyValues.LEFT)
|
||||||
|
self.assertFalse(self.game.is_in_store_menu)
|
||||||
|
self.game.handle_key_pressed(KeyValues.RIGHT)
|
||||||
|
self.assertTrue(self.game.is_in_store_menu)
|
||||||
self.game.handle_key_pressed(KeyValues.UP)
|
self.game.handle_key_pressed(KeyValues.UP)
|
||||||
self.assertEqual(self.game.store_menu.position, 1)
|
self.assertEqual(self.game.store_menu.position, 1)
|
||||||
|
|
||||||
|
self.game.player.hazel = 0x7ffff42ff
|
||||||
|
|
||||||
# The second item is not a heart
|
# The second item is not a heart
|
||||||
merchant.inventory[1] = Sword()
|
merchant.inventory[1] = sword = Sword()
|
||||||
# Buy the second item by clicking on it
|
# Buy the second item by clicking on it
|
||||||
item = self.game.store_menu.validate()
|
item = self.game.store_menu.validate()
|
||||||
self.assertIn(item, merchant.inventory)
|
self.assertIn(item, merchant.inventory)
|
||||||
self.game.display_actions(DisplayActions.MOUSE, 8, 25)
|
self.game.display_actions(DisplayActions.MOUSE, 7, 25,
|
||||||
self.game.handle_key_pressed(KeyValues.ENTER)
|
curses.BUTTON1_CLICKED)
|
||||||
self.assertIn(item, self.game.player.inventory)
|
self.assertIn(item, self.game.player.inventory)
|
||||||
self.assertNotIn(item, merchant.inventory)
|
self.assertNotIn(item, merchant.inventory)
|
||||||
|
|
||||||
# Buy a heart
|
# Buy a heart
|
||||||
merchant.inventory[1] = Heart()
|
merchant.inventory[1] = Heart()
|
||||||
|
self.game.display_actions(DisplayActions.REFRESH)
|
||||||
item = self.game.store_menu.validate()
|
item = self.game.store_menu.validate()
|
||||||
self.assertIn(item, merchant.inventory)
|
self.assertIn(item, merchant.inventory)
|
||||||
self.assertEqual(item, merchant.inventory[1])
|
self.assertEqual(item, merchant.inventory[1])
|
||||||
|
@ -576,9 +607,169 @@ class TestGame(unittest.TestCase):
|
||||||
self.game.handle_key_pressed(KeyValues.ENTER)
|
self.game.handle_key_pressed(KeyValues.ENTER)
|
||||||
self.assertNotIn(item, self.game.player.inventory)
|
self.assertNotIn(item, self.game.player.inventory)
|
||||||
self.assertIn(item, merchant.inventory)
|
self.assertIn(item, merchant.inventory)
|
||||||
self.assertEqual(self.game.message, _("You do not have enough money"))
|
self.assertEqual(self.game.message,
|
||||||
|
_("The buyer does not have enough money"))
|
||||||
self.game.handle_key_pressed(KeyValues.ENTER)
|
self.game.handle_key_pressed(KeyValues.ENTER)
|
||||||
|
|
||||||
|
# Sell an item
|
||||||
|
self.game.inventory_menu.position = len(self.game.player.inventory) - 1
|
||||||
|
self.game.handle_key_pressed(KeyValues.LEFT)
|
||||||
|
self.assertFalse(self.game.is_in_store_menu)
|
||||||
|
self.assertIn(sword, self.game.player.inventory)
|
||||||
|
self.assertEqual(self.game.inventory_menu.validate(), sword)
|
||||||
|
old_player_money, old_merchant_money = self.game.player.hazel,\
|
||||||
|
merchant.hazel
|
||||||
|
self.game.handle_key_pressed(KeyValues.ENTER)
|
||||||
|
self.assertNotIn(sword, self.game.player.inventory)
|
||||||
|
self.assertIn(sword, merchant.inventory)
|
||||||
|
self.assertEqual(self.game.player.hazel, old_player_money + sword.price)
|
||||||
|
self.assertEqual(merchant.hazel, old_merchant_money - sword.price)
|
||||||
|
|
||||||
# Exit the menu
|
# Exit the menu
|
||||||
self.game.handle_key_pressed(KeyValues.SPACE)
|
self.game.handle_key_pressed(KeyValues.SPACE)
|
||||||
self.assertEqual(self.game.state, GameMode.PLAY)
|
self.assertEqual(self.game.state, GameMode.PLAY)
|
||||||
|
|
||||||
|
def test_equipment(self) -> None:
|
||||||
|
"""
|
||||||
|
Ensure that equipment is working.
|
||||||
|
"""
|
||||||
|
self.game.state = GameMode.INVENTORY
|
||||||
|
|
||||||
|
# sword goes into the main equipment slot
|
||||||
|
sword = Sword()
|
||||||
|
sword.hold(self.game.player)
|
||||||
|
self.game.handle_key_pressed(KeyValues.EQUIP)
|
||||||
|
self.assertEqual(self.game.player.equipped_main, sword)
|
||||||
|
self.assertFalse(self.game.player.inventory)
|
||||||
|
|
||||||
|
# shield goes into the secondary equipment slot
|
||||||
|
shield = Shield()
|
||||||
|
shield.hold(self.game.player)
|
||||||
|
self.game.handle_key_pressed(KeyValues.EQUIP)
|
||||||
|
self.assertEqual(self.game.player.equipped_secondary, shield)
|
||||||
|
self.assertFalse(self.game.player.inventory)
|
||||||
|
|
||||||
|
# helmet goes into the helmet slot
|
||||||
|
helmet = Helmet()
|
||||||
|
helmet.hold(self.game.player)
|
||||||
|
self.game.handle_key_pressed(KeyValues.EQUIP)
|
||||||
|
self.assertEqual(self.game.player.equipped_helmet, helmet)
|
||||||
|
self.assertFalse(self.game.player.inventory)
|
||||||
|
|
||||||
|
# helmet goes into the armor slot
|
||||||
|
chestplate = Chestplate()
|
||||||
|
chestplate.hold(self.game.player)
|
||||||
|
self.game.handle_key_pressed(KeyValues.EQUIP)
|
||||||
|
self.assertEqual(self.game.player.equipped_armor, chestplate)
|
||||||
|
self.assertFalse(self.game.player.inventory)
|
||||||
|
|
||||||
|
# Use bomb
|
||||||
|
bomb = Bomb()
|
||||||
|
bomb.hold(self.game.player)
|
||||||
|
self.game.handle_key_pressed(KeyValues.EQUIP)
|
||||||
|
self.assertEqual(self.game.player.equipped_secondary, bomb)
|
||||||
|
self.assertIn(shield, self.game.player.inventory)
|
||||||
|
self.game.state = GameMode.PLAY
|
||||||
|
self.game.handle_key_pressed(KeyValues.USE)
|
||||||
|
self.assertIsNone(self.game.player.equipped_secondary)
|
||||||
|
self.game.state = GameMode.INVENTORY
|
||||||
|
self.game.handle_key_pressed(KeyValues.EQUIP)
|
||||||
|
self.assertEqual(self.game.player.equipped_secondary, shield)
|
||||||
|
self.assertFalse(self.game.player.inventory)
|
||||||
|
|
||||||
|
# Reequip, which is useless but covers code
|
||||||
|
sword.equip()
|
||||||
|
shield.equip()
|
||||||
|
helmet.equip()
|
||||||
|
chestplate.equip()
|
||||||
|
self.game.save_state()
|
||||||
|
|
||||||
|
# Unequip all
|
||||||
|
sword.unequip()
|
||||||
|
shield.unequip()
|
||||||
|
helmet.unequip()
|
||||||
|
chestplate.unequip()
|
||||||
|
self.assertIsNone(self.game.player.equipped_main)
|
||||||
|
self.assertIsNone(self.game.player.equipped_secondary)
|
||||||
|
self.assertIsNone(self.game.player.equipped_helmet)
|
||||||
|
self.assertIsNone(self.game.player.equipped_armor)
|
||||||
|
self.assertIn(sword, self.game.player.inventory)
|
||||||
|
self.assertIn(shield, self.game.player.inventory)
|
||||||
|
self.assertIn(helmet, self.game.player.inventory)
|
||||||
|
self.assertIn(chestplate, self.game.player.inventory)
|
||||||
|
|
||||||
|
# Test rings
|
||||||
|
self.game.player.inventory.clear()
|
||||||
|
ring = RingCritical()
|
||||||
|
ring.hold(self.game.player)
|
||||||
|
old_critical = self.game.player.critical
|
||||||
|
self.game.handle_key_pressed(KeyValues.EQUIP)
|
||||||
|
self.assertEqual(self.game.player.critical,
|
||||||
|
old_critical + ring.critical)
|
||||||
|
self.game.save_state()
|
||||||
|
ring.unequip()
|
||||||
|
|
||||||
|
def test_monocle(self) -> None:
|
||||||
|
"""
|
||||||
|
The player is wearing a monocle, then the stats are displayed.
|
||||||
|
"""
|
||||||
|
self.game.state = GameMode.PLAY
|
||||||
|
|
||||||
|
monocle = Monocle()
|
||||||
|
monocle.hold(self.game.player)
|
||||||
|
monocle.equip()
|
||||||
|
|
||||||
|
sea_eagle = GiantSeaEagle()
|
||||||
|
self.game.map.add_entity(sea_eagle)
|
||||||
|
sea_eagle.move(2, 6)
|
||||||
|
|
||||||
|
self.game.display_actions(DisplayActions.REFRESH)
|
||||||
|
|
||||||
|
def test_ladders(self) -> None:
|
||||||
|
"""
|
||||||
|
Ensure that the player can climb on ladders.
|
||||||
|
"""
|
||||||
|
self.game.state = GameMode.PLAY
|
||||||
|
|
||||||
|
self.assertEqual(self.game.player.map.floor, 0)
|
||||||
|
self.game.handle_key_pressed(KeyValues.LADDER)
|
||||||
|
self.assertEqual(self.game.player.map.floor, 0)
|
||||||
|
|
||||||
|
# Move nowhere
|
||||||
|
self.game.player.move(10, 10)
|
||||||
|
self.game.handle_key_pressed(KeyValues.LADDER)
|
||||||
|
self.assertEqual(self.game.player.map.floor, 0)
|
||||||
|
|
||||||
|
# Move down
|
||||||
|
self.game.player.move(3, 40) # Move on a ladder
|
||||||
|
self.game.handle_key_pressed(KeyValues.LADDER)
|
||||||
|
self.assertEqual(self.game.map_index, 1)
|
||||||
|
self.assertEqual(self.game.player.map.floor, 1)
|
||||||
|
self.assertEqual(self.game.player.y, 1)
|
||||||
|
self.assertEqual(self.game.player.x, 17)
|
||||||
|
self.game.display_actions(DisplayActions.UPDATE)
|
||||||
|
|
||||||
|
# Move up
|
||||||
|
self.game.handle_key_pressed(KeyValues.LADDER)
|
||||||
|
self.assertEqual(self.game.player.map.floor, 0)
|
||||||
|
self.assertEqual(self.game.player.y, 3)
|
||||||
|
self.assertEqual(self.game.player.x, 40)
|
||||||
|
self.game.display_actions(DisplayActions.UPDATE)
|
||||||
|
|
||||||
|
def test_credits(self) -> None:
|
||||||
|
"""
|
||||||
|
Load credits menu.
|
||||||
|
"""
|
||||||
|
self.game.state = GameMode.MAINMENU
|
||||||
|
|
||||||
|
self.game.display_actions(DisplayActions.MOUSE, 41, 41,
|
||||||
|
curses.BUTTON1_CLICKED)
|
||||||
|
self.assertEqual(self.game.state, GameMode.CREDITS)
|
||||||
|
self.game.display_actions(DisplayActions.MOUSE, 21, 21,
|
||||||
|
curses.BUTTON1_CLICKED)
|
||||||
|
self.game.display_actions(DisplayActions.REFRESH)
|
||||||
|
|
||||||
|
self.game.state = GameMode.CREDITS
|
||||||
|
self.game.handle_key_pressed(KeyValues.ENTER)
|
||||||
|
|
||||||
|
self.assertEqual(self.game.state, GameMode.MAINMENU)
|
||||||
|
|
|
@ -4,14 +4,14 @@
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from squirrelbattle.display.texturepack import TexturePack
|
from squirrelbattle.display.texturepack import TexturePack
|
||||||
from squirrelbattle.interfaces import Map, Tile
|
from squirrelbattle.interfaces import Map, Tile, Slope
|
||||||
from squirrelbattle.resources import ResourceManager
|
from squirrelbattle.resources import ResourceManager
|
||||||
|
|
||||||
|
|
||||||
class TestInterfaces(unittest.TestCase):
|
class TestInterfaces(unittest.TestCase):
|
||||||
def test_map(self) -> None:
|
def test_map(self) -> None:
|
||||||
"""
|
"""
|
||||||
Create a map and check that it is well parsed.
|
Creates a map and checks that it is well parsed.
|
||||||
"""
|
"""
|
||||||
m = Map.load_from_string("0 0\n.#\n#.\n")
|
m = Map.load_from_string("0 0\n.#\n#.\n")
|
||||||
self.assertEqual(m.width, 2)
|
self.assertEqual(m.width, 2)
|
||||||
|
@ -20,7 +20,7 @@ class TestInterfaces(unittest.TestCase):
|
||||||
|
|
||||||
def test_load_map(self) -> None:
|
def test_load_map(self) -> None:
|
||||||
"""
|
"""
|
||||||
Try to load a map from a file.
|
Tries to load a map from a file.
|
||||||
"""
|
"""
|
||||||
m = Map.load(ResourceManager.get_asset_path("example_map.txt"))
|
m = Map.load(ResourceManager.get_asset_path("example_map.txt"))
|
||||||
self.assertEqual(m.width, 52)
|
self.assertEqual(m.width, 52)
|
||||||
|
@ -28,7 +28,7 @@ class TestInterfaces(unittest.TestCase):
|
||||||
|
|
||||||
def test_tiles(self) -> None:
|
def test_tiles(self) -> None:
|
||||||
"""
|
"""
|
||||||
Test some things about tiles.
|
Tests some things about tiles.
|
||||||
"""
|
"""
|
||||||
self.assertFalse(Tile.FLOOR.is_wall())
|
self.assertFalse(Tile.FLOOR.is_wall())
|
||||||
self.assertTrue(Tile.WALL.is_wall())
|
self.assertTrue(Tile.WALL.is_wall())
|
||||||
|
@ -37,3 +37,21 @@ class TestInterfaces(unittest.TestCase):
|
||||||
self.assertFalse(Tile.WALL.can_walk())
|
self.assertFalse(Tile.WALL.can_walk())
|
||||||
self.assertFalse(Tile.EMPTY.can_walk())
|
self.assertFalse(Tile.EMPTY.can_walk())
|
||||||
self.assertRaises(ValueError, Tile.from_ascii_char, 'unknown')
|
self.assertRaises(ValueError, Tile.from_ascii_char, 'unknown')
|
||||||
|
|
||||||
|
def test_slope(self) -> None:
|
||||||
|
"""
|
||||||
|
Test good behaviour of slopes (basically vectors, compared according to
|
||||||
|
the determinant)
|
||||||
|
"""
|
||||||
|
a = Slope(1, 1)
|
||||||
|
b = Slope(0, 1)
|
||||||
|
self.assertTrue(b < a)
|
||||||
|
self.assertTrue(b <= a)
|
||||||
|
self.assertTrue(a <= a)
|
||||||
|
self.assertTrue(a == a)
|
||||||
|
self.assertTrue(a > b)
|
||||||
|
self.assertTrue(a >= b)
|
||||||
|
|
||||||
|
# def test_visibility(self) -> None:
|
||||||
|
# m = Map.load(ResourceManager.get_asset_path("example_map_3.txt"))
|
||||||
|
# m.compute_visibility(1, 1, 50)
|
||||||
|
|
|
@ -12,8 +12,8 @@ class FakePad:
|
||||||
def addstr(self, y: int, x: int, message: str, color: int = 0) -> None:
|
def addstr(self, y: int, x: int, message: str, color: int = 0) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def refresh(self, pminrow: int, pmincol: int, sminrow: int,
|
def noutrefresh(self, pminrow: int, pmincol: int, sminrow: int,
|
||||||
smincol: int, smaxrow: int, smaxcol: int) -> None:
|
smincol: int, smaxrow: int, smaxcol: int) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def erase(self) -> None:
|
def erase(self) -> None:
|
||||||
|
@ -24,3 +24,6 @@ class FakePad:
|
||||||
|
|
||||||
def getmaxyx(self) -> Tuple[int, int]:
|
def getmaxyx(self) -> Tuple[int, int]:
|
||||||
return 42, 42
|
return 42, 42
|
||||||
|
|
||||||
|
def inch(self, y: int, x: int) -> str:
|
||||||
|
return "i"
|
||||||
|
|
|
@ -13,7 +13,7 @@ class TestSettings(unittest.TestCase):
|
||||||
|
|
||||||
def test_settings(self) -> None:
|
def test_settings(self) -> None:
|
||||||
"""
|
"""
|
||||||
Ensure that settings are well loaded.
|
Ensures that settings are well loaded.
|
||||||
"""
|
"""
|
||||||
settings = Settings()
|
settings = Settings()
|
||||||
self.assertEqual(settings.KEY_UP_PRIMARY, 'z')
|
self.assertEqual(settings.KEY_UP_PRIMARY, 'z')
|
||||||
|
|
|
@ -11,7 +11,7 @@ class TestTranslations(unittest.TestCase):
|
||||||
|
|
||||||
def test_main_menu_translation(self) -> None:
|
def test_main_menu_translation(self) -> None:
|
||||||
"""
|
"""
|
||||||
Ensure that the main menu is translated.
|
Ensures that the main menu is translated.
|
||||||
"""
|
"""
|
||||||
self.assertEqual(_("New game"), "Nouvelle partie")
|
self.assertEqual(_("New game"), "Nouvelle partie")
|
||||||
self.assertEqual(_("Resume"), "Continuer")
|
self.assertEqual(_("Resume"), "Continuer")
|
||||||
|
@ -22,7 +22,7 @@ class TestTranslations(unittest.TestCase):
|
||||||
|
|
||||||
def test_settings_menu_translation(self) -> None:
|
def test_settings_menu_translation(self) -> None:
|
||||||
"""
|
"""
|
||||||
Ensure that the settings menu is translated.
|
Ensures that the settings menu is translated.
|
||||||
"""
|
"""
|
||||||
self.assertEqual(_("Main key to move up"),
|
self.assertEqual(_("Main key to move up"),
|
||||||
"Touche principale pour aller vers le haut")
|
"Touche principale pour aller vers le haut")
|
||||||
|
@ -52,18 +52,33 @@ class TestTranslations(unittest.TestCase):
|
||||||
"Touche pour jeter un objet de l'inventaire")
|
"Touche pour jeter un objet de l'inventaire")
|
||||||
self.assertEqual(_("Key used to talk to a friendly entity"),
|
self.assertEqual(_("Key used to talk to a friendly entity"),
|
||||||
"Touche pour parler à une entité pacifique")
|
"Touche pour parler à une entité pacifique")
|
||||||
|
self.assertEqual(_("Key used to wait"), "Touche pour attendre")
|
||||||
|
self.assertEqual(_("Key used to use ladders"),
|
||||||
|
"Touche pour utiliser les échelles")
|
||||||
self.assertEqual(_("Texture pack"), "Pack de textures")
|
self.assertEqual(_("Texture pack"), "Pack de textures")
|
||||||
self.assertEqual(_("Language"), "Langue")
|
self.assertEqual(_("Language"), "Langue")
|
||||||
|
|
||||||
def test_entities_translation(self) -> None:
|
def test_entities_translation(self) -> None:
|
||||||
self.assertEqual(_("player"), "joueur")
|
self.assertEqual(_("player"), "joueur")
|
||||||
|
|
||||||
self.assertEqual(_("tiger"), "tigre")
|
|
||||||
self.assertEqual(_("hedgehog"), "hérisson")
|
self.assertEqual(_("hedgehog"), "hérisson")
|
||||||
|
self.assertEqual(_("merchant"), "marchand")
|
||||||
self.assertEqual(_("rabbit"), "lapin")
|
self.assertEqual(_("rabbit"), "lapin")
|
||||||
|
self.assertEqual(_("sunflower"), "tournesol")
|
||||||
self.assertEqual(_("teddy bear"), "nounours")
|
self.assertEqual(_("teddy bear"), "nounours")
|
||||||
|
self.assertEqual(_("tiger"), "tigre")
|
||||||
|
self.assertEqual(_("eagle"), "pygargue")
|
||||||
|
|
||||||
self.assertEqual(_("body snatch potion"), "potion d'arrachage de corps")
|
self.assertEqual(_("body snatch potion"), "potion d'arrachage de corps")
|
||||||
self.assertEqual(_("bomb"), "bombe")
|
self.assertEqual(_("bomb"), "bombe")
|
||||||
|
self.assertEqual(_("explosion"), "explosion")
|
||||||
self.assertEqual(_("heart"), "cœur")
|
self.assertEqual(_("heart"), "cœur")
|
||||||
self.assertEqual(_("sword"), "épée")
|
self.assertEqual(_("sword"), "épée")
|
||||||
|
self.assertEqual(_("helmet"), "casque")
|
||||||
|
self.assertEqual(_("chestplate"), "plastron")
|
||||||
|
self.assertEqual(_("shield"), "bouclier")
|
||||||
|
self.assertEqual(_("ring of critical damage"),
|
||||||
|
"anneau de coup critique")
|
||||||
|
self.assertEqual(_("ring of more experience"),
|
||||||
|
"anneau de plus d'expérience")
|
||||||
|
self.assertEqual(_("monocle"), "monocle")
|
||||||
|
|
|
@ -13,7 +13,7 @@ class Translator:
|
||||||
"""
|
"""
|
||||||
This module uses gettext to translate strings.
|
This module uses gettext to translate strings.
|
||||||
Translator.setlocale defines the language of the strings,
|
Translator.setlocale defines the language of the strings,
|
||||||
then gettext() translates the message.
|
then gettext() translates the messages.
|
||||||
"""
|
"""
|
||||||
SUPPORTED_LOCALES: List[str] = ["de", "en", "es", "fr"]
|
SUPPORTED_LOCALES: List[str] = ["de", "en", "es", "fr"]
|
||||||
locale: str = "en"
|
locale: str = "en"
|
||||||
|
@ -22,7 +22,7 @@ class Translator:
|
||||||
@classmethod
|
@classmethod
|
||||||
def refresh_translations(cls) -> None:
|
def refresh_translations(cls) -> None:
|
||||||
"""
|
"""
|
||||||
Load compiled translations.
|
Loads compiled translations.
|
||||||
"""
|
"""
|
||||||
for language in cls.SUPPORTED_LOCALES:
|
for language in cls.SUPPORTED_LOCALES:
|
||||||
rep = Path(__file__).parent / "locale" / language / "LC_MESSAGES"
|
rep = Path(__file__).parent / "locale" / language / "LC_MESSAGES"
|
||||||
|
@ -37,7 +37,7 @@ class Translator:
|
||||||
@classmethod
|
@classmethod
|
||||||
def setlocale(cls, lang: str) -> None:
|
def setlocale(cls, lang: str) -> None:
|
||||||
"""
|
"""
|
||||||
Define the language used to translate the game.
|
Defines the language used to translate the game.
|
||||||
The language must be supported, otherwise nothing is done.
|
The language must be supported, otherwise nothing is done.
|
||||||
"""
|
"""
|
||||||
lang = lang[:2]
|
lang = lang[:2]
|
||||||
|
@ -51,7 +51,7 @@ class Translator:
|
||||||
@classmethod
|
@classmethod
|
||||||
def makemessages(cls) -> None: # pragma: no cover
|
def makemessages(cls) -> None: # pragma: no cover
|
||||||
"""
|
"""
|
||||||
Analyse all strings in the project and extract them.
|
Analyses all strings in the project and extracts them.
|
||||||
"""
|
"""
|
||||||
for language in cls.SUPPORTED_LOCALES:
|
for language in cls.SUPPORTED_LOCALES:
|
||||||
if language == "en":
|
if language == "en":
|
||||||
|
@ -83,7 +83,7 @@ class Translator:
|
||||||
@classmethod
|
@classmethod
|
||||||
def compilemessages(cls) -> None:
|
def compilemessages(cls) -> None:
|
||||||
"""
|
"""
|
||||||
Compile translation messages from source files.
|
Compiles translation messages from source files.
|
||||||
"""
|
"""
|
||||||
for language in cls.SUPPORTED_LOCALES:
|
for language in cls.SUPPORTED_LOCALES:
|
||||||
if language == "en":
|
if language == "en":
|
||||||
|
@ -99,7 +99,7 @@ class Translator:
|
||||||
|
|
||||||
def gettext(message: str) -> str:
|
def gettext(message: str) -> str:
|
||||||
"""
|
"""
|
||||||
Translate a message.
|
Translates a message.
|
||||||
"""
|
"""
|
||||||
return Translator.get_translator().gettext(message)
|
return Translator.get_translator().gettext(message)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue