def _get_unit_model(self, **kwargs):
        unit_level = UnitLevel()
        is_player = False
        level = randrange(1, 100)
        xp = unit_level.get_xp_for_level(level)
        unit_id = uuid4()
        unit_name = "Unit%s" % unit_id
        unit_types = (1, 2, 3)
        unit_type_id = choice(unit_types)

        if "is_player" in kwargs:
            is_player = kwargs["is_player"]

        user_id = 0

        if is_player:
            user_id = randrange(1, 999)

        unit_model = {
            "id": unit_id,
            "unit_name": unit_name,
            "items": [],
            "effects": [],
            "dialogue": [],
            "experience": xp,
            "level": level,
            "user_id": user_id,
            "is_boss": 0,
            "unit_type_id": unit_type_id,
            "unit_type_name": "quux",
            "combat_status": "hostile",
            "base_items": []
        }

        return unit_model
    def test_get_xp_remaining_until_next_level_percentage(self):
        unit_generator = UnitGenerator()
        unit = unit_generator.generate()

        unit_level = UnitLevel()

        level_42_xp = unit_level.get_xp_for_level(42)
        ten_percent_of_total_xp = level_42_xp * .10
        unit.experience = level_42_xp - ten_percent_of_total_xp
        total_xp = level_42_xp

        # Test 90%
        expected_percentage = 90
        actual_percentage = unit.get_xp_remaining_until_next_level_percentage(
            total_xp)

        self.assertEqual(expected_percentage, actual_percentage)

        # Test 50%
        expected_fifty_percentage = 50
        fifty_percent_of_total_xp = level_42_xp * .50
        unit.experience = level_42_xp - fifty_percent_of_total_xp
        a_fifty = unit.get_xp_remaining_until_next_level_percentage(total_xp)

        self.assertEqual(expected_fifty_percentage, a_fifty)

        # Test xp over max level
        expected_one_twenty_percent = 120
        max_level_xp = unit_level.get_xp_for_max_level()
        twenty_percent_of_max = max_level_xp * .20
        unit.experience = max_level_xp + twenty_percent_of_max
        a_120 = unit.get_xp_remaining_until_next_level_percentage(max_level_xp)

        self.assertEqual(expected_one_twenty_percent, a_120)
    def generate(self, **kwargs):
        unit_level = UnitLevel()
        level = randrange(1, 100)
        unit_id = uuid4()
        combat_status = "passive"

        if "level" in kwargs:
            level = kwargs["level"]

        xp = unit_level.get_xp_for_level(level) + 1

        if "combat_status" in kwargs:
            combat_status = kwargs["combat_status"]

        if "unit_name" in kwargs:
            unit_name = kwargs["unit_name"]
        else:
            unit_name = "Level%sUnit%s" % (level, unit_id)

        unit_types = (1, 2, 3)
        unit_type_id = choice(unit_types)

        if "unit_type_id" in kwargs:
            unit_type_id = kwargs["unit_type_id"]

        user_id = 0

        if "is_player" in kwargs:
            user_id = randrange(1, 999)

        if "user_id" in kwargs:
            user_id = kwargs["user_id"]

        if "base_items" in kwargs:
            base_items = kwargs["base_items"]
        else:
            base_items = []

        unit_model = {
            "id": unit_id,
            "unit_name": unit_name,
            "items": [],
            "effects": [],
            "dialogue": [],
            "experience": xp,
            "level": level,
            "user_id": user_id,
            "is_boss": 0,
            "unit_type_id": unit_type_id,
            "unit_type_name": "quux",
            "combat_status": combat_status,
            "base_items": base_items
        }

        with LogCapture():
            logger = logging.getLogger()
            unit = Unit(unit=unit_model, log=logger)

        return unit
    def __init__(self, **kwargs):
        self._irc = kwargs["irc"]
        self.is_public = kwargs["public"]
        self.destination = kwargs["destination"]
        self.ircutils = kwargs["ircutils"]
        self.ircmsgs = kwargs["ircmsgs"]
        unit_levels = UnitLevel()
        self.levels = unit_levels.get_levels()
        self.log = None
        self.testing = False

        if "log" in kwargs:
            self.log = kwargs["log"]

        if "testing" in kwargs:
            self.testing = kwargs["testing"]
