Added a familiar class that follows the player around and hits monsters when it sees one. Added a trumpet, an instance of familiar. Closes #46.

This commit is contained in:
eichhornchen 2020-12-18 17:29:59 +01:00
parent 8ecbf13eae
commit dadafc84eb
5 changed files with 100 additions and 17 deletions

View File

@ -32,6 +32,7 @@ class TexturePack:
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"
@ -77,6 +78,7 @@ TexturePack.ASCII_PACK = TexturePack(
SWORD='\u2020', SWORD='\u2020',
TEDDY_BEAR='8', TEDDY_BEAR='8',
TIGER='n', TIGER='n',
TRUMPET='/',
WALL='#', WALL='#',
) )
@ -103,5 +105,6 @@ TexturePack.SQUIRREL_PACK = TexturePack(
SWORD='🗡️', SWORD='🗡️',
TEDDY_BEAR='🧸', TEDDY_BEAR='🧸',
TIGER='🐅', TIGER='🐅',
TRUMPET='🎺',
WALL='🧱', WALL='🧱',
) )

View File

@ -1,8 +1,9 @@
from ..interfaces import FriendlyEntity, InventoryHolder from ..interfaces import FriendlyEntity, InventoryHolder, FightingEntity, Map
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):
@ -11,7 +12,7 @@ class Merchant(InventoryHolder, FriendlyEntity):
""" """
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())())
@ -41,7 +41,7 @@ class Merchant(InventoryHolder, FriendlyEntity):
class Sunflower(FriendlyEntity): class Sunflower(FriendlyEntity):
""" """
A friendly sunflower A friendly sunflower.
""" """
def __init__(self, maxhealth: int = 15, def __init__(self, maxhealth: int = 15,
*args, **kwargs) -> None: *args, **kwargs) -> None:
@ -53,3 +53,64 @@ class Sunflower(FriendlyEntity):
Lists all that a sunflower can say to the player. Lists all that a sunflower can say to the player.
""" """
return [_("Flower power!!"), _("The sun is warm today")] 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__(*args, **kwargs)
self.target = None
def act(self, p: Player, m : Map) :
"""
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 == None:
self.target = p
if self.target == p:
for entity in m.entities:
if (self.y - entity.y) ** 2 + (self.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))
if self.target.dead :
self.target.paths = None
self.target = None
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)

View File

@ -60,7 +60,12 @@ class Monster(FightingEntity):
for move in moves: for move in moves:
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):
""" """

View File

@ -111,16 +111,16 @@ class Game:
""" """
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
elif key == KeyValues.SPACE: elif key == KeyValues.SPACE:
@ -129,7 +129,7 @@ class Game:
# 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: elif key == KeyValues.WAIT:
self.map.tick() self.map.tick(self.player)
def handle_friendly_entity_chat(self, key: KeyValues) -> None: def handle_friendly_entity_chat(self, key: KeyValues) -> None:
""" """

View File

@ -63,7 +63,10 @@ class Map:
""" """
Registers 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:
@ -152,12 +155,15 @@ class Map:
entity.move(y, x) entity.move(y, x)
self.add_entity(entity) self.add_entity(entity)
def tick(self) -> None: def tick(self, p: Any) -> None:
""" """
Triggers all entity events. 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:
""" """
@ -296,7 +302,7 @@ class Entity:
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 = 8) -> None: def recalculate_paths(self, max_distance: int = 12) -> None:
""" """
Uses Dijkstra algorithm to calculate best paths for other entities to Uses Dijkstra algorithm to calculate best paths for other entities to
go to this entity. If self.paths is None, does nothing. go to this entity. If self.paths is None, does nothing.
@ -386,6 +392,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?
@ -408,9 +421,10 @@ class Entity:
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
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, Trumpet]
@staticmethod @staticmethod
def get_all_entity_classes_in_a_dict() -> dict: def get_all_entity_classes_in_a_dict() -> dict: