Beispiel #1
0
async def select_monster_full(ctx, name, cutoff=5, return_key=False, pm=False, message=None, list_filter=None,
                              return_metadata=False):
    """
    Gets a Monster from the compendium and active bestiary/ies.
    """
    try:
        bestiary = await Bestiary.from_ctx(ctx)
        custom_monsters = bestiary.monsters
        bestiary_id = bestiary.id
    except NoActiveBrew:
        custom_monsters = []
        bestiary_id = None
    choices = list(itertools.chain(c.monster_mash, custom_monsters))
    if ctx.guild:
        async for servbestiary in ctx.bot.mdb.bestiaries.find({"server_active": str(ctx.guild.id)}, ['monsters']):
            choices.extend(
                Monster.from_bestiary(m) for m in servbestiary['monsters'] if servbestiary['_id'] != bestiary_id)

    def get_homebrew_formatted_name(monster):
        if monster.source == 'homebrew':
            return f"{monster.name} ({HOMEBREW_EMOJI})"
        return monster.name

    return await search_and_select(ctx, choices, name, lambda e: e.name, cutoff, return_key, pm, message, list_filter,
                                   selectkey=get_homebrew_formatted_name, return_metadata=return_metadata)
Beispiel #2
0
def migrate(bestiary):
    sha256 = hashlib.sha256()
    hash_str = json.dumps(bestiary['monsters']).encode() \
               + bestiary['name'].encode() \
               + str(bestiary.get('desc')).encode()
    sha256.update(hash_str)

    active = [bestiary['owner']] if bestiary.get('active') else []
    server_active = []
    for serv_sub in bestiary.get('server_active', []):
        server_active.append({
            "subscriber_id": bestiary['owner'],
            "guild_id": serv_sub
        })

    for monster in bestiary['monsters']:
        for key in ('traits', 'actions', 'reactions', 'legactions'):
            for trait in monster[key]:
                if 'attacks' in trait:
                    del trait[
                        'attacks']  # why did we store this in the first place?

    monsters = [Monster.from_bestiary(m) for m in bestiary['monsters']]
    new_bestiary = Bestiary(None, sha256.hexdigest(), bestiary['critterdb_id'],
                            [bestiary['owner']], active, server_active,
                            bestiary['name'], monsters, bestiary.get('desc'))
    return new_bestiary
Beispiel #3
0
    def __init__(self):
        self.cfeats = self.load_json('srd-classfeats.json', [])
        self.classes = self.load_json('srd-classes.json', [])
        self.conditions = self.load_json('conditions.json', [])
        self.feats = self.load_json('srd-feats.json', [])
        self.itemprops = self.load_json('itemprops.json', {})
        self.monsters = self.load_json('srd-bestiary.json', [])
        self.names = self.load_json('names.json', [])
        self.rules = self.load_json('rules.json', [])

        self.spells = [Spell.from_data(r) for r in self.load_json('srd-spells.json', [])]
        self.backgrounds = [Background.from_data(b) for b in self.load_json('srd-backgrounds.json', [])]
        self.items = [i for i in self.load_json('srd-items.json', []) if i.get('type') is not '$']
        self.monster_mash = [Monster.from_data(m) for m in self.monsters]

        self.subclasses = self.load_subclasses()

        srd_races = self.load_json('srd-races.json', [])
        self.fancyraces = [Race.from_data(r) for r in srd_races]
        self.rfeats = []
        for race in srd_races:
            for entry in race['entries']:
                if isinstance(entry, dict) and 'name' in entry:
                    temp = {'name': "{}: {}".format(race['name'], entry['name']),
                            'text': parse_data_entry(entry['entries']), 'srd': race['srd']}
                    self.rfeats.append(temp)
Beispiel #4
0
 async def bestiary_from_critterdb(self, url):
     log.info(f"Getting bestiary ID {url}...")
     index = 1
     creatures = []
     async with aiohttp.ClientSession() as session:
         for _ in range(100):  # 100 pages max
             log.info(f"Getting page {index} of {url}...")
             async with session.get(
                     f"http://critterdb.com/api/publishedbestiaries/{url}/creatures/{index}"
             ) as resp:
                 if not 199 < resp.status < 300:
                     raise ExternalImportError(
                         "Error importing bestiary. Are you sure the link is right?"
                     )
                 raw = await resp.json()
                 if not raw:
                     break
                 creatures.extend(raw)
                 index += 1
         async with session.get(
                 f"http://critterdb.com/api/publishedbestiaries/{url}"
         ) as resp:
             raw = await resp.json()
             name = raw['name']
     parsed_creatures = [Monster.from_critterdb(c) for c in creatures]
     return Bestiary(url, name, parsed_creatures)
Beispiel #5
0
    async def from_critterdb(cls, ctx, url):
        log.info(f"Getting bestiary ID {url}...")
        index = 1
        creatures = []
        sha256_hash = hashlib.sha256()
        async with aiohttp.ClientSession() as session:
            for _ in range(100):  # 100 pages max
                log.info(f"Getting page {index} of {url}...")
                async with session.get(
                        f"http://critterdb.com/api/publishedbestiaries/{url}/creatures/{index}"
                ) as resp:
                    if not 199 < resp.status < 300:
                        raise ExternalImportError(
                            "Error importing bestiary: HTTP error. Are you sure the link is right?"
                        )
                    try:
                        raw_creatures = await resp.json()
                        sha256_hash.update(await resp.read())
                    except (ValueError, aiohttp.ContentTypeError):
                        raise ExternalImportError(
                            "Error importing bestiary: bad data. Are you sure the link is right?"
                        )
                    if not raw_creatures:
                        break
                    creatures.extend(raw_creatures)
                    index += 1
            async with session.get(
                    f"http://critterdb.com/api/publishedbestiaries/{url}"
            ) as resp:
                try:
                    raw = await resp.json()
                except (ValueError, aiohttp.ContentTypeError):
                    raise ExternalImportError(
                        "Error importing bestiary metadata. Are you sure the link is right?"
                    )
                name = raw['name']
                desc = raw['description']
                sha256_hash.update(name.encode() + desc.encode())

        # try and find a bestiary by looking up upstream|hash
        # if it exists, return it
        # otherwise commit a new one to the db and return that
        sha256 = sha256_hash.hexdigest()
        log.debug(f"Bestiary hash: {sha256}")
        existing_bestiary = await ctx.bot.mdb.bestiaries.find_one({
            "upstream":
            url,
            "sha256":
            sha256
        })
        if existing_bestiary:
            log.info("This bestiary already exists, subscribing")
            existing_bestiary = Bestiary.from_dict(existing_bestiary)
            await existing_bestiary.subscribe(ctx)
            return existing_bestiary

        parsed_creatures = [Monster.from_critterdb(c) for c in creatures]
        b = cls(None, sha256, url, [], [], [], name, parsed_creatures, desc)
        await b.write_to_db(ctx)
        return b
Beispiel #6
0
 async def load_monsters(self, ctx):
     if not self._monsters:
         bestiary = await ctx.bot.mdb.bestiaries.find_one(
             {"_id": self.id}, projection=['monsters'])
         self._monsters = [
             Monster.from_bestiary(m) for m in bestiary['monsters']
         ]
     return self._monsters
Beispiel #7
0
    def load_common(self):
        self.backgrounds = [Background.from_data(b) for b in self.srd_backgrounds]
        self.fancyraces = [Race.from_data(r) for r in self.srd_races]
        self.monster_mash = [Monster.from_data(m) for m in self.monsters]
        self.spells = [Spell.from_data(s) for s in self.srd_spells]

        self.items = [i for i in self.srd_items if i.get('type') is not '$']

        self.rfeats = self.load_rfeats()
        self.subclasses = self.load_subclasses()
Beispiel #8
0
def migrate_monster(old_monster):
    def spaced_to_camel(spaced):
        return re.sub(r"\s+(\w)", lambda m: m.group(1).upper(), spaced.lower())

    for old_key in ('raw_saves', 'raw_skills'):
        if old_key in old_monster:
            del old_monster[old_key]

    if 'spellcasting' in old_monster and old_monster['spellcasting']:
        old_spellcasting = old_monster.pop('spellcasting')
        old_monster['spellbook'] = Spellbook(
            {}, {}, [SpellbookSpell(s) for s in old_spellcasting['spells']],
            old_spellcasting['dc'], old_spellcasting['attackBonus'],
            old_spellcasting['casterLevel']).to_dict()
    else:
        old_monster['spellbook'] = Spellbook({}, {}, []).to_dict()

    base_stats = BaseStats(0, old_monster.pop('strength'),
                           old_monster.pop('dexterity'),
                           old_monster.pop('constitution'),
                           old_monster.pop('intelligence'),
                           old_monster.pop('wisdom'),
                           old_monster.pop('charisma'))
    old_monster['ability_scores'] = base_stats.to_dict()

    old_saves = old_monster.pop('saves')
    saves = Saves.default(base_stats)
    save_updates = {}
    for save, value in old_saves.items():
        if value != saves[save]:
            save_updates[save] = value
    saves.update(save_updates)
    old_monster['saves'] = saves.to_dict()

    old_skills = old_monster.pop('skills')
    skills = Skills.default(base_stats)
    skill_updates = {}
    for skill, value in old_skills.items():
        name = spaced_to_camel(skill)
        if value != skills[name]:
            skill_updates[name] = value
    skills.update(skill_updates)
    old_monster['skills'] = skills.to_dict()

    new_monster = Monster.from_bestiary(old_monster)
    return new_monster
Beispiel #9
0
 def __init__(self):
     with open('./res/conditions.json', 'r') as f:
         self.conditions = json.load(f)
     with open('./res/rules.json', 'r') as f:
         self.rules = json.load(f)
     with open('./res/feats.json', 'r') as f:
         self.feats = json.load(f)
     with open('./res/races.json', 'r') as f:
         _raw = json.load(f)
         self.rfeats = []
         self.fancyraces = [Race.from_data(r) for r in _raw]
         for race in _raw:
             for entry in race['entries']:
                 if isinstance(entry, dict) and 'name' in entry:
                     temp = {
                         'name': "{}: {}".format(race['name'],
                                                 entry['name']),
                         'text': parse_data_entry(entry['entries']),
                         'srd': race['srd']
                     }
                     self.rfeats.append(temp)
     with open('./res/classes.json', 'r') as f:
         self.classes = json.load(f)
     with open('./res/classfeats.json') as f:
         self.cfeats = json.load(f)
     with open('./res/bestiary.json', 'r') as f:
         self.monsters = json.load(f)
         self.monster_mash = [Monster.from_data(m) for m in self.monsters]
     with open('./res/spells.json', 'r') as f:
         self.spells = [Spell.from_data(r) for r in json.load(f)]
     with open('./res/items.json', 'r') as f:
         _items = json.load(f)
         self.items = [i for i in _items if i.get('type') is not '$']
     with open('./res/backgrounds.json', 'r') as f:
         self.backgrounds = [Background.from_data(b) for b in json.load(f)]
     self.subclasses = self.load_subclasses()
     with open('./res/itemprops.json', 'r') as f:
         self.itemprops = json.load(f)
     with open('./res/names.json', 'r') as f:
         self.names = json.load(f)
Beispiel #10
0
 def from_dict(cls, d):
     if 'monsters' in d:
         d['monsters'] = [Monster.from_bestiary(m) for m in d['monsters']]
     if 'published' not in d:  # versions prior to v1.5.11 don't have this tag, default to True
         d['published'] = True
     return cls(**d)
Beispiel #11
0
def _monster_factory(data):
    ability_scores = BaseStats(
        data['stats']['proficiencyBonus'] or 0,
        data['stats']['abilityScores']['strength'] or 10,
        data['stats']['abilityScores']['dexterity'] or 10,
        data['stats']['abilityScores']['constitution'] or 10,
        data['stats']['abilityScores']['intelligence'] or 10,
        data['stats']['abilityScores']['wisdom'] or 10,
        data['stats']['abilityScores']['charisma'] or 10)
    cr = {
        0.125: '1/8',
        0.25: '1/4',
        0.5: '1/2'
    }.get(data['stats']['challengeRating'],
          str(data['stats']['challengeRating']))
    num_hit_die = data['stats']['numHitDie']
    hit_die_size = data['stats']['hitDieSize']
    con_by_level = num_hit_die * ability_scores.get_mod('con')
    hp = floor(((hit_die_size + 1) / 2) * num_hit_die) + con_by_level
    hitdice = f"{num_hit_die}d{hit_die_size} + {con_by_level}"

    proficiency = data['stats']['proficiencyBonus']
    if proficiency is None:
        raise ExternalImportError(
            f"Monster's proficiency bonus is nonexistent ({data['name']}).")

    skills = Skills.default(ability_scores)
    skill_updates = {}
    for skill in data['stats']['skills']:
        name = spaced_to_camel(skill['name'])
        if skill['proficient']:
            mod = skills[name].value + proficiency
        else:
            mod = skill.get('value')
        if mod is not None:
            skill_updates[name] = mod
    skills.update(skill_updates)

    saves = Saves.default(ability_scores)
    save_updates = {}
    for save in data['stats']['savingThrows']:
        name = save['ability'].lower() + 'Save'
        if save['proficient']:
            mod = saves.get(name).value + proficiency
        else:
            mod = save.get('value')
        if mod is not None:
            save_updates[name] = mod
    saves.update(save_updates)

    attacks = []
    traits, atks = parse_critterdb_traits(data, 'additionalAbilities')
    attacks.extend(atks)
    actions, atks = parse_critterdb_traits(data, 'actions')
    attacks.extend(atks)
    reactions, atks = parse_critterdb_traits(data, 'reactions')
    attacks.extend(atks)
    legactions, atks = parse_critterdb_traits(data, 'legendaryActions')
    attacks.extend(atks)

    attacks = AttackList.from_dict(attacks)
    spellcasting = parse_critterdb_spellcasting(traits, ability_scores)

    return Monster(data['name'],
                   data['stats']['size'],
                   data['stats']['race'],
                   data['stats']['alignment'],
                   data['stats']['armorClass'],
                   data['stats']['armorType'],
                   hp,
                   hitdice,
                   data['stats']['speed'],
                   ability_scores,
                   cr,
                   data['stats']['experiencePoints'],
                   None,
                   ', '.join(data['stats']['senses']),
                   data['stats']['damageVulnerabilities'],
                   data['stats']['damageResistances'],
                   data['stats']['damageImmunities'],
                   data['stats']['conditionImmunities'],
                   saves,
                   skills,
                   data['stats']['languages'],
                   traits,
                   actions,
                   reactions,
                   legactions,
                   data['stats']['legendaryActionsPerRound'],
                   True,
                   'homebrew',
                   attacks,
                   data['flavor']['nameIsProper'],
                   data['flavor']['imageUrl'],
                   spellcasting=spellcasting)
Beispiel #12
0
 def __init__(self):
     with open('./res/conditions.json', 'r') as f:
         self.conditions = json.load(f)
     with open('./res/rules.json', 'r') as f:
         self.rules = json.load(f)
     with open('./res/feats.json', 'r') as f:
         self.feats = json.load(f)
     with open('./res/races.json', 'r') as f:
         _raw = json.load(f)
         self.rfeats = []
         self.races = copy.deepcopy(_raw)
         self.fancyraces = [Race.from_data(r) for r in self.races]
         for race in _raw:
             for entry in race['entries']:
                 if isinstance(entry, dict) and 'name' in entry:
                     temp = {
                         'name': "{}: {}".format(race['name'],
                                                 entry['name']),
                         'text': parse_data_entry(entry['entries']),
                         'srd': race['srd']
                     }
                     self.rfeats.append(temp)
     with open('./res/classes.json', 'r', encoding='utf-8-sig') as f:
         _raw = json.load(f)
         self.cfeats = []
         self.classes = copy.deepcopy(_raw)
         for _class in _raw:
             for level in _class.get('classFeatures', []):
                 for feature in level:
                     fe = {
                         'name': f"{_class['name']}: {feature['name']}",
                         'text': parse_data_entry(feature['entries']),
                         'srd': _class['srd']
                     }
                     self.cfeats.append(fe)
                     options = [
                         e for e in feature['entries']
                         if isinstance(e, dict) and e['type'] == 'options'
                     ]
                     for option in options:
                         for opt_entry in option.get('entries', []):
                             fe = {
                                 'name':
                                 f"{_class['name']}: {feature['name']}: {_resolve_name(opt_entry)}",
                                 'text':
                                 f"{_parse_prereqs(opt_entry)}{parse_data_entry(opt_entry['entries'])}",
                                 'srd': _class['srd']
                             }
                             self.cfeats.append(fe)
             for subclass in _class.get('subclasses', []):
                 for level in subclass.get('subclassFeatures', []):
                     for feature in level:
                         options = [
                             f for f in feature.get('entries', []) if
                             isinstance(f, dict) and f['type'] == 'options'
                         ]  # battlemaster only
                         for option in options:
                             for opt_entry in option.get('entries', []):
                                 fe = {
                                     'name':
                                     f"{_class['name']}: {option['name']}: "
                                     f"{_resolve_name(opt_entry)}",
                                     'text':
                                     parse_data_entry(opt_entry['entries']),
                                     'srd':
                                     subclass.get('srd', False)
                                 }
                                 self.cfeats.append(fe)
                         for entry in feature.get('entries', []):
                             if not isinstance(entry, dict): continue
                             if not entry.get('type') == 'entries': continue
                             fe = {
                                 'name':
                                 f"{_class['name']}: {subclass['name']}: {entry['name']}",
                                 'text': parse_data_entry(entry['entries']),
                                 'srd': subclass.get('srd', False)
                             }
                             self.cfeats.append(fe)
                             options = [
                                 e for e in entry['entries']
                                 if isinstance(e, dict)
                                 and e['type'] == 'options'
                             ]
                             for option in options:
                                 for opt_entry in option.get('entries', []):
                                     fe = {
                                         'name':
                                         f"{_class['name']}: {subclass['name']}: {entry['name']}: "
                                         f"{_resolve_name(opt_entry)}",
                                         'text':
                                         parse_data_entry(
                                             opt_entry['entries']),
                                         'srd':
                                         subclass.get('srd', False)
                                     }
                                     self.cfeats.append(fe)
     with open('./res/bestiary.json', 'r') as f:
         self.monsters = json.load(f)
         self.monster_mash = [Monster.from_data(m) for m in self.monsters]
     with open('./res/spells.json', 'r') as f:
         self.spells = json.load(f)
     with open('./res/items.json', 'r') as f:
         _items = json.load(f)
         self.items = [i for i in _items if i.get('type') is not '$']
     with open('./res/auto_spells.json', 'r') as f:
         self.autospells = json.load(f)
     with open('./res/backgrounds.json', 'r') as f:
         self.backgrounds = json.load(f)
     self.subclasses = self.load_subclasses()
     with open('./res/itemprops.json', 'r') as f:
         self.itemprops = json.load(f)
Beispiel #13
0
 def from_raw(cls, _id, raw):
     monsters = [Monster.from_bestiary(m) for m in raw['monsters']]
     return cls(_id, raw['name'], monsters)
Beispiel #14
0
 def from_dict(cls, d):
     if 'monsters' in d:
         d['monsters'] = [Monster.from_bestiary(m) for m in d['monsters']]
     return cls(**d)