Esempio n. 5
0
class Unit:

    """
    Basis for units in a dungeon
    """
    UNIT_TYPE_ZEN_MASTER = 1
    UNIT_TYPE_HACKER = 2
    UNIT_TYPE_TROLL = 3

    def __init__(self, **kwargs):
        """
        Start by initializing the unit as if it is an NPC,
        since for the most part, a player is identical to a NPC.
        """
        self.log = kwargs["log"]
        self.lower_damage_coefficient = 5
        self.upper_damage_coefficient = 10
        self.critical_strike_chance = 5
        self.slain_units = []
        self.winning_streak = []
        self.hostile_combatants = []
        self.raised_units = []
        self.battles = []
        self.units_that_have_struck_me = []

        """
        Build unit from db info
        """
        unit = kwargs["unit"]

        self.id = unit["id"]
        self.is_boss = unit["is_boss"] == 1 or unit["is_boss"]
        self.user_id = unit["user_id"]
        self.created_at = time.time()
        self.slain_at = None
        self.unit_type_name = unit["unit_type_name"]
        self.unit_type_id = unit["unit_type_id"]
        self.effects = unit["effects"]
        self.items = unit["items"]
        self.base_items = unit["base_items"]
        self.dialogue = unit["dialogue"]
        self.experience = unit["experience"]

        self.unit_level = UnitLevel()

        self.level = self.unit_level.get_level_by_xp(self.experience)
        self.combat_status = unit["combat_status"]
        self.is_casting_spell = False
        self.spell_interrupted_callback = None

        """ When unit is an NPC then use the unit name """
        self.name = unit["unit_name"]
        self.nick = self.name
        self.title = self.unit_type_name

        """
        If the unit has a non-zero user_id, then they are a player.
        When the unit is a NPC then their level depends
        on the dungeon min/max.
        """
        self.is_player = self.user_id > 0
        self.is_npc = self.user_id <= 0

        """
        Each unit should get some base items for their level.
        """
        self.populate_inventory_with_base_items()

        """
        The above line ensures that the unit always has at least the
        base weapons.
        """
        self.equip_random_weapon()

        """
        HP is a function of the unit's level, so
        the level must be determined prior.
        """
        self.hp = self.calculate_hp()

        """
        Apply unit effects
        """
        self.apply_unit_effects()

        """
        Some items have an effect on possession!
        """
        self.apply_item_effects()

        self.lootable_items = [
            item for item in self.items if not item.is_permanent]

    def __str__(self):
        return "%s-%s" % (self.name, self.id)

    def __eq__(self, other):
        """
        Compares this unit against another one, checking
        unit id and inventory contents
        """
        id_match = self.id == other.id

        if not id_match:
            return False

        # if not self.name == other.name:
        #    return False

        """
        Test inventory contents
        """
        item_length_match = len(self.items) == len(other.items)

        if not item_length_match:
            return False

        self_item_ids = [item.id for item in self.items]
        other_item_ids = [item.id for item in other.items]
        items_match = self_item_ids == other_item_ids

        if not items_match:
            return False

        """
        Test effects
        """
        effect_length_match = len(self.effects) == len(other.effects)

        if not effect_length_match:
            return False

        self_effect_ids = [effect.id for effect in self.effects]
        other_effect_ids = [effect.id for effect in other.effects]
        effects_match = self_effect_ids == other_effect_ids

        if not effects_match:
            return False

    def add_hostile_combatant(self, **kwargs):
        combatant = kwargs["combatant"]

        if combatant not in self.hostile_combatants:
            self.hostile_combatants.append(combatant)

    def remove_hostile_combatant(self, **kwargs):
        combatant = kwargs["combatant"]
        hostile_combatants = []

        for hcombatant in self.hostile_combatants:
            if combatant != hcombatant:
                hostile_combatants.append(hcombatant)

        self.hostile_combatants = hostile_combatants

    def apply_unit_effects(self):
        for effect in self.effects:
            self.apply_effect(effect)

    def add_charge_to_random_potion(self):
        potion = self.get_random_potion_without_charges()

        if potion is not None:
            self.add_charge_to_item(item=potion)

    def get_random_potion_without_charges(self):
        potions = []

        for item in self.items:
            is_potion = item.is_potion()
            has_charges = item.charges > 0

            if item.is_usable() and is_potion and not has_charges:
                potions.append(item)

        if len(potions) > 0:
            return random.choice(potions)

    def add_charge_to_item(self, **kwargs):
        item = kwargs["item"]

        if item.is_usable():
            item.charges += 1

            self.log.info("SpiffyRPG: added charge to %s" % item.name)
        else:
            self.log.error(
                "SpiffyRPG: cannot add charges to %s because it is not usable" % item.name)

    def use_item(self, **kwargs):
        item = kwargs["item"]

        if item in self.items:
            if not item.can_use or item.charges == 0:
                return

            """
            Decrement charges each time an item is used
            """
            item.charges -= 1

            if len(item.effects) > 0:
                """
                Apply item effects
                """
                for effect in item.effects:
                    self.apply_effect(effect)

                return True
            else:
                self.log.info("SpiffyRPG: attempting to use item %s \
                but it has no effects!" % item.name)
        else:
            self.log.error("SpiffyRPG: attempting to use %s but it is not in %s's bags" %
                           (item.name, self.name))

    def begin_casting_raise_dead(self):
        self.begin_casting_spell()

    def begin_casting_spell(self):
        self.combat_status = "hostile"
        self.is_casting_spell = True

    def set_spell_interrupted_callback(self, callback):
        self.spell_interrupted_callback = callback

    def on_unit_spell_interrupted(self, **kwargs):
        attacker = kwargs["attacker"]

        self.is_casting_spell = False

        """
        This callback typically announces the spell interruption
        """
        if self.spell_interrupted_callback is not None:
            self.spell_interrupted_callback(unit=self,
                                            attacker=attacker)

    def raise_dead(self, **kwargs):
        """
        Raise dead is a spell, so begin_casting_raise_dead is
        called before this to symbolize a spell being cast.
        If apply_damage is called before this method, then
        the spell is "interrupted". Returns True if the spell
        was cast successfully.
        """
        dead_unit = kwargs["unit"]

        if self.is_casting_spell:
            self.log.info("SpiffyRPG: %s raises %s from the dead!" %
                          (self.name, dead_unit.get_name()))

            """ Reset HP """
            dead_unit.hp = dead_unit.calculate_hp()

            """ Apply Undead effect """
            undead_effect = self.effects_collection.get_effect_by_name(
                name="Undead")
            dead_unit.apply_effect(undead_effect)

            """ Make this unit hostile """
            dead_unit.combat_status = "hostile"

            self.is_casting_spell = False

            return True

    def has_item(self, **kwargs):
        loot_item = kwargs["item"]
        item_ids = [item.id for item in self.items]

        return loot_item.id in item_ids

    def add_inventory_item(self, **kwargs):
        """
        Permanently adds an item to a unit's inventory
        """
        item = kwargs["item"]

        if not self.has_item(item=item):
            """
            Add to inventory
            """
            self.items.append(item)

            """
            Apply any effects this item may have
            """
            self.apply_item_effects()

            """
            Persist item to database
            """
            self.item_collection.add_unit_item(item_id=item.id,
                                               unit_id=self.id)

    def apply_item_effects(self):
        for item in self.items:
            if len(item.effects) > 0:
                for effect in item.effects:
                    if effect.effect_on_possession == "1":
                        params = (effect.name, self.name, item.name)
                        self.log.info(
                            "SpiffyRPG: applying %s to %s due to item effect on %s" % params)

                        self.apply_effect(effect)

    def make_hostile(self):
        self.combat_status = "hostile"

    def get_random_lootable_item(self, **kwargs):
        """
        Find a random lootable item, and something the
        other unit does not already have.
        """
        already_has_items = kwargs["already_has"]
        already_has_item_ids = [item.id for item in already_has_items]
        lootable = [item for item in self.lootable_items
                    if item.id not in already_has_item_ids]

        if len(lootable):
            return random.choice(lootable)

    def add_struck_unit(self, unit):
        if unit not in self.units_that_have_struck_me:
            self.units_that_have_struck_me.append(unit)

    def reset_struck_units(self):
        self.units_that_have_struck_me = []

    def get_slain_units(self):
        return self.slain_units

    def add_slain_unit(self, unit):
        self.slain_units.append(unit)

    def get_effects_list(self):
        """
        Retrieves a list of effects on this unit
        """
        effect_names = []

        for effect in self.effects:
            effect_names.append(effect.name)

        return ", ".join(effect_names)

    def add_raised_unit(self, **kwargs):
        unit = kwargs["unit"]

        if unit not in self.raised_units:
            self.raised_units.append(unit)

    def add_winning_streak_unit(self, **kwargs):
        unit = kwargs["unit"]

        self.winning_streak.append(unit)

        streak_count = self.is_on_hot_streak()

        return streak_count

    def is_on_hot_streak(self):
        """
        Determines if unit is on a hot streak. A streak is
        at least three consecutive victories. Returns the
        streak count, if any.
        """
        streak_count = len(self.winning_streak)

        if streak_count >= 3:
            if streak_count == 3:
                self.log.info("SpiffyRPG: %s is on a streak of 3!" % self.name)
            elif streak_count == 4:
                self.log.info("SpiffyRPG: %s is on a streak of 4!" % self.name)

            return streak_count

    def reset_winning_streak(self):
        self.winning_streak = []

    def is_hostile(self):
        return self.combat_status == "hostile"

    def is_friendly(self):
        return self.combat_status == "friendly"

    def is_unit_same_stage(self, **kwargs):
        unit = kwargs["unit"]

        this_unit_stage = self.get_stage_by_level(level=self.level)
        target_unit_stage = self.get_stage_by_level(level=unit.level)

        return target_unit_stage == this_unit_stage

    def can_battle_unit(self, **kwargs):
        """
        Returns True if can battle, or a reason (string) if not
        """
        unit = kwargs["unit"]
        reason = "That target "

        """ Check if unit exists """
        if unit is None:
            reason += "does not appear to exist"
            return reason

        """ Check if we're trying to hit ourselves """
        if self.id == unit.id:
            reason = "is not as clever as they would have you believe"
            return reason

        """ Check if this unit is hostile """
        if not unit.is_hostile():
            reason += " is not hostile (%s)" % unit.combat_status
            return reason

        """
        Check if we're currently in battle with something other than
        this unit
        """
        self_battles = self.get_incomplete_battles()
        battles_with_others = [battle for battle in self_battles
                               if battle["combatant"].id != unit.id]

        if len(battles_with_others) > 0:
            battle_with_other = battles_with_others[0]
            combatant = battle_with_other["combatant"]
            current_round = len(battle_with_other["rounds"]) + 1
            total_rounds = battle_with_other["total_rounds"]
            params = (combatant.name, current_round, total_rounds)
            reason = "You're currently currently battling %s (round %s/%s)" % params
            return reason

        """ Check if the unit is in battle """
        target_battles = unit.get_incomplete_battles()
        target_battles_with_others = [battle for battle in target_battles
                                      if battle["combatant"].id != self.id]

        if len(target_battles_with_others) > 0:
            battle_with_other = target_battles_with_others[0]
            combatant = battle_with_other["combatant"]
            current_round = len(battle_with_other["rounds"])
            total_rounds = battle_with_other["total_rounds"]
            params = (combatant.name, current_round, total_rounds)
            reason += "is currently battling %s (round %s/%s)" % params
            return reason

        """ Check stage match """
        this_unit_stage = self.get_stage_by_level(level=self.level)
        target_unit_stage = self.get_stage_by_level(level=unit.level)

        if target_unit_stage > this_unit_stage:
            reason += "is too powerful! Use .look to find monsters your level"
            return reason

        if target_unit_stage < this_unit_stage:
            reason = "is too weak. Use .look to find monsters your level"
            return reason

        """ It's all gravy """
        return True

    def regenerate_hp(self, regen_hp):
        current_hp = self.get_hp()
        max_hp = self.calculate_hp()

        if self.is_below_max_hp():
            self.hp += regen_hp

            # self.announcer.player_regenerates(regen_hp=regen_hp)

            self.log.info("SpiffyRPG: unit %s gains %sHP from Regneration" %
                          (self.name, regen_hp))
        else:
            """
            If regeneration has brought this unit back to life,
            reset created_at
            """
            self.created_at = time.time()

            self.log.info("SpiffyRPG: unit %s is not rengerating because max HP (%s/%s)" %
                          (self.name, current_hp, max_hp))

    def is_below_max_hp(self):
        current_hp = self.get_hp()
        max_hp = self.calculate_hp()

        return current_hp < max_hp

    def add_victory_hp_bonus(self, **kwargs):
        current_hp = self.get_hp()
        max_hp = self.calculate_hp()
        hp_bonus = 0

        if self.is_below_max_hp():
            hp_bonus = int(max_hp * .10)
            self.hp += hp_bonus

            self.log.info("SpiffyRPG: unit %s gains %sHP for winning" %
                          (self.name, hp_bonus))
        else:
            self.log.info("SpiffyRPG: unit %s is at max HP (%s/%s)" %
                          (self.name, current_hp, max_hp))

        return hp_bonus

    def populate_inventory_with_base_items(self):
        """
        The base items contain all of the items available
        for this unit type
        """
        base_items = self.base_items

        for item in base_items:
            is_unit_type = item.unit_type_id == self.unit_type_id
            is_level_appropriate = item.min_level <= self.level
            is_in_bags = item in self.items

            if not is_in_bags and is_unit_type and is_level_appropriate:
                if self.is_player:
                    """
                    Players start with a rock
                    """
                    if self.is_stage_one() and item.is_scissors():
                        continue
                else:
                    """
                    NPCs start with scissors
                    """
                    if self.is_stage_one() and item.is_rock():
                        continue

                self.items.append(item)

    def get_stage_by_level(self, **kwargs):
        stage = 1
        stage_two_min_level = 3
        stage_three_min_level = 10

        level = kwargs["level"]

        if level >= stage_two_min_level:
            stage = 2

        if level >= stage_three_min_level:
            stage = 3

        return stage

    def is_stage_one(self):
        return self.get_stage_by_level(level=self.level) == 1

    def is_stage_two(self):
        return self.get_stage_by_level(level=self.level) == 2

    def is_stage_three(self):
        return self.get_stage_by_level(level=self.level) == 3

    def equip_item(self, **kwargs):
        self.equipped_weapon = kwargs["item"]

    def equip_random_inventory_item_by_type(self, **kwargs):
        item_name = kwargs["item_type_name"]
        inventory_item = self.get_item_from_inventory_by_type(
            item_type_name=item_name)

        if inventory_item is not None:
            self.equip_item(item=inventory_item)

            self.announcer.item_equip(player=self,
                                      item=inventory_item)
        else:
            self.announcer.item_equip_failed(player=self,
                                             item_name=item_name)

    def equip_item_by_name(self, **kwargs):
        item_name = kwargs["item_name"]
        inventory_item = self.get_item_from_inventory_by_name(
            item_name=item_name)

        if inventory_item is not None:
            self.equip_item(item=inventory_item)

            return inventory_item

    def get_item_type_from_user_input(self, **kwargs):
        item_type = kwargs["item_type"].lower()

        if item_type == "rock" or item_type[0] == "r":
            item_type = "rock"

        if item_type == "paper" or item_type[0] == "p":
            item_type = "paper"

        starts_with_scissors = (item_type[0] == "s" and item_type != "spock")

        if item_type == "scissors" or starts_with_scissors:
            item_type = "scissors"

        if item_type == "lizard" or item_type[0] == "l":
            item_type = "lizard"

        if item_type == "spock" or item_type[0] == "v":
            item_type = "spock"

        return item_type

    def equip_rock_weapon(self):
        self.equip_item_by_type(item_type="rock")

    def equip_paper_weapon(self):
        self.equip_item_by_type(item_type="paper")

    def equip_scissors_weapon(self):
        self.equip_item_by_type(item_type="scissors")

    def equip_lizard_weapon(self):
        self.equip_item_by_type(item_type="lizard")

    def equip_spock_weapon(self):
        self.equip_item_by_type(item_type="spock")

    def equip_item_by_type(self, **kwargs):
        item_type = self.get_item_type_from_user_input(
            item_type=kwargs["item_type"])
        inventory_item = self.get_item_from_inventory_by_type(
            item_type_name=item_type)
        equip_ok = False

        if inventory_item is not None:
            if self.equipped_weapon.item_type != inventory_item.item_type:
                self.equip_item(item=inventory_item)

            equip_ok = True
        else:
            self.announcer.item_equip_failed(player=self)
            equip_ok = False

        return equip_ok

    def get_equipped_weapon(self):
        """
        If the player only has one weapon, then they're
        always going to equip that.
        """
        return self.equipped_weapon

    def get_rock_weapon(self):
        return self.get_item_from_inventory_by_type(item_type_name="rock")

    def get_paper_weapon(self):
        return self.get_item_from_inventory_by_type(item_type_name="paper")

    def get_scissors_weapon(self):
        return self.get_item_from_inventory_by_type(item_type_name="scissors")

    def get_lizard_weapon(self):
        return self.get_item_from_inventory_by_type(item_type_name="lizard")

    def get_spock_weapon(self):
        return self.get_item_from_inventory_by_type(item_type_name="spock")

    def get_item_from_inventory_by_type(self, **kwargs):
        item_type_name = kwargs["item_type_name"]
        items = []
        inventory_item_to_equip = None

        """
        Find a random item of $type in inventory
        """
        for item in self.items:
            if item.item_type == item_type_name:
                items.append(item)

        if len(items) > 0:
            inventory_item_to_equip = random.choice(items)

        return inventory_item_to_equip

    def get_item_from_inventory_by_id(self, **kwargs):
        items = self.items

        if len(items) > 0:
            for item in items:
                if kwargs["item_id"] == item.id:
                    return item
        else:
            self.log.info("SpiffyRPG: trying to get items but inventory is empty!")

    def get_item_from_inventory_by_name(self, **kwargs):
        item_name = kwargs["item_name"].lower()
        items = self.items

        for item in items:
            if item_name in item.name.lower():
                return item

    def get_equippable_items(self):
        items = []
        stage = self.get_stage_by_level(level=self.level)
        unit_type = "NPC"

        if self.is_player:
            unit_type = "PC"

        self.log.info("SpiffyRPG: %s is a stage %s %s" %
                      (self.name, stage, unit_type))

        for item in self.items:
            equippable = False

            if self.is_stage_three():
                equippable = True
            else:
                """
                Stage one players start with a rock
                Stage one NPCs start with scissors
                """
                stage_two_items = ("paper", "scissors", "rock")
                is_stage_two_item = item.item_type in stage_two_items

                if self.is_stage_one():
                    if self.is_player:
                        if item.is_rock():
                            equippable = True
                    else:
                        if item.is_scissors():
                            equippable = True
                elif self.is_stage_two() and is_stage_two_item:
                    equippable = True

            if equippable:
                items.append(item)

        return items

    def equip_random_weapon(self, **kwargs):
        """
        Before each fight, NPCs equip a random weapon. However,
        this behavior can be modified through effects!
        """
        all_types = ("rock", "paper", "scissors", "lizard", "spock")
        weapon_types = all_types

        """
        This is used to avoid draws
        """
        if "avoid_weapon_type" in kwargs:
            avoid = kwargs["avoid_weapon_type"]
            weapon_types = [wtype for wtype in all_types if wtype != avoid]

        items = [item for item in self.items if item.item_type in weapon_types]

        if len(items) > 0:
            """
            By default we fetch a random item as usual.
            """
            random_item = random.choice(items)
            equipped_item = random_item
            pref_chance = 70
            chance_to_equip_preferred = random.randrange(1, 100) < pref_chance

            """
            If the unit has one of the preferential weapon effects,
            then choose one of those appropriately.
            """
            uname = self.name

            if self.is_archeologist():
                if chance_to_equip_preferred:
                    self.log.info(
                        "SpiffyRPG: Archeologist %s is equipping rock!" % uname)
                    equipped_item = self.get_rock_weapon()
            elif self.is_paper_enthusiast():
                if chance_to_equip_preferred:
                    self.log.info(
                        "SpiffyRPG: Paper Enthusiast %s is equipping paper!" % uname)
                    equipped_item = self.get_paper_weapon()
            elif self.is_running_with_scissors():
                if chance_to_equip_preferred:
                    self.log.info(
                        "SpiffyRPG: Running With Scissors %s is equipping scissors!" % uname)
                    equipped_item = self.get_scissors_weapon()
            elif self.is_blue_tongue():
                if chance_to_equip_preferred:
                    self.log.info(
                        "SpiffyRPG: Blue Tongue %s is equipping lizard!" % self.name)
                    equipped_item = self.get_lizard_weapon()
            elif self.is_vulcan_embraced():
                if chance_to_equip_preferred:
                    self.log.info(
                        "SpiffyRPG: Vulcan's Embrace %s is equipping spock!" % self.name)
                    equipped_item = self.get_spock_weapon()

            self.equipped_weapon = equipped_item

    def set_title(self, title):
        self.name = title

    def get_dialogue_by_type(self, **kwargs):
        dialogue_type = kwargs["dialogue_type"]
        dialogues = []

        for dialogue in self.dialogue:
            if dialogue["context"] == dialogue_type:
                dialogues.append(dialogue)

        if len(dialogues) > 0:
            dialogue = random.choice(dialogues)

            return dialogue["dialogue"]

    def dialogue_intro(self):
        return self.get_dialogue_by_type(dialogue_type="intro")

    def dialogue_win(self):
        return self.get_dialogue_by_type(dialogue_type="win")

    def dialogue_sup(self):
        return self.get_dialogue_by_type(dialogue_type="sup")

    def dialogue_zombie(self):
        return self.dialogue_collection.get_dialogue_by_context(context="zombie")

    def get_title(self):
        return self.title

    def get_xp_required_for_next_level(self):
        return self.unit_level.get_xp_for_next_level(self.level)

    def get_xp_required_for_previous_level(self):
        return self.unit_level.get_xp_for_next_level(self.level - 1)

    def get_level(self):
        return self.unit_level.get_level_by_xp(self.experience)

    def get_unit_title(self):
        return self.unit_type_name

    def on_unit_level(self, **kwargs):
        self.title = self.get_unit_title()
        self.hp = self.calculate_hp()
        self.populate_inventory_with_base_items()

    def add_experience(self, experience):
        gained_level = False
        if experience <= 0:
            return

        current_level = self.level

        self.experience += experience
        self.level = self.get_level()

        if self.level != current_level:
            gained_level = True
            self.on_unit_level()

        self.log.info("Player %s adding %s xp" % (self.name, experience))

        self.unit_model.add_experience(self.id, self.experience)

        return gained_level

    def calculate_hp(self):
        base_factor = 15

        if self.is_player:
            base_factor += 5

        base_hp = self.level * base_factor

        return base_hp

    def get_effects(self):
        """ TODO: Check duration here """
        return self.effects

    def get_hp(self):
        hp = self.hp

        return hp

    def get_xp_remaining_until_next_level_percentage(self, total):
        current_xp = self.experience
        total_xp_for_current_level = total

        remaining_xp = float(
            current_xp) / float(total_xp_for_current_level) * 100

        return remaining_xp

    def get_hp_percentage(self):
        total_hp = self.calculate_hp()
        current_hp = self.get_hp()

        percentage = float(current_hp) / float(total_hp) * 100

        return int(percentage)

    def get_name(self):
        unit_name = self.name

        if self.is_undead():
            unit_name = "Undead %s" % unit_name

        return unit_name

    def has_full_hp(self):
        hp = self.get_hp()
        total_hp = self.calculate_hp()

        return hp == total_hp

    def adjust_hp(self, effect):
        total_hp = self.calculate_hp()
        adjustment = float(
            total_hp * (float(effect.hp_adjustment) / float(100)))

        if effect.operator == "+":
            self.hp += adjustment

            self.log.info("SpiffyRPG: added %s HP from %s" %
                          (adjustment, effect.name))
        elif effect.operator == "-":
            self.hp -= adjustment

            self.log.info("SpiffyRPG: subtracted %s HP from %s" %
                          (adjustment, effect.name))
        elif effect.operator == "*":
            self.hp *= adjustment

            self.log.info("SpiffyRPG: multiplying %s HP from %s" %
                          (adjustment, effect.name))

    def apply_effect(self, effect):
        """
        hp_adjustment is an instant effect that
        adjusts HP based on a percentage of the unit's
        total HP
        """
        if effect not in self.effects:
            self.effects.append(effect)

        if effect.hp_adjustment is not None:
            self.adjust_hp(effect)

        if effect.name == "Undead":
            self.on_effect_undead_applied()

    def on_effect_undead_applied(self):
        """
        Revive unit
        """
        total_hp = self.calculate_hp()
        self.hp = total_hp

        """
        Reset created_at so show that the unit has just been
        raised from the dead
        """
        self.created_at = time.time()

        params = (self.name, total_hp)
        self.log.info("SpiffyRPG: %s has been turned! setting HP to %s" % params)

    def get_min_base_attack_damage(self):
        return float(self.level * self.lower_damage_coefficient)

    def get_max_base_attack_damage(self):
        return float(self.level * self.upper_damage_coefficient)

    def get_attack_damage(self):
        return random.randrange(self.get_min_base_attack_damage(),
                                self.get_max_base_attack_damage())

    def attack(self, **kwargs):
        """
        Get attack info from attacker and
        target, then return all the details
        of the attack
        """
        attacker = self
        target = kwargs["target"]
        is_hit = False
        is_draw = False
        hit_word = "draw"

        """ Attacker weapon """
        attacker_weapon = attacker.get_equipped_weapon()
        attacker_weapon_type = attacker_weapon.item_type

        """ Target weapon """
        target_weapon = target.get_equipped_weapon()
        target_weapon_type = target_weapon.item_type

        """ Rock crushes Scissors """
        if attacker_weapon.is_rock() and \
                target_weapon.is_scissors():
            is_hit = True
            hit_word = "crushes"

        """ Scissors cuts Paper """
        if attacker_weapon.is_scissors() and \
                target_weapon.is_paper():
            is_hit = True
            hit_word = "cuts"

        """ Paper covers Rock """
        if attacker_weapon.is_paper() and \
                target_weapon.is_rock():
            is_hit = True
            hit_word = "covers"

        """ Lizard eats Paper """
        if attacker_weapon.is_lizard() and \
                target_weapon.is_paper():
            is_hit = True
            hit_word = "eats"

        """ Spock vaporizes Rock """
        if attacker_weapon.is_spock() and \
                target_weapon.is_rock():
            is_hit = True
            hit_word = "vaporizes"

        """ Lizard poisons Spock """
        if attacker_weapon.is_lizard() and \
                target_weapon.is_spock():
            is_hit = True
            hit_word = "poisons"

        """ Rock crushes Lizard """
        if attacker_weapon.is_rock() and \
                target_weapon.is_lizard():
            is_hit = True
            hit_word = "crushes"

        """ Spock smashes Scissors """
        if attacker_weapon.is_spock() and \
                target_weapon.is_scissors():
            is_hit = True
            hit_word = "smashes"

        """ Paper disproves Spock """
        if attacker_weapon.is_paper() and \
                target_weapon.is_spock():
            is_hit = True
            hit_word = "disproves"

        """ Scissors decapitate Lizard """
        if attacker_weapon.is_scissors() and \
                target_weapon.is_lizard():
            is_hit = True
            hit_word = "decapitates"

        """ Draw! """
        if attacker_weapon_type == target_weapon_type:
            is_draw = True

        damage = 0
        is_critical_strike = False

        if is_hit and not is_draw:
            attack = self.get_attack()
            is_critical_strike = attack["is_critical_strike"]
            damage = attack["damage"]

            """
            After determining that the hit landed, apply damage
            to to the target unit
            """
            target.apply_damage(damage=damage,
                                attacker=attacker)

        return {
            "is_hit": is_hit,
            "attacker_weapon": attacker_weapon,
            "target_weapon": target_weapon,
            "is_draw": is_draw,
            "hit_word": hit_word,
            "damage": damage,
            "is_critical_strike": is_critical_strike
        }

    def get_attack(self):
        damage = self.get_attack_damage()
        item = self.get_equipped_weapon()

        """ Critical Strikes """
        crit_chance = self.get_critical_strike_chance()
        is_critical_strike = random.randrange(1, 100) <= crit_chance

        if is_critical_strike:
            damage *= 2

        """ Undead bonus """
        self.log.info("SpiffyRPG: unit has effects %s" % self.effects)

        for effect in self.effects:
            if effect.operator == "+":
                outgoing_damage_adjustment = effect.outgoing_damage_adjustment

                if outgoing_damage_adjustment > 0:
                    decimal_adjustment = float(
                        outgoing_damage_adjustment) / float(100)
                    damage_adjustment = (damage * decimal_adjustment)
                    damage += damage_adjustment

                    self.log.info("SpiffyRPG: adding %s damage (undead) " % damage_adjustment)

        attack_info = {
            "damage": damage,
            "item": item,
            "is_critical_strike": is_critical_strike
        }

        return attack_info

    def is_undead(self):
        return self.has_effect_name(name="Undead")

    def is_necromancer(self):
        return self.has_effect_name(name="Necromancer")

    def is_archeologist(self):
        return self.has_effect_name(name="Archeologist")

    def is_paper_enthusiast(self):
        return self.has_effect_name(name="Paper Enthusiast")

    def is_running_with_scissors(self):
        return self.has_effect_name(name="Running With Scissors")

    def is_blue_tongue(self):
        return self.has_effect_name(name="Blue Tongue")

    def is_vulcan_embraced(self):
        return self.has_effect_name(name="Vulcan's Embrace")

    def has_effect_name(self, **kwargs):
        name = kwargs["name"]

        for effect in self.effects:
            if effect.name.lower() == name.lower():
                return True

    def get_critical_strike_chance(self):
        return self.critical_strike_chance

    def get_incoming_damage_adjustment(self, damage):
        """
        Effects can increase/decrease incoming damage,
        so find any effects this unit has and adjust the damage
        accordingly
        """
        adjusted_damage = damage

        if len(self.effects) > 0:
            for effect in self.effects:
                if effect.operator == "-":
                    inc_dmg_adjustment = int(effect.incoming_damage_adjustment)

                    if inc_dmg_adjustment > 0:
                        decimal_adjustment = float(
                            inc_dmg_adjustment) / float(100)
                        damage_amount = float(damage * decimal_adjustment)
                        adjusted_damage -= damage_amount

        return adjusted_damage

    def apply_damage(self, **kwargs):
        damage = kwargs["damage"]
        attacker = kwargs["attacker"]

        self.add_struck_unit(attacker)

        adjusted_damage = self.get_incoming_damage_adjustment(damage)
        self.hp = int(self.hp - adjusted_damage)

        """ Interrupt spell casting on damage """
        self.on_unit_spell_interrupted(unit=self, attacker=attacker)

        if self.hp <= 0:
            """
            Ensure that HP is never negative so players
            don't have to wait a long time to regenerate.
            """
            self.hp = 0

            self.on_unit_death()

    def on_unit_death(self):
        if self.is_player:
            self.announcer.unit_death()

        """
        This is used in unit_info as an alternative
        to displaying the time the unit has been alive,
        since it's dead now.
        """
        self.slain_at = time.time()

    def remove_effect_by_id(self, id):
        effects = []

        for e in effects:
            if e["id"] != id:
                effects.append(e)

        self.effects = effects

    def is_alive(self):
        return self.get_hp() > 0

    def is_dead(self):
        return self.get_hp() <= 0

    def kill(self):
        self.hp = 0
