Esempio n. 1
0
    def run(self, autoctx):
        super().run(autoctx)
        spell = self.spell
        if spell is None:
            autoctx.meta_queue(f"**Error**: Spell {self.id} not found.")
            return CastSpellResult(success=False)
        cast_level = self.level if self.level is not None else spell.level
        if not spell.level <= cast_level <= 9:
            autoctx.meta_queue(
                f"**Error**: Unable to cast {spell.name} at level {cast_level} (invalid level)."
            )
            return CastSpellResult(success=False, spell=spell)
        if autoctx.is_spell:
            autoctx.meta_queue(
                f"**Error**: Unable to cast another spell inside a spell.")
            return CastSpellResult(success=False, spell=spell)

        if spell.automation and spell.automation.effects:
            # save old autoctx values
            old_ab_override = autoctx.ab_override
            old_dc_override = autoctx.dc_override
            old_spell_override = autoctx.evaluator.builtins.get('spell')
            old_level_override = autoctx.spell_level_override

            # run the spell using the given values
            if self.attack_bonus is not None:
                autoctx.ab_override = autoctx.parse_intexpression(
                    self.attack_bonus)
            if self.dc is not None:
                autoctx.dc_override = autoctx.parse_intexpression(self.dc)
            if self.casting_mod is not None:
                autoctx.evaluator.builtins[
                    'spell'] = autoctx.parse_intexpression(self.casting_mod)
            if self.level is not None:
                autoctx.spell_level_override = self.level
            autoctx.spell = spell
            results = self.run_children(spell.automation.effects, autoctx)

            # and restore them
            autoctx.ab_override = old_ab_override
            autoctx.dc_override = old_dc_override
            autoctx.evaluator.builtins['spell'] = old_spell_override
            autoctx.spell_level_override = old_level_override
            autoctx.spell = None
        else:  # copied from Spell.cast
            results = []
            autoctx.queue(smart_trim(spell.description))
            autoctx.push_embed_field(title=spell.name)
            if cast_level != spell.level and spell.higherlevels:
                autoctx.queue(smart_trim(spell.higherlevels))
                autoctx.push_embed_field(title="At Higher Levels")

        return CastSpellResult(success=True, spell=spell, children=results)
Esempio n. 2
0
    async def subclass(self, ctx, *, name: str):
        """Looks up a subclass."""
        result: gamedata.Subclass = await self._lookup_search3(
            ctx, {'class': compendium.subclasses}, name,
            query_type='subclass'
        )

        embed = EmbedWithAuthor(ctx)
        embed.url = result.url
        embed.title = result.name
        embed.description = smart_trim(result.description, 2048)

        for level in result.levels:
            for feature in level:
                text = smart_trim(feature.text, 1024)
                embed.add_field(name=feature.name, value=text, inline=False)

        handle_source_footer(
            embed, result, f"Use {ctx.prefix}classfeat to look up a feature if it is cut off",
            add_source_str=True
        )

        await (await self._get_destination(ctx)).send(embed=embed)
Esempio n. 3
0
    def _get_custom_counters(self):
        out = []

        for cons in self.character_data['consumables']:
            live_id = f"{cons['id']}-{cons['typeId']}"
            display_type = 'bubble' if cons['max'] < 7 else None
            reset = RESET_MAP.get(cons['reset'], 'long')
            name = cons['name'].replace('\u2019', "'").strip()
            desc = None
            source_feature_type = cons['sourceFeatureType']
            source_feature_id = cons['sourceFeatureId']

            if cons['desc'] is not None:
                desc = smart_trim(
                    html_to_md(cons['desc'].replace('\u2019', "'")))

            source_feature = compendium.lookup_entity(source_feature_type,
                                                      source_feature_id)
            log.debug(
                f"Processed counter named {name!r} for feature {source_feature}"
            )

            if source_feature is None:
                log.warning(
                    f"Could not find source feature ({source_feature_type}, {source_feature_id}) for counter "
                    f"named {name!r}")

            if cons['max'] and name:  # don't make counters with a range of 0 - 0, or blank named counters
                out.append(
                    CustomCounter(None,
                                  name,
                                  cons['value'],
                                  minv='0',
                                  maxv=str(cons['max']),
                                  reset=reset,
                                  display_type=display_type,
                                  live_id=live_id,
                                  desc=desc,
                                  ddb_source_feature_type=source_feature_type,
                                  ddb_source_feature_id=source_feature_id))

        return [cc.to_dict() for cc in out]
