diff --git a/squirrelbattle/display/texturepack.py b/squirrelbattle/display/texturepack.py index e4c181e..449a2b7 100644 --- a/squirrelbattle/display/texturepack.py +++ b/squirrelbattle/display/texturepack.py @@ -32,6 +32,7 @@ class TexturePack: SWORD: str TEDDY_BEAR: str TIGER: str + TRUMPET: str WALL: str ASCII_PACK: "TexturePack" @@ -77,6 +78,7 @@ TexturePack.ASCII_PACK = TexturePack( SWORD='\u2020', TEDDY_BEAR='8', TIGER='n', + TRUMPET='/', WALL='#', ) @@ -103,5 +105,6 @@ TexturePack.SQUIRREL_PACK = TexturePack( SWORD='🗡️', TEDDY_BEAR='🧸', TIGER='🐅', + TRUMPET='🎺', WALL='🧱', ) diff --git a/squirrelbattle/entities/friendly.py b/squirrelbattle/entities/friendly.py index d06f35b..1c94ed8 100644 --- a/squirrelbattle/entities/friendly.py +++ b/squirrelbattle/entities/friendly.py @@ -1,8 +1,9 @@ -from ..interfaces import FriendlyEntity, InventoryHolder +from ..interfaces import FriendlyEntity, InventoryHolder, FightingEntity, Map from ..translations import gettext as _ from .player import Player +from .monsters import Monster from .items import Item -from random import choice +from random import choice, shuffle class Merchant(InventoryHolder, FriendlyEntity): @@ -11,7 +12,7 @@ class Merchant(InventoryHolder, FriendlyEntity): """ def keys(self) -> list: """ - Returns a friendly entitie's specific attributes + Returns a friendly entitie's specific attributes. """ return super().keys() + ["inventory", "hazel"] @@ -20,7 +21,6 @@ class Merchant(InventoryHolder, FriendlyEntity): super().__init__(name=name, *args, **kwargs) self.inventory = self.translate_inventory(inventory or []) self.hazel = hazel - if not self.inventory: for i in range(5): self.inventory.append(choice(Item.get_all_items())()) @@ -41,7 +41,7 @@ class Merchant(InventoryHolder, FriendlyEntity): class Sunflower(FriendlyEntity): """ - A friendly sunflower + A friendly sunflower. """ def __init__(self, maxhealth: int = 15, *args, **kwargs) -> None: @@ -53,3 +53,64 @@ class Sunflower(FriendlyEntity): 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__(*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) diff --git a/squirrelbattle/entities/monsters.py b/squirrelbattle/entities/monsters.py index 5453235..87f643e 100644 --- a/squirrelbattle/entities/monsters.py +++ b/squirrelbattle/entities/monsters.py @@ -60,7 +60,12 @@ class Monster(FightingEntity): for move in moves: if move(): 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): """ diff --git a/squirrelbattle/game.py b/squirrelbattle/game.py index 35c0b0f..72bbe2f 100644 --- a/squirrelbattle/game.py +++ b/squirrelbattle/game.py @@ -111,16 +111,16 @@ class Game: """ if key == KeyValues.UP: if self.player.move_up(): - self.map.tick() + self.map.tick(self.player) elif key == KeyValues.DOWN: if self.player.move_down(): - self.map.tick() + self.map.tick(self.player) elif key == KeyValues.LEFT: if self.player.move_left(): - self.map.tick() + self.map.tick(self.player) elif key == KeyValues.RIGHT: if self.player.move_right(): - self.map.tick() + self.map.tick(self.player) elif key == KeyValues.INVENTORY: self.state = GameMode.INVENTORY elif key == KeyValues.SPACE: @@ -129,7 +129,7 @@ class Game: # Wait for the direction of the friendly entity self.waiting_for_friendly_key = True elif key == KeyValues.WAIT: - self.map.tick() + self.map.tick(self.player) def handle_friendly_entity_chat(self, key: KeyValues) -> None: """ diff --git a/squirrelbattle/interfaces.py b/squirrelbattle/interfaces.py index afe4c44..0fec4d0 100644 --- a/squirrelbattle/interfaces.py +++ b/squirrelbattle/interfaces.py @@ -63,7 +63,10 @@ class 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 def remove_entity(self, entity: "Entity") -> None: @@ -152,12 +155,15 @@ class Map: entity.move(y, x) self.add_entity(entity) - def tick(self) -> None: + def tick(self, p: Any) -> None: """ Triggers all entity events. """ 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: """ @@ -296,7 +302,7 @@ class Entity: return self.move(self.y, self.x + 1) if force else \ 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 go to this entity. If self.paths is None, does nothing. @@ -386,6 +392,13 @@ class Entity: """ 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: """ Is this entity a merchant? @@ -408,9 +421,10 @@ class Entity: from squirrelbattle.entities.items import BodySnatchPotion, Bomb, Heart from squirrelbattle.entities.monsters import Tiger, Hedgehog, \ Rabbit, TeddyBear - from squirrelbattle.entities.friendly import Merchant, Sunflower + from squirrelbattle.entities.friendly import Merchant, Sunflower, \ + Trumpet return [BodySnatchPotion, Bomb, Heart, Hedgehog, Rabbit, TeddyBear, - Sunflower, Tiger, Merchant] + Sunflower, Tiger, Merchant, Trumpet] @staticmethod def get_all_entity_classes_in_a_dict() -> dict: