Merge branch 'EvenmoreDoc' into 'moredocs'

# Conflicts:
#   docs/entities/items.rst
This commit is contained in:
2021-01-10 18:49:25 +01:00
23 changed files with 486 additions and 301 deletions

View File

@ -1,97 +0,0 @@
# Copyright (C) 2020-2021 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

View File

@ -290,3 +290,29 @@ class Box(Display):
self.refresh_pad(self.pad, 0, 0, self.y, self.x,
self.y + self.height - 1, self.x + self.width - 1)
class MessageDisplay(Display):
"""
A class to handle the display of popup messages.
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.box = Box(fg_border_color=curses.COLOR_RED, *args, **kwargs)
self.message = ""
self.pad = self.newpad(1, 1)
def update(self, game: Game) -> None:
self.message = game.message
def display(self) -> None:
self.box.refresh(self.y - 1, self.x - 2,
self.height + 2, self.width + 4)
self.box.display()
self.pad.erase()
self.addstr(self.pad, 0, 0, self.message, bold=True)
self.refresh_pad(self.pad, 0, 0, self.y, self.x,
self.height + self.y - 1,
self.width + self.x - 1)

View File

@ -4,14 +4,11 @@
import curses
from typing import Any, List
from .creditsdisplay import CreditsDisplay
from .display import Display, HorizontalSplit, VerticalSplit
from .logsdisplay import LogsDisplay
from .mapdisplay import MapDisplay
from .menudisplay import ChestInventoryDisplay, MainMenuDisplay, \
PlayerInventoryDisplay, SettingsMenuDisplay, StoreInventoryDisplay
from .messagedisplay import MessageDisplay
from .statsdisplay import StatsDisplay
from .display import Display, HorizontalSplit, MessageDisplay, VerticalSplit
from .gamedisplay import LogsDisplay, MapDisplay, StatsDisplay
from .menudisplay import ChestInventoryDisplay, CreditsDisplay, \
MainMenuDisplay, PlayerInventoryDisplay, \
SettingsMenuDisplay, StoreInventoryDisplay
from .texturepack import TexturePack
from ..enums import DisplayActions
from ..game import Game, GameMode

View File

@ -7,10 +7,116 @@ from .display import Display
from ..entities.items import Monocle
from ..entities.player import Player
from ..game import Game
from ..interfaces import FightingEntity
from ..interfaces import FightingEntity, Logs, Map
from ..translations import gettext as _
class LogsDisplay(Display):
"""
A class to handle the display of the logs.
"""
logs: Logs
def __init__(self, *args) -> None:
super().__init__(*args)
self.pad = self.newpad(self.rows, self.cols)
def update(self, game: Game) -> None:
self.logs = game.logs
def display(self) -> None:
messages = self.logs.messages[-self.height:]
messages = messages[::-1]
self.pad.erase()
for i in range(min(self.height, len(messages))):
self.addstr(self.pad, self.height - i - 1, self.x,
messages[i][:self.width])
self.refresh_pad(self.pad, 0, 0, self.y, self.x,
self.y + self.height - 1, self.x + self.width - 1)
class MapDisplay(Display):
"""
A class to handle the display of the map.
"""
map: Map
def __init__(self, *args):
super().__init__(*args)
def update(self, game: Game) -> None:
self.map = game.map
self.pad = self.newpad(self.map.height,
self.pack.tile_width * self.map.width + 1)
def update_pad(self) -> None:
for j in range(len(self.map.tiles)):
for i in range(len(self.map.tiles[j])):
if not self.map.seen_tiles[j][i]:
continue
fg, bg = self.map.tiles[j][i].visible_color(self.pack) if \
self.map.visibility[j][i] else \
self.map.tiles[j][i].hidden_color(self.pack)
self.addstr(self.pad, j, self.pack.tile_width * i,
self.map.tiles[j][i].char(self.pack), fg, bg)
for e in self.map.entities:
if self.map.visibility[e.y][e.x]:
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 debug purposes
# from squirrelbattle.entities.player import Player
# players = [ p for p in self.map.entities if isinstance(p,Player) ]
# player = players[0] if len(players) > 0 else None
# if player:
# for x in range(self.map.width):
# for y in range(self.map.height):
# if (y,x) in player.paths:
# deltay, deltax = (y - player.paths[(y, x)][0],
# x - player.paths[(y, x)][1])
# if (deltay, deltax) == (-1, 0):
# character = '↓'
# elif (deltay, deltax) == (1, 0):
# character = '↑'
# elif (deltay, deltax) == (0, -1):
# character = '→'
# else:
# character = '←'
# self.addstr(self.pad, y, self.pack.tile_width * x,
# character, self.pack.tile_fg_color,
# self.pack.tile_bg_color)
def display(self) -> None:
y, x = self.map.currenty, self.pack.tile_width * self.map.currentx
deltay, deltax = (self.height // 2) + 1, (self.width // 2) + 1
pminrow, pmincol = y - deltay, x - deltax
sminrow, smincol = max(-pminrow, 0), max(-pmincol, 0)
deltay, deltax = self.height - deltay, self.width - deltax
smaxrow = self.map.height - (y + deltay) + self.height - 1
smaxrow = min(smaxrow, self.height - 1)
smaxcol = self.pack.tile_width * self.map.width - \
(x + deltax) + self.width - 1
# Wrap perfectly the map according to the width of the tiles
pmincol = self.pack.tile_width * (pmincol // self.pack.tile_width)
smincol = self.pack.tile_width * (smincol // self.pack.tile_width)
smaxcol = self.pack.tile_width \
* (smaxcol // self.pack.tile_width + 1) - 1
smaxcol = min(smaxcol, self.width - 1)
pminrow = max(0, min(self.map.height, pminrow))
pmincol = max(0, min(self.pack.tile_width * self.map.width, pmincol))
self.pad.erase()
self.update_pad()
self.refresh_pad(self.pad, pminrow, pmincol, sminrow, smincol, smaxrow,
smaxcol)
class StatsDisplay(Display):
"""
A class to handle the display of the stats of the player.

