# Copyright (C) 2020 by ÿnérant, eichhornchen, nicomarg, charlse # SPDX-License-Identifier: GPL-3.0-or-later from random import choice, randint from typing import Optional from ..interfaces import Entity, FightingEntity, Map, InventoryHolder from ..translations import gettext as _ class Item(Entity): """ A class for items """ held: bool held_by: Optional[InventoryHolder] price: int def __init__(self, held: bool = False, held_by: Optional[InventoryHolder] = None, price: int = 2, *args, **kwargs): super().__init__(*args, **kwargs) self.held = held self.held_by = held_by self.price = price def drop(self) -> None: """ The item is dropped from the inventory onto the floor """ if self.held: 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 self.held_by = None def use(self) -> None: """ Indicates what should be done when the item is used. """ def equip(self) -> None: """ Indicates what should be done when the item is equipped. """ if self.held_by.equipped_item: self.held_by.equipped_item.unequip() self.held_by.remove_from_inventory(self) self.held_by.equipped_item = self def unequip(self) -> None: """ Indicates what should be done when the item is unequipped. """ self.held_by.add_to_inventory(self) self.held_by.equipped_item = None def hold(self, holder: InventoryHolder) -> None: """ The item is taken from the floor and put into the inventory """ self.held = True self.held_by = holder self.held_by.map.remove_entity(self) holder.add_to_inventory(self) def save_state(self) -> dict: """ Saves the state of the entity into a dictionary """ d = super().save_state() d["held"] = self.held return d @staticmethod def get_all_items() -> list: return [BodySnatchPotion, Bomb, Heart, Sword] def be_sold(self, buyer: InventoryHolder, seller: InventoryHolder) -> bool: """ Does all necessary actions when an object is to be sold. Is overwritten by some classes that cannot exist in the player's inventory """ if buyer.hazel >= self.price: self.hold(buyer) seller.remove_from_inventory(self) buyer.change_hazel_balance(-self.price) seller.change_hazel_balance(self.price) return True else: return False class Heart(Item): """ A heart item to return health to the player """ healing: int def __init__(self, name: str = "heart", healing: int = 5, price: int = 3, *args, **kwargs): super().__init__(name=name, price=price, *args, **kwargs) self.healing = healing def hold(self, entity: InventoryHolder) -> None: """ When holding a heart, heal the player and don't put item in inventory. """ entity.health = min(entity.maxhealth, entity.health + self.healing) entity.map.remove_entity(self) def save_state(self) -> dict: """ Saves the state of the header into a dictionary """ d = super().save_state() d["healing"] = self.healing return d class Bomb(Item): """ A bomb item intended to deal damage to enemies at long range """ damage: int = 5 exploding: bool owner: Optional["InventoryHolder"] tick: int def __init__(self, name: str = "bomb", damage: int = 5, exploding: bool = False, price: int = 4, *args, **kwargs): super().__init__(name=name, price=price, *args, **kwargs) self.damage = damage self.exploding = exploding self.tick = 4 self.owner = None def use(self) -> None: """ When the bomb is used, throw it and explodes it. """ if self.held: self.owner = self.held_by super().drop() self.exploding = True def act(self, m: Map) -> None: """ Special exploding action of the bomb """ if self.exploding: if self.tick > 0: # The bomb will explode in moves self.tick -= 1 else: # The bomb is exploding. # Each entity that is close to the bomb takes damages. # The player earn XP if the entity was killed. log_message = _("Bomb is exploding.") for e in m.entities.copy(): if abs(e.x - self.x) + abs(e.y - self.y) <= 3 and \ isinstance(e, FightingEntity): log_message += " " + e.take_damage(self, self.damage) if e.dead: self.owner.add_xp(randint(3, 7)) m.logs.add_message(log_message) 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: """ Saves the state of the bomb into a dictionary """ d = super().save_state() d["exploding"] = self.exploding d["damage"] = self.damage 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 explosion instant dies. """ m.remove_entity(self) def hold(self, player: InventoryHolder) -> None: """ The player can't hold any explosion. """ pass class Weapon(Item): """ Non-throwable items that improve player damage """ damage: int def __init__(self, damage: int = 3, *args, **kwargs): super().__init__(*args, **kwargs) self.damage = damage def save_state(self) -> dict: """ Saves the state of the weapon into a dictionary """ d = super().save_state() d["damage"] = self.damage return d class Sword(Weapon): """ A basic weapon """ strength: int def __init__(self, name: str = "sword", price: int = 20, strength: int = 3, *args, **kwargs): super().__init__(name=name, price=price, *args, **kwargs) self.name = name self.strength = strength def equip(self) -> None: """ When a sword is equipped, the player gains strength. """ super().equip() self.held_by.strength += self.strength def unequip(self) -> None: """ Remove the strength earned by the sword. :return: """ super().unequip() self.held_by.strength -= self.strength class BodySnatchPotion(Item): """ The body-snatch potion allows to exchange all characteristics with a random other entity. """ def __init__(self, name: str = "body_snatch_potion", price: int = 14, *args, **kwargs): super().__init__(name=name, price=price, *args, **kwargs) def use(self) -> None: """ Find a valid random entity, then exchange characteristics. """ valid_entities = self.held_by.map.find_entities(FightingEntity) valid_entities.remove(self.held_by) entity = choice(valid_entities) entity_state = entity.save_state() player_state = self.held_by.save_state() self.held_by.__dict__.update(entity_state) entity.__dict__.update(player_state) self.held_by.map.currenty, self.held_by.map.currentx = self.held_by.y,\ self.held_by.x self.held_by.map.logs.add_message( _("{player} exchanged its body with {entity}.").format( player=self.held_by.translated_name.capitalize(), entity=entity.translated_name)) self.held_by.recalculate_paths() self.held_by.inventory.remove(self)