Merge branch 'equipment' into doc

This commit is contained in:
Eichhornchen 2021-01-06 14:49:09 +01:00
commit 2dc178d67c
8 changed files with 341 additions and 40 deletions

View File

@ -23,15 +23,16 @@ class StatsDisplay(Display):
self.player = game.player
def update_pad(self) -> None:
string2 = "Player -- LVL {}\nEXP {}/{}\nHP {}/{}"\
string2 = _("player").capitalize() + " -- LVL {}\nEXP {}/{}\nHP {}/{}"\
.format(self.player.level, self.player.current_xp,
self.player.max_xp, self.player.health,
self.player.maxhealth)
self.addstr(self.pad, 0, 0, string2)
string3 = "STR {}\nINT {}\nCHR {}\nDEX {}\nCON {}"\
string3 = "STR {}\nINT {}\nCHR {}\nDEX {}\nCON {}\nCRI {}%"\
.format(self.player.strength,
self.player.intelligence, self.player.charisma,
self.player.dexterity, self.player.constitution)
self.player.dexterity, self.player.constitution,\
self.player.critical)
self.addstr(self.pad, 3, 0, string3)
inventory_str = _("Inventory:") + " "
@ -47,13 +48,30 @@ class StatsDisplay(Display):
if count > 1:
inventory_str += f"x{count} "
printed_items.append(item)
self.addstr(self.pad, 8, 0, inventory_str)
self.addstr(self.pad, 9, 0, inventory_str)
self.addstr(self.pad, 9, 0, f"{self.pack.HAZELNUT} "
f"x{self.player.hazel}")
if self.player.equipped_main:
self.addstr(self.pad, 10, 0,
_("Equipped main:") + " "
f"{self.pack[self.player.equipped_main.name.upper()]}")
if self.player.equipped_secondary:
self.addstr(self.pad, 11, 0,
_("Equipped secondary:") + " "
f"{self.pack[self.player.equipped_secondary.name.upper()]}")
if self.player.equipped_armor:
self.addstr(self.pad, 12, 0,
_("Equipped chestplate:") + " "
f"{self.pack[self.player.equipped_armor.name.upper()]}")
if self.player.equipped_helmet:
self.addstr(self.pad, 13, 0,
_("Equipped helmet:") + " "
f"{self.pack[self.player.equipped_helmet.name.upper()]}")
self.addstr(self.pad, 14, 0, f"{self.pack.HAZELNUT} "
f"x{self.player.hazel}")
if self.player.dead:
self.addstr(self.pad, 11, 0, _("YOU ARE DEAD"), curses.COLOR_RED,
self.addstr(self.pad, 15, 0, _("YOU ARE DEAD"), curses.COLOR_RED,
bold=True, blink=True, standout=True)
def display(self) -> None:

View File

@ -34,6 +34,12 @@ class TexturePack:
TIGER: str
TRUMPET: str
WALL: str
EAGLE: str
SHIELD: str
CHESTPLATE: str
HELMET: str
RING_OF_MORE_EXPERIENCE: str
RING_OF_CRITICAL_DAMAGE: str
ASCII_PACK: "TexturePack"
SQUIRREL_PACK: "TexturePack"
@ -64,7 +70,7 @@ TexturePack.ASCII_PACK = TexturePack(
entity_bg_color=curses.COLOR_BLACK,
BODY_SNATCH_POTION='S',
BOMB='o',
BOMB='ç',
EMPTY=' ',
EXPLOSION='%',
FLOOR='.',
@ -74,12 +80,18 @@ TexturePack.ASCII_PACK = TexturePack(
MERCHANT='M',
PLAYER='@',
RABBIT='Y',
SHIELD='D',
SUNFLOWER='I',
SWORD='\u2020',
TEDDY_BEAR='8',
TIGER='n',
TRUMPET='/',
WALL='#',
EAGLE='µ',
CHESTPLATE='(',
HELMET='0',
RING_OF_MORE_EXPERIENCE='o',
RING_OF_CRITICAL_DAMAGE='o',
)
TexturePack.SQUIRREL_PACK = TexturePack(
@ -101,10 +113,16 @@ TexturePack.SQUIRREL_PACK = TexturePack(
PLAYER='🐿️ ',
MERCHANT='🦜',
RABBIT='🐇',
SHIELD='🛡️ ',
SUNFLOWER='🌻',
SWORD='🗡️ ',
TEDDY_BEAR='🧸',
TIGER='🐅',
TRUMPET='🎺',
WALL='🧱',
EAGLE='🦅',
CHESTPLATE='🦺',
HELMET='⛑️',
RING_OF_MORE_EXPERIENCE='💍',
RING_OF_CRITICAL_DAMAGE='💍',
)

View File

@ -4,7 +4,6 @@
from random import choice, randint
from typing import Optional
from .player import Player
from ..interfaces import Entity, FightingEntity, Map, InventoryHolder
from ..translations import gettext as _
@ -30,7 +29,7 @@ class Item(Entity):
The item is dropped from the inventory onto the floor.
"""
if self.held:
self.held_by.inventory.remove(self)
self.held_by.remove_from_inventory(self)
self.held_by.map.add_entity(self)
self.move(self.held_by.y, self.held_by.x)
self.held = False
@ -45,15 +44,58 @@ class Item(Entity):
"""
Indicates what should be done when the item is equipped.
"""
if isinstance(self, Chestplate):
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
elif isinstance(self, Helmet):
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
elif isinstance(self, Weapon):
if self.held_by.equipped_main:
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
# For weapons, they are equipped as main only if main is empty.
else:
self.held_by.remove_from_inventory(self)
self.held_by.equipped_main = self
else:
# Other objects are only equipped as secondary.
if self.held_by.equipped_secondary:
self.held_by.equipped_secondary.unequip()
self.held_by.remove_from_inventory(self)
self.held_by.equipped_secondary = self
def hold(self, player: InventoryHolder) -> None:
def unequip(self) -> None:
"""
Indicates what should be done when the item is unequipped.
"""
if isinstance(self, Chestplate):
self.held_by.equipped_armor = None
elif isinstance(self, Helmet):
self.held_by.equipped_helmet = None
elif isinstance(self, Weapon):
if self.held_by.equipped_main == self:
self.held_by.equipped_main = None
else:
self.held_by.equipped_secondary = None
else:
self.held_by.equipped_secondary = None
self.held_by.add_to_inventory(self)
def hold(self, holder: InventoryHolder) -> None:
"""
The item is taken from the floor and put into the inventory.
"""
self.held = True
self.held_by = player
self.held_by = holder
self.held_by.map.remove_entity(self)
player.add_to_inventory(self)
holder.add_to_inventory(self)
def save_state(self) -> dict:
"""
@ -68,7 +110,8 @@ class Item(Entity):
"""
Returns the list of all item classes.
"""
return [BodySnatchPotion, Bomb, Heart, Sword]
return [BodySnatchPotion, Bomb, Heart, Shield, Sword,\
Chestplate, Helmet, RingCritical, RingXP]
def be_sold(self, buyer: InventoryHolder, seller: InventoryHolder) -> bool:
"""
@ -120,7 +163,7 @@ class Bomb(Item):
"""
damage: int = 5
exploding: bool
owner: Optional["Player"]
owner: Optional["InventoryHolder"]
tick: int
def __init__(self, name: str = "bomb", damage: int = 5,
@ -214,14 +257,80 @@ class Weapon(Item):
d["damage"] = self.damage
return d
def equip(self) -> None:
"""
When a weapon is equipped, the player gains strength.
"""
super().equip()
self.held_by.strength += self.damage
def unequip(self) -> None:
"""
Remove the strength earned by the weapon.
:return:
"""
super().unequip()
self.held_by.strength -= self.damage
class Sword(Weapon):
"""
A basic weapon
"""
def __init__(self, name: str = "sword", price: int = 20, *args, **kwargs):
def __init__(self, name: str = "sword", price: int = 20,
*args, **kwargs):
super().__init__(name=name, price=price, *args, **kwargs)
self.name = name
class Armor(Item):
"""
Class of items that increase the player's constitution.
"""
constitution: int
def __init__(self, constitution: int, *args, **kwargs):
super().__init__(*args, **kwargs)
self.constitution = constitution
def equip(self) -> None:
super().equip()
self.held_by.constitution += self.constitution
def unequip(self) -> None:
super().unequip()
self.held_by.constitution -= self.constitution
def save_state(self) -> dict:
d = super().save_state()
d["constitution"] = self.constitution
return d
class Shield(Armor):
"""
Class of shield items, they can be equipped in the other hand.
"""
def __init__(self, name: str = "shield", constitution: int = 2,\
price: int = 6, *args, **kwargs):
super().__init__(name=name, constitution=constitution, *args, **kwargs)
class Helmet(Armor):
"""
Class of helmet items, they can be equipped on the head.
"""
def __init__(self, name: str = "helmet", constitution: int = 2, \
price: int = 8, *args, **kwargs):
super().__init__(name=name, constitution=constitution, *args, **kwargs)
class Chestplate(Armor):
"""
Class of chestplate items, they can be equipped on the body.
"""
def __init__(self, name: str = "chestplate", constitution: int = 4,\
price: int = 15, *args, **kwargs):
super().__init__(name=name, constitution=constitution, *args, **kwargs)
class BodySnatchPotion(Item):
@ -256,3 +365,69 @@ class BodySnatchPotion(Item):
self.held_by.recalculate_paths()
self.held_by.inventory.remove(self)
class Ring(Item):
"""
A class of rings that boost the player's statistics.
"""
maxhealth: int
strength: int
intelligence: int
charisma: int
dexterity: int
constitution: int
critical: int
experience: float
def __init__(self, maxhealth: int = 0, strength: int = 0,\
intelligence: int = 0, charisma: int = 0,\
dexterity: int = 0, constitution: int = 0,\
critical: int = 0, experience: float = 0, *args, **kwargs):
super().__init__(*args, **kwargs)
self.maxhealth = maxhealth
self.strength = strength
self.intelligence = intelligence
self.charisma = charisma
self.dexterity = dexterity
self.constitution = constitution
self.critical = critical
self.experience = experience
def equip(self) -> None:
super().equip()
self.held_by.maxhealth += self.maxhealth
self.held_by.strength += self.strength
self.held_by.intelligence += self.intelligence
self.held_by.charisma += self.charisma
self.held_by.dexterity += self.dexterity
self.held_by.constitution += self.constitution
self.held_by.critical += self.critical
self.held_by.xp_buff += self.experience
def unequip(self) -> None:
super().unequip()
self.held_by.maxhealth -= self.maxhealth
self.held_by.strength -= self.strength
self.held_by.intelligence -= self.intelligence
self.held_by.charisma -= self.charisma
self.held_by.dexterity -= self.dexterity
self.held_by.constitution -= self.constitution
self.held_by.critical -= self.critical
self.held_by.xp_buff -= self.experience
def save_state(self) -> dict:
d = super().save_state()
d["constitution"] = self.constitution
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)

View File

@ -94,9 +94,9 @@ class Rabbit(Monster):
A rabbit monster.
"""
def __init__(self, name: str = "rabbit", strength: int = 1,
maxhealth: int = 15, *args, **kwargs) -> None:
maxhealth: int = 15, critical: int = 30, *args, **kwargs) -> None:
super().__init__(name=name, strength=strength,
maxhealth=maxhealth, *args, **kwargs)
maxhealth=maxhealth, critical=critical, *args, **kwargs)
class TeddyBear(Monster):
@ -107,3 +107,12 @@ class TeddyBear(Monster):
maxhealth: int = 50, *args, **kwargs) -> None:
super().__init__(name=name, strength=strength,
maxhealth=maxhealth, *args, **kwargs)
class GiantSeaEagle(Monster):
"""
An eagle boss
"""
def __init__(self, name: str = "eagle", strength: int = 1000,
maxhealth: int = 5000, *args, **kwargs) -> None:
super().__init__(name=name, strength=strength,
maxhealth=maxhealth, *args, **kwargs)

View File

@ -2,7 +2,9 @@
# SPDX-License-Identifier: GPL-3.0-or-later
from random import randint
from typing import Dict, Optional, Tuple
from .items import Item
from ..interfaces import FightingEntity, InventoryHolder
@ -12,22 +14,44 @@ class Player(InventoryHolder, FightingEntity):
"""
current_xp: int = 0
max_xp: int = 10
xp_buff: float = 1
paths: Dict[Tuple[int, int], Tuple[int, int]]
equipped_main: Optional[Item]
equipped_secondary: Optional[Item]
equipped_helmet: Optional[Item]
equipped_armor: Optional[Item]
def __init__(self, name: str = "player", maxhealth: int = 20,
strength: int = 5, intelligence: int = 1, charisma: int = 1,
dexterity: int = 1, constitution: int = 1, level: int = 1,
current_xp: int = 0, max_xp: int = 10, inventory: list = None,
hazel: int = 42, *args, **kwargs) \
-> None:
hazel: int = 42, equipped_main: Optional[Item] = None,
equipped_armor: Optional[Item] = None, critical: int = 5,\
equipped_secondary: Optional[Item] = None, \
equipped_helmet: Optional[Item] = None, xp_buff: float = 1,\
*args, **kwargs) -> None:
super().__init__(name=name, maxhealth=maxhealth, strength=strength,
intelligence=intelligence, charisma=charisma,
dexterity=dexterity, constitution=constitution,
level=level, *args, **kwargs)
level=level, critical=critical, *args, **kwargs)
self.current_xp = current_xp
self.max_xp = max_xp
self.xp_buff = xp_buff
self.inventory = self.translate_inventory(inventory or [])
self.paths = dict()
self.hazel = hazel
if isinstance(equipped_main, dict):
equipped_main = self.dict_to_item(equipped_main)
if isinstance(equipped_armor, dict):
equipped_armor = self.dict_to_item(equipped_armor)
if isinstance(equipped_secondary, dict):
equipped_secondary = self.dict_to_item(equipped_secondary)
if isinstance(equipped_helmet, dict):
equipped_helmet = self.dict_to_item(equipped_helmet)
self.equipped_main = equipped_main
self.equipped_armor = equipped_armor
self.equipped_secondary = equipped_secondary
self.equipped_helmet = equipped_helmet
def move(self, y: int, x: int) -> None:
"""
@ -58,9 +82,24 @@ class Player(InventoryHolder, FightingEntity):
Adds some experience to the player.
If the required amount is reached, the player levels up.
"""
self.current_xp += xp
self.current_xp += int(xp*self.xp_buff)
self.level_up()
def remove_from_inventory(self, obj: Item) -> None:
"""
Remove the given item from the inventory, even if the item is equipped.
"""
if obj == self.equipped_main:
self.equipped_main = None
elif obj == self.equipped_armor:
self.equipped_armor = None
elif obj == self.equipped_secondary:
self.equipped_secondary = None
elif obj == self.equipped_helmet:
self.equipped_helmet = None
else:
return super().remove_from_inventory(obj)
# noinspection PyTypeChecker,PyUnresolvedReferences
def check_move(self, y: int, x: int, move_if_possible: bool = False) \
-> bool:
@ -90,4 +129,12 @@ class Player(InventoryHolder, FightingEntity):
d = super().save_state()
d["current_xp"] = self.current_xp
d["max_xp"] = self.max_xp
d["equipped_main"] = self.equipped_main.save_state()\
if self.equipped_main else None
d["equipped_armor"] = self.equipped_armor.save_state()\
if self.equipped_armor else None
d["equipped_secondary"] = self.equipped_secondary.save_state()\
if self.equipped_secondary else None
d["equipped_helmet"] = self.equipped_helmet.save_state()\
if self.equipped_helmet else None
return d

View File

@ -128,6 +128,9 @@ class Game:
self.map.tick(self.player)
elif key == KeyValues.INVENTORY:
self.state = GameMode.INVENTORY
self.display_actions(DisplayActions.UPDATE)
elif key == KeyValues.USE and self.player.equipped_main:
self.player.equipped_main.use()
elif key == KeyValues.SPACE:
self.state = GameMode.MAINMENU
elif key == KeyValues.CHAT:

View File

@ -7,6 +7,8 @@ from random import choice, randint
from typing import List, Optional, Any, Dict, Tuple
from queue import PriorityQueue
from functools import reduce
from random import choice, randint, choices
from typing import List, Optional, Any
from .display.texturepack import TexturePack
from .translations import gettext as _
@ -152,7 +154,8 @@ class Map:
tile = self.tiles[y][x]
if tile.can_walk():
break
entity = choice(Entity.get_all_entity_classes())()
entity = choices(Entity.get_all_entity_classes(),\
weights = Entity.get_weights(), k=1)[0]()
entity.move(y, x)
self.add_entity(entity)
@ -421,11 +424,20 @@ class Entity:
"""
from squirrelbattle.entities.items import BodySnatchPotion, Bomb, Heart
from squirrelbattle.entities.monsters import Tiger, Hedgehog, \
Rabbit, TeddyBear
Rabbit, TeddyBear, GiantSeaEagle
from squirrelbattle.entities.friendly import Merchant, Sunflower, \
Trumpet
Trumpet
return [BodySnatchPotion, Bomb, Heart, Hedgehog, Rabbit, TeddyBear,
Sunflower, Tiger, Merchant, Trumpet]
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]
@staticmethod
def get_all_entity_classes_in_a_dict() -> dict:
@ -434,11 +446,11 @@ class Entity:
"""
from squirrelbattle.entities.player import Player
from squirrelbattle.entities.monsters import Tiger, Hedgehog, Rabbit, \
TeddyBear
TeddyBear, GiantSeaEagle
from squirrelbattle.entities.friendly import Merchant, Sunflower, \
Trumpet
Trumpet
from squirrelbattle.entities.items import BodySnatchPotion, Bomb, \
Heart, Sword
Heart, Sword, Shield, Chestplate, Helmet, RingCritical, RingXP
return {
"Tiger": Tiger,
"Bomb": Bomb,
@ -452,6 +464,12 @@ class Entity:
"Sunflower": Sunflower,
"Sword": Sword,
"Trumpet": Trumpet,
"Eagle": GiantSeaEagle,
"Shield": Shield,
"Chestplate": Chestplate,
"Helmet": Helmet,
"RingCritical": RingCritical,
"RingXP": RingXP,
}
def save_state(self) -> dict:
@ -478,11 +496,12 @@ class FightingEntity(Entity):
dexterity: int
constitution: int
level: int
critical: int
def __init__(self, maxhealth: int = 0, health: Optional[int] = None,
strength: int = 0, intelligence: int = 0, charisma: int = 0,
dexterity: int = 0, constitution: int = 0, level: int = 0,
*args, **kwargs) -> None:
critical: int = 0, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self.maxhealth = maxhealth
self.health = maxhealth if health is None else health
@ -492,6 +511,7 @@ class FightingEntity(Entity):
self.dexterity = dexterity
self.constitution = constitution
self.level = level
self.critical = critical
@property
def dead(self) -> bool:
@ -505,21 +525,28 @@ class FightingEntity(Entity):
The entity deals damage to the opponent
based on their respective stats.
"""
diceroll = randint(0, 100)
damage = self.strength
string = " "
if diceroll <= self.critical: # It is a critical hit
damage *= 4
string = _(" It's a critical hit! ")
return _("{name} hits {opponent}.")\
.format(name=_(self.translated_name.capitalize()),
opponent=_(opponent.translated_name)) + " " + \
opponent.take_damage(self, self.strength)
opponent=_(opponent.translated_name)) + string + \
opponent.take_damage(self, damage)
def take_damage(self, attacker: "Entity", amount: int) -> str:
"""
The entity takes damage from the attacker
based on their respective stats.
"""
self.health -= amount
damage = max(0, amount - self.constitution)
self.health -= damage
if self.health <= 0:
self.die()
return _("{name} takes {amount} damage.")\
.format(name=self.translated_name.capitalize(), amount=str(amount))\
return _("{name} takes {damage} damage.")\
.format(name=self.translated_name.capitalize(), damage=str(damage))\
+ (" " + _("{name} dies.")
.format(name=self.translated_name.capitalize())
if self.health <= 0 else "")
@ -576,10 +603,10 @@ class InventoryHolder(Entity):
"""
for i in range(len(inventory)):
if isinstance(inventory[i], dict):
inventory[i] = self.dict_to_inventory(inventory[i])
inventory[i] = self.dict_to_item(inventory[i])
return inventory
def dict_to_inventory(self, item_dict: dict) -> Entity:
def dict_to_item(self, item_dict: dict) -> Entity:
"""
Translates a dictionnary that contains the state of an item
into an item object.
@ -602,13 +629,15 @@ class InventoryHolder(Entity):
"""
Adds an object to the inventory.
"""
self.inventory.append(obj)
if obj not in self.inventory:
self.inventory.append(obj)
def remove_from_inventory(self, obj: Any) -> None:
"""
Removes an object from the inventory.
"""
self.inventory.remove(obj)
if obj in self.inventory:
self.inventory.remove(obj)
def change_hazel_balance(self, hz: int) -> None:
"""

View File

@ -19,6 +19,7 @@ class TestEntities(unittest.TestCase):
"""
self.map = Map.load(ResourceManager.get_asset_path("example_map.txt"))
self.player = Player()
self.player.constitution = 0
self.map.add_entity(self.player)
self.player.move(self.map.start_y, self.map.start_x)
@ -55,6 +56,7 @@ class TestEntities(unittest.TestCase):
self.assertTrue(entity.dead)
entity = Rabbit()
entity.critical = 0
self.map.add_entity(entity)
entity.move(15, 44)
# Move randomly