Esempio n. 6
0
    def __init__(self, **kwargs):
        """
        Start by initializing the unit as if it is an NPC,
        since for the most part, a player is identical to a NPC.
        """
        self.log = kwargs["log"]
        self.lower_damage_coefficient = 5
        self.upper_damage_coefficient = 10
        self.critical_strike_chance = 5
        self.slain_units = []
        self.winning_streak = []
        self.hostile_combatants = []
        self.raised_units = []
        self.battles = []
        self.units_that_have_struck_me = []

        """
        Build unit from db info
        """
        unit = kwargs["unit"]

        self.id = unit["id"]
        self.is_boss = unit["is_boss"] == 1 or unit["is_boss"]
        self.user_id = unit["user_id"]
        self.created_at = time.time()
        self.slain_at = None
        self.unit_type_name = unit["unit_type_name"]
        self.unit_type_id = unit["unit_type_id"]
        self.effects = unit["effects"]
        self.items = unit["items"]
        self.base_items = unit["base_items"]
        self.dialogue = unit["dialogue"]
        self.experience = unit["experience"]

        self.unit_level = UnitLevel()

        self.level = self.unit_level.get_level_by_xp(self.experience)
        self.combat_status = unit["combat_status"]
        self.is_casting_spell = False
        self.spell_interrupted_callback = None

        """ When unit is an NPC then use the unit name """
        self.name = unit["unit_name"]
        self.nick = self.name
        self.title = self.unit_type_name

        """
        If the unit has a non-zero user_id, then they are a player.
        When the unit is a NPC then their level depends
        on the dungeon min/max.
        """
        self.is_player = self.user_id > 0
        self.is_npc = self.user_id <= 0

        """
        Each unit should get some base items for their level.
        """
        self.populate_inventory_with_base_items()

        """
        The above line ensures that the unit always has at least the
        base weapons.
        """
        self.equip_random_weapon()

        """
        HP is a function of the unit's level, so
        the level must be determined prior.
        """
        self.hp = self.calculate_hp()

        """
        Apply unit effects
        """
        self.apply_unit_effects()

        """
        Some items have an effect on possession!
        """
        self.apply_item_effects()

        self.lootable_items = [
            item for item in self.items if not item.is_permanent]