View File

@ -1,31 +0,0 @@
# Copyright (C) 2020-2021 by ÿnérant, eichhornchen, nicomarg, charlse
# SPDX-License-Identifier: GPL-3.0-or-later
from squirrelbattle.display.display import Display
from squirrelbattle.game import Game
from squirrelbattle.interfaces import Logs
class LogsDisplay(Display):
"""
A class to handle the display of the logs.
"""
logs: Logs
def __init__(self, *args) -> None:
super().__init__(*args)
self.pad = self.newpad(self.rows, self.cols)
def update(self, game: Game) -> None:
self.logs = game.logs
def display(self) -> None:
messages = self.logs.messages[-self.height:]
messages = messages[::-1]
self.pad.erase()
for i in range(min(self.height, len(messages))):
self.addstr(self.pad, self.height - i - 1, self.x,
messages[i][:self.width])
self.refresh_pad(self.pad, 0, 0, self.y, self.x,
self.y + self.height - 1, self.x + self.width - 1)

View File

@ -1,87 +0,0 @@
# Copyright (C) 2020-2021 by ÿnérant, eichhornchen, nicomarg, charlse
# SPDX-License-Identifier: GPL-3.0-or-later
from .display import Display
from ..game import Game
from ..interfaces import Map
class MapDisplay(Display):
"""
A class to handle the display of the map.
"""
map: Map
def __init__(self, *args):
super().__init__(*args)
def update(self, game: Game) -> None:
self.map = game.map
self.pad = self.newpad(self.map.height,
self.pack.tile_width * self.map.width + 1)
def update_pad(self) -> None:
for j in range(len(self.map.tiles)):
for i in range(len(self.map.tiles[j])):
if not self.map.seen_tiles[j][i]:
continue
fg, bg = self.map.tiles[j][i].visible_color(self.pack) if \
self.map.visibility[j][i] else \
self.map.tiles[j][i].hidden_color(self.pack)
self.addstr(self.pad, j, self.pack.tile_width * i,
self.map.tiles[j][i].char(self.pack), fg, bg)
for e in self.map.entities:
if self.map.visibility[e.y][e.x]:
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 debug purposes
# from squirrelbattle.entities.player import Player
# players = [ p for p in self.map.entities if isinstance(p,Player) ]
# player = players[0] if len(players) > 0 else None
# if player:
# for x in range(self.map.width):
# for y in range(self.map.height):
# if (y,x) in player.paths:
# deltay, deltax = (y - player.paths[(y, x)][0],
# x - player.paths[(y, x)][1])
# if (deltay, deltax) == (-1, 0):
# character = '↓'
# elif (deltay, deltax) == (1, 0):
# character = '↑'
# elif (deltay, deltax) == (0, -1):
# character = '→'
# else:
# character = '←'
# self.addstr(self.pad, y, self.pack.tile_width * x,
# character, self.pack.tile_fg_color,
# self.pack.tile_bg_color)
def display(self) -> None:
y, x = self.map.currenty, self.pack.tile_width * self.map.currentx
deltay, deltax = (self.height // 2) + 1, (self.width // 2) + 1
pminrow, pmincol = y - deltay, x - deltax
sminrow, smincol = max(-pminrow, 0), max(-pmincol, 0)
deltay, deltax = self.height - deltay, self.width - deltax
smaxrow = self.map.height - (y + deltay) + self.height - 1
smaxrow = min(smaxrow, self.height - 1)
smaxcol = self.pack.tile_width * self.map.width - \
(x + deltax) + self.width - 1
# Wrap perfectly the map according to the width of the tiles
pmincol = self.pack.tile_width * (pmincol // self.pack.tile_width)
smincol = self.pack.tile_width * (smincol // self.pack.tile_width)
smaxcol = self.pack.tile_width \
* (smaxcol // self.pack.tile_width + 1) - 1
smaxcol = min(smaxcol, self.width - 1)
pminrow = max(0, min(self.map.height, pminrow))
pmincol = max(0, min(self.pack.tile_width * self.map.width, pmincol))
self.pad.erase()
self.update_pad()
self.refresh_pad(self.pad, pminrow, pmincol, sminrow, smincol, smaxrow,
smaxcol)

View File

@ -104,7 +104,8 @@ class MainMenuDisplay(Display):
super().__init__(*args)
self.menu = menu
with open(ResourceManager.get_asset_path("ascii_art.txt"), "r") as file:
with open(ResourceManager.get_asset_path("ascii_art-title.txt"), "r")\
as file:
self.title = file.read().split("\n")
self.pad = self.newpad(max(self.rows, len(self.title) + 30),
@ -281,3 +282,91 @@ class ChestInventoryDisplay(MenuDisplay):
self.menu.position = max(0, min(len(self.menu.values) - 1, y - 2))
game.is_in_chest_menu = True
game.handle_key_pressed(KeyValues.ENTER)
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

View File

@ -1,32 +0,0 @@
# Copyright (C) 2020-2021 by ÿnérant, eichhornchen, nicomarg, charlse
# SPDX-License-Identifier: GPL-3.0-or-later
import curses
from squirrelbattle.display.display import Box, Display
from squirrelbattle.game import Game
class MessageDisplay(Display):
"""
A class to handle the display of popup messages.
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.box = Box(fg_border_color=curses.COLOR_RED, *args, **kwargs)
self.message = ""
self.pad = self.newpad(1, 1)
def update(self, game: Game) -> None:
self.message = game.message
def display(self) -> None:
self.box.refresh(self.y - 1, self.x - 2,
self.height + 2, self.width + 4)
self.box.display()
self.pad.erase()
self.addstr(self.pad, 0, 0, self.message, bold=True)
self.refresh_pad(self.pad, 0, 0, self.y, self.x,
self.height + self.y - 1,
self.width + self.x - 1)

View File

@ -3,7 +3,7 @@
from random import choice, shuffle
from .items import Item
from .items import Bomb, Item
from .monsters import Monster
from .player import Player
from ..interfaces import Entity, FightingEntity, FriendlyEntity, \
@ -48,11 +48,14 @@ class Chest(InventoryHolder, FriendlyEntity):
"""
A class of chest inanimate entities which contain objects.
"""
annihilated: bool
def __init__(self, name: str = "chest", inventory: list = None,
hazel: int = 0, *args, **kwargs):
super().__init__(name=name, *args, **kwargs)
self.hazel = hazel
self.inventory = self.translate_inventory(inventory or [])
self.annihilated = False
if not self.inventory:
for i in range(3):
self.inventory.append(choice(Item.get_all_items())())
@ -68,6 +71,10 @@ class Chest(InventoryHolder, FriendlyEntity):
"""
A chest is not living, it can not take damage
"""
if isinstance(attacker, Bomb):
self.die()
self.annihilated = True
return _("The chest exploded")
return _("It's not really effective")
@property
@ -75,14 +82,14 @@ class Chest(InventoryHolder, FriendlyEntity):
"""
Chest can not die
"""
return False
return self.annihilated
class Sunflower(FriendlyEntity):
"""
A friendly sunflower.
"""
def __init__(self, maxhealth: int = 15,
def __init__(self, maxhealth: int = 20,
*args, **kwargs) -> None:
super().__init__(name="sunflower", maxhealth=maxhealth, *args, **kwargs)
@ -162,6 +169,6 @@ class Trumpet(Familiar):
A class of familiars.
"""
def __init__(self, name: str = "trumpet", strength: int = 3,
maxhealth: int = 20, *args, **kwargs) -> None:
maxhealth: int = 30, *args, **kwargs) -> None:
super().__init__(name=name, strength=strength,
maxhealth=maxhealth, *args, **kwargs)

View File

@ -498,7 +498,7 @@ class ScrollofDamage(Item):
class ScrollofWeakening(Item):
"""
A scroll that, when used, reduces the damage of the ennemies for 3 turn.
A scroll that, when used, reduces the damage of the ennemies for 3 turns.
"""
def __init__(self, name: str = "scroll_of_weakening", price: int = 13,
*args, **kwargs):
@ -613,7 +613,7 @@ class FireBallStaff(LongRangeWeapon):
@property
def stat(self) -> str:
"""
Here it is dexterity
Here it is intelligence
"""
return "intelligence"

View File

@ -76,8 +76,8 @@ class Tiger(Monster):
"""
A tiger monster.
"""
def __init__(self, name: str = "tiger", strength: int = 2,
maxhealth: int = 20, *args, **kwargs) -> None:
def __init__(self, name: str = "tiger", strength: int = 5,
maxhealth: int = 30, *args, **kwargs) -> None:
super().__init__(name=name, strength=strength,
maxhealth=maxhealth, *args, **kwargs)
@ -97,7 +97,7 @@ class Rabbit(Monster):
A rabbit monster.
"""
def __init__(self, name: str = "rabbit", strength: int = 1,
maxhealth: int = 15, critical: int = 30,
maxhealth: int = 20, critical: int = 30,
*args, **kwargs) -> None:
super().__init__(name=name, strength=strength,
maxhealth=maxhealth, critical=critical,

View File

@ -1,11 +1,13 @@
# Copyright (C) 2020-2021 by ÿnérant, eichhornchen, nicomarg, charlse
# SPDX-License-Identifier: GPL-3.0-or-later
from math import log
from random import randint
from typing import Dict, Optional, Tuple
from .items import Item
from ..interfaces import FightingEntity, InventoryHolder
from ..translations import gettext as _
class Player(InventoryHolder, FightingEntity):
@ -61,6 +63,31 @@ class Player(InventoryHolder, FightingEntity):
self.recalculate_paths()
self.map.compute_visibility(self.y, self.x, self.vision)
def dance(self) -> None:
"""
Dancing has a certain probability or making ennemies unable
to fight for 3 turns. That probability depends on the player's
charisma.
"""
diceroll = randint(1, 10)
found = False
if diceroll <= self.charisma:
for entity in self.map.entities:
if entity.is_fighting_entity() and not entity == self \
and entity.distance(self) <= 3:
found = True
entity.confused = 1
entity.effects.append(["confused", 1, 3])
if found:
self.map.logs.add_message(_(
"It worked! Nearby ennemies will be confused for 3 turns."))
else:
self.map.logs.add_message(_(
"It worked, but there is no one nearby..."))
else:
self.map.logs.add_message(
_("The dance was not effective..."))
def level_up(self) -> None:
"""
Add as many levels as possible to the player.
@ -69,9 +96,19 @@ class Player(InventoryHolder, FightingEntity):
self.level += 1
self.current_xp -= self.max_xp
self.max_xp = self.level * 10
self.maxhealth += int(2 * log(self.level) / log(2))
self.health = self.maxhealth
self.strength = self.strength + 1
# TODO Remove it, that's only fun
if self.level % 3 == 0:
self.dexterity += 1
self.constitution += 1
if self.level % 4 == 0:
self.intelligence += 1
if self.level % 6 == 0:
self.charisma += 1
if self.level % 10 == 0 and self.critical < 95:
self.critical += (100 - self.charisma) // 30
# TODO Remove it, that's only for fun
self.map.spawn_random_entities(randint(3 * self.level,
10 * self.level))

View File

@ -50,6 +50,7 @@ class KeyValues(Enum):
WAIT = auto()
LADDER = auto()
LAUNCH = auto()
DANCE = auto()
@staticmethod
def translate_key(key: str, settings: Settings) -> Optional["KeyValues"]:
@ -88,4 +89,6 @@ class KeyValues(Enum):
return KeyValues.LADDER
elif key == settings.KEY_LAUNCH:
return KeyValues.LAUNCH
elif key == settings.KEY_DANCE:
return KeyValues.DANCE
return None

View File

@ -179,6 +179,9 @@ class Game:
self.map.tick(self.player)
elif key == KeyValues.LADDER:
self.handle_ladder()
elif key == KeyValues.DANCE:
self.player.dance()
self.map.tick(self.player)
def handle_ladder(self) -> None:
"""

View File

@ -628,8 +628,9 @@ class Entity:
Rabbit, TeddyBear, GiantSeaEagle
from squirrelbattle.entities.friendly import Merchant, Sunflower, \
Trumpet, Chest
return [BodySnatchPotion, Bomb, Heart, Hedgehog, Rabbit, TeddyBear,
Sunflower, Tiger, Merchant, GiantSeaEagle, Trumpet, Chest]
return [BodySnatchPotion, Bomb, Chest, GiantSeaEagle, Heart,
Hedgehog, Merchant, Rabbit, Sunflower, TeddyBear, Tiger,
Trumpet]
@staticmethod
def get_weights() -> list:
@ -637,7 +638,7 @@ class Entity:
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, 3, 1, 2, 4]
return [30, 80, 50, 1, 100, 100, 60, 70, 70, 20, 40, 40]
@staticmethod
def get_all_entity_classes_in_a_dict() -> dict:
@ -706,6 +707,7 @@ class FightingEntity(Entity):
constitution: int
level: int
critical: int
confused: int # Seulement 0 ou 1
def __init__(self, maxhealth: int = 0, health: Optional[int] = None,
strength: int = 0, intelligence: int = 0, charisma: int = 0,
@ -722,6 +724,7 @@ class FightingEntity(Entity):
self.level = level
self.critical = critical
self.effects = [] # effects = temporary buff or weakening of the stats.
self.confused = 0
@property
def dead(self) -> bool:
@ -749,6 +752,10 @@ class FightingEntity(Entity):
The entity deals damage to the opponent
based on their respective stats.
"""
if self.confused:
return _("{name} is confused, it can not hit {opponent}.")\
.format(name=_(self.translated_name.capitalize()),
opponent=_(opponent.translated_name))
diceroll = randint(1, 100)
damage = max(0, self.strength)
string = " "
@ -765,7 +772,7 @@ class FightingEntity(Entity):
The entity takes damage from the attacker
based on their respective stats.
"""
damage = max(0, amount - self.constitution)
damage = max(1, amount - self.constitution)
self.health -= damage
if self.health <= 0:
self.die()

View File

@ -36,6 +36,7 @@ class Settings:
self.KEY_WAIT = ['w', 'Key used to wait']
self.KEY_LADDER = ['<', 'Key used to use ladders']
self.KEY_LAUNCH = ['l', 'Key used to use a bow']
self.KEY_DANCE = ['y', 'Key used to dance']
self.TEXTURE_PACK = ['ascii', 'Texture pack']
self.LOCALE = [locale.getlocale()[0][:2], 'Language']

View File

@ -4,7 +4,7 @@
import random
import unittest
from ..entities.friendly import Trumpet
from ..entities.friendly import Chest, Trumpet
from ..entities.items import BodySnatchPotion, Bomb, Explosion, Heart, Item
from ..entities.monsters import GiantSeaEagle, Hedgehog, Rabbit, \
TeddyBear, Tiger
@ -45,18 +45,19 @@ class TestEntities(unittest.TestCase):
"""
entity = Tiger()
self.map.add_entity(entity)
self.assertEqual(entity.maxhealth, 20)
self.assertEqual(entity.maxhealth, 30)
self.assertEqual(entity.maxhealth, entity.health)
self.assertEqual(entity.strength, 2)
for _ in range(9):
self.assertEqual(entity.strength, 5)
for _ in range(5):
self.assertEqual(entity.hit(entity),
"Tiger hits tiger. Tiger takes 2 damage.")
"Tiger hits tiger. Tiger takes 5 damage.")
self.assertFalse(entity.dead)
self.assertEqual(entity.hit(entity), "Tiger hits tiger. "
+ "Tiger takes 2 damage. Tiger dies.")
+ "Tiger takes 5 damage. Tiger dies.")
self.assertTrue(entity.dead)
entity = Rabbit()
entity.health = 15
entity.critical = 0
self.map.add_entity(entity)
entity.move(15, 44)
@ -94,7 +95,20 @@ class TestEntities(unittest.TestCase):
self.assertTrue(entity.dead)
self.assertGreaterEqual(self.player.current_xp, 3)
# Test the familiars
# Test that a chest is destroyed by a bomb
bomb = Bomb()
bomb.owner = self.player
bomb.move(3, 6)
self.map.add_entity(bomb)
chest = Chest()
chest.move(4, 6)
self.map.add_entity(chest)
bomb.exploding = True
for _ in range(5):
self.map.tick(self.player)
self.assertTrue(chest.annihilated)
def test_familiar(self) -> None:
fam = Trumpet()
entity = Rabbit()
self.map.add_entity(entity)
@ -266,6 +280,15 @@ class TestEntities(unittest.TestCase):
player_state = player.save_state()
self.assertEqual(player_state["current_xp"], 10)
player = Player()
player.map = self.map
player.add_xp(700)
for _ in range(13):
player.level_up()
self.assertEqual(player.level, 12)
self.assertEqual(player.critical, 5 + 95 // 30)
self.assertEqual(player.charisma, 3)
def test_critical_hit(self) -> None:
"""
Ensure that critical hits are working.

View File

@ -160,6 +160,9 @@ class TestGame(unittest.TestCase):
KeyValues.SPACE)
self.assertEqual(KeyValues.translate_key('plop', self.game.settings),
None)
self.assertEqual(KeyValues.translate_key(
self.game.settings.KEY_DANCE, self.game.settings),
KeyValues.DANCE)
def test_key_press(self) -> None:
"""
@ -249,6 +252,30 @@ class TestGame(unittest.TestCase):
self.game.handle_key_pressed(KeyValues.WAIT)
self.assertNotIn(explosion, self.game.map.entities)
rabbit = Rabbit()
self.game.map.add_entity(rabbit)
self.game.player.move(1, 6)
rabbit.move(3, 6)
self.game.player.charisma = 11
self.game.handle_key_pressed(KeyValues.DANCE)
self.assertEqual(rabbit.confused, 1)
string = rabbit.hit(self.game.player)
self.assertEqual(string,
"{name} is confused, it can not hit {opponent}."
.format(name=_(rabbit.translated_name.capitalize()
), opponent=_(
self.game.player.translated_name
)))
rabbit.confused = 0
self.game.player.charisma = 0
self.game.handle_key_pressed(KeyValues.DANCE)
self.assertEqual(rabbit.confused, 0)
rabbit.die()
self.game.player.charisma = 11
self.game.handle_key_pressed(KeyValues.DANCE)
self.game.player.charisma = 1
self.game.handle_key_pressed(KeyValues.SPACE)
self.assertEqual(self.game.state, GameMode.MAINMENU)
@ -350,7 +377,7 @@ class TestGame(unittest.TestCase):
self.assertEqual(self.game.settings.KEY_LEFT_PRIMARY, 'a')
# Navigate to "texture pack"
for ignored in range(13):
for ignored in range(14):
self.game.handle_key_pressed(KeyValues.DOWN)
# Change texture pack