Esempio n. 4
0
File: spell.py Progetto: avrae/avrae
    async def cast(self, ctx, caster, targets, args, combat=None):
        """
        Casts this spell.

        :param ctx: The context of the casting.
        :param caster: The caster of this spell.
        :type caster: :class:`~cogs5e.models.sheet.statblock.StatBlock`
        :param targets: A list of targets
        :type targets: list of :class:`~cogs5e.models.sheet.statblock.StatBlock`
        :param args: Args
        :type args: :class:`~utils.argparser.ParsedArguments`
        :param combat: The combat the spell was cast in, if applicable.
        :rtype: CastResult
        """

        # generic args
        l = args.last('l', self.level, int)
        i = args.last('i', type_=bool)
        title = args.last('title')
        nopact = args.last('nopact', type_=bool)

        # meta checks
        if not self.level <= l <= 9:
            raise SpellException("Invalid spell level.")

        # caster spell-specific overrides
        dc_override = None
        ab_override = None
        spell_override = None
        is_prepared = True
        spellbook_spell = caster.spellbook.get_spell(self)
        if spellbook_spell is not None:
            dc_override = spellbook_spell.dc
            ab_override = spellbook_spell.sab
            spell_override = spellbook_spell.mod
            is_prepared = spellbook_spell.prepared

        if not i:
            # if I'm a warlock, and I didn't have any slots of this level anyway (#655)
            # automatically scale up to our pact slot level (or the next available level s.t. max > 0)
            if l > 0 \
                    and l == self.level \
                    and not caster.spellbook.get_max_slots(l) \
                    and not caster.spellbook.can_cast(self, l):
                if caster.spellbook.pact_slot_level is not None:
                    l = caster.spellbook.pact_slot_level
                else:
                    l = next((sl for sl in range(l, 6)
                              if caster.spellbook.get_max_slots(sl)),
                             l)  # only scale up to l5
                args['l'] = l

            # can I cast this spell?
            if not caster.spellbook.can_cast(self, l):
                embed = EmbedWithAuthor(ctx)
                embed.title = "Cannot cast spell!"
                if not caster.spellbook.get_slots(l):
                    # out of spell slots
                    err = (
                        f"You don't have enough level {l} slots left! Use `-l <level>` to cast at a different "
                        f"level, `{ctx.prefix}g lr` to take a long rest, or `-i` to ignore spell slots!"
                    )
                elif self.name not in caster.spellbook:
                    # don't know spell
                    err = (
                        f"You don't know this spell! Use `{ctx.prefix}sb add {self.name}` to add it to your "
                        f"spellbook, or pass `-i` to ignore restrictions.")
                else:
                    # ?
                    err = (
                        "Not enough spell slots remaining, or spell not in known spell list!\n"
                        f"Use `{ctx.prefix}game longrest` to restore all spell slots if this is a character, "
                        f"or pass `-i` to ignore restrictions.")
                embed.description = err
                if l > 0:
                    embed.add_field(name="Spell Slots",
                                    value=caster.spellbook.remaining_casts_of(
                                        self, l))
                return CastResult(embed=embed,
                                  success=False,
                                  automation_result=None)

            # #1000: is this spell prepared (soft check)?
            if not is_prepared:
                skip_prep_conf = await confirm(
                    ctx,
                    f"{self.name} is not prepared. Do you want to cast it anyway? (Reply with yes/no)",
                    delete_msgs=True)
                if not skip_prep_conf:
                    embed = EmbedWithAuthor(
                        ctx,
                        title=f"Cannot cast spell!",
                        description=
                        f"{self.name} is not prepared! Prepare it on your character sheet and use "
                        f"`{ctx.prefix}update` to mark it as prepared, or use `-i` to ignore restrictions."
                    )
                    return CastResult(embed=embed,
                                      success=False,
                                      automation_result=None)

            # use resource
            caster.spellbook.cast(self, l, pact=not nopact)

        # base stat stuff
        mod_arg = args.last("mod", type_=int)
        with_arg = args.last("with")
        stat_override = ''
        if mod_arg is not None:
            mod = mod_arg
            prof_bonus = caster.stats.prof_bonus
            dc_override = 8 + mod + prof_bonus
            ab_override = mod + prof_bonus
            spell_override = mod
        elif with_arg is not None:
            if with_arg not in STAT_ABBREVIATIONS:
                raise InvalidArgument(
                    f"{with_arg} is not a valid stat to cast with.")
            mod = caster.stats.get_mod(with_arg)
            dc_override = 8 + mod + caster.stats.prof_bonus
            ab_override = mod + caster.stats.prof_bonus
            spell_override = mod
            stat_override = f" with {verbose_stat(with_arg)}"

        # begin setup
        embed = discord.Embed()
        if title:
            embed.title = title.replace('[name]', caster.name) \
                .replace('[aname]', self.name) \
                .replace('[sname]', self.name) \
                .replace('[verb]', 'casts')  # #1514, [aname] is action name now, #1587, add verb to action/cast
        else:
            embed.title = f"{caster.get_title_name()} casts {self.name}{stat_override}!"
        if targets is None:
            targets = [None]

        # concentration
        noconc = args.last("noconc", type_=bool)
        conc_conflict = None
        conc_effect = None
        if all((self.concentration, isinstance(caster, BaseCombatant), combat,
                not noconc)):
            duration = args.last('dur', self.get_combat_duration(), int)
            conc_effect = Effect.new(combat, caster, self.name, duration, "",
                                     True)
            effect_result = caster.add_effect(conc_effect)
            conc_conflict = effect_result['conc_conflict']

        # run
        automation_result = None
        if self.automation and self.automation.effects:
            title = f"{caster.name} cast {self.name}!"
            automation_result = await self.automation.run(
                ctx,
                embed,
                caster,
                targets,
                args,
                combat,
                self,
                conc_effect=conc_effect,
                ab_override=ab_override,
                dc_override=dc_override,
                spell_override=spell_override,
                title=title)
        else:  # no automation, display spell description
            phrase = args.join('phrase', '\n')
            if phrase:
                embed.description = f"*{phrase}*"
            embed.add_field(name="Description",
                            value=smart_trim(self.description),
                            inline=False)
            embed.set_footer(text="No spell automation found.")

        if l != self.level and self.higherlevels:
            embed.add_field(name="At Higher Levels",
                            value=smart_trim(self.higherlevels),
                            inline=False)

        if l > 0 and not i:
            embed.add_field(name="Spell Slots",
                            value=caster.spellbook.remaining_casts_of(self, l))

        if conc_conflict:
            conflicts = ', '.join(e.name for e in conc_conflict)
            embed.add_field(name="Concentration",
                            value=f"Dropped {conflicts} due to concentration.")

        if 'thumb' in args:
            embed.set_thumbnail(url=maybe_http_url(args.last('thumb', '')))
        elif self.image:
            embed.set_thumbnail(url=self.image)

        add_fields_from_args(embed, args.get('f'))
        gamedata.lookuputils.handle_source_footer(embed,
                                                  self,
                                                  add_source_str=False)

        return CastResult(embed=embed,
                          success=True,
                          automation_result=automation_result)