示例#1
0
 def background(self, bg):
     if isinstance(bg, background.Background):
         self._background = bg
         self._background.owner = self
     elif isinstance(bg, type) and issubclass(bg, background.Background):
         self._background = bg(owner=self)
     elif isinstance(bg, str):
         try:
             self._background = find_content(
                 bg, valid_classes=[background.Background])(owner=self)
         except exceptions.ContentNotFound:
             msg = (
                 f'Background "{bg}" not defined. Please add it to ``background.py``'
             )
             self._background = background.Background(owner=self)
             warnings.warn(msg)
示例#2
0
 def race(self, newrace):
     if isinstance(newrace, race.Race):
         self._race = newrace
         self._race.owner = self
     elif isinstance(newrace, type) and issubclass(newrace, race.Race):
         self._race = newrace(owner=self)
     elif isinstance(newrace, str):
         try:
             self._race = find_content(newrace, valid_classes=[race.Race])(
                 owner=self
             )
         except exceptions.ContentNotFound:
             msg = f'Race "{newrace}" not defined. Please add it to ``race.py``'
             self._race = race.Race(owner=self)
             warnings.warn(msg)
     elif newrace is None:
         self._race = race.Race(owner=self)
示例#3
0
 def wild_shapes(self, new_shapes):
     actual_shapes = []
     # Retrieve the actual monster classes if possible
     for shape in new_shapes:
         if isinstance(shape, monsters.Monster):
             # Already a monster shape so just add it as is
             new_shape = shape
         else:
             # Not already a monster so see if we can find one
             try:
                 NewMonster = find_content(shape, valid_classes=[monsters.Monster])
                 new_shape = NewMonster()
             except exceptions.ContentNotFound:
                 msg = (
                     f"Wild shape '{shape}' not found. Please add it to"
                     " ``monsters.py``"
                 )
                 raise exceptions.MonsterError(msg)
         actual_shapes.append(new_shape)
     # Save the updated list for later
     self._wild_shapes = actual_shapes
示例#4
0
    def _resolve_mechanic(mechanic, SuperClass, warning_message=None):
        """Take a raw entry in a character sheet and turn it into a usable object.

        Eg: spells can be defined in many ways. This function accepts all
        of those options and returns an actual *Spell* class that can be
        used by a character::

            >>> _resolve_mechanic("mage_hand", SuperClass=spells.Spell)

            >>> _resolve_mechanic("mage_hand", SuperClass=None)

            >>> from dungeonsheets import spells
            >>> class MySpell(spells.Spell): pass
            >>> _resolve_mechanic(MySpell, SuperClass=spells.Spell)

            >>> _resolve_mechanic("hocus pocus", SuperClass=spells.Spell)

        The acceptable entries for *mechanic*, in priority order, are:
          1. A subclass of *SuperClass*
          2. A string with the name of defined content
          3. The name of an unknown spell (creates generic object using *factory*)

        *SuperClass* can be ``None`` to match any class, however this will
        raise an exception if more than one content type has this
        name. For example, "shield" can refer to both the armor or the
        spell, so ``_resolve_mechanic("shield")`` will raise an exception.

        Parameters
        ==========
        mechanic : str, type
          The thing to be resolved, either a string with the name of the
          mechanic, or a subclass of *ParentClass* describing the
          mechanic.
        SuperClass : type
          Class to determine whether *mechanic* should just be allowed
          through as is.
        error_message : str, optional
          A string whose ``str.format()`` method (receiving one positional
          argument *mechanic*) will be used for displaying a warning when an
          unknown mechanic is resolved. If omitted, no warning will be
          displayed.

        Returns
        =======
        Mechanic
          A class representing the resolved game mechanic. This will
          likely be a subclass of *SuperClass* if the other parameters are
          well behaved, but this is not enforced.

        """
        is_already_resolved = isinstance(mechanic, type) and issubclass(
            mechanic, SuperClass
        )
        if is_already_resolved:
            Mechanic = mechanic
        elif SuperClass is not None and isinstance(mechanic, SuperClass):
            # Has been instantiated for some reason
            Mechanic = type(mechanic)
        else:
            try:
                # Retrieve pre-defined mechanic
                valid_classes = [SuperClass] if SuperClass is not None else []
                Mechanic = find_content(mechanic, valid_classes=valid_classes)
            except ValueError:
                # No pre-defined mechanic available
                if warning_message is not None:
                    # Emit the warning
                    msg = warning_message.format(mechanic)
                    warnings.warn(msg)
                else:
                    # Create a generic message so we can make a docstring later.
                    msg = f'Mechanic "{mechanic}" not defined. Please add it.'
                # Create generic mechanic from the factory
                class_name = "".join([s.title() for s in mechanic.split("_")])
                mechanic_name = mechanic.replace("_", " ").title()
                attrs = {"name": mechanic_name, "__doc__": msg, "source": "Unknown"}
                Mechanic = type(class_name, (SuperClass,), attrs)
        return Mechanic
def make_gm_sheet(
    gm_file: Union[str, Path],
    output_format: str = "pdf",
    fancy_decorations: bool = False,
    debug: bool = False,
):
    """Prepare a PDF character sheet from the given character file.

    Parameters
    ----------
    gm_file
      The file with the gm_sheet definitions.
    output_format
      Either "pdf" or "epub" to generate a PDF file or an EPUB file.
    fancy_decorations
      Use fancy page layout and decorations for extra sheets, namely
      the dnd style file: https://github.com/rpgtex/DND-5e-LaTeX-Template.
    debug
      Provide extra info and preserve temporary files.

    """
    # Parse the GM file and filename
    gm_file = Path(gm_file)
    basename = gm_file.stem
    gm_props = readers.read_sheet_file(gm_file)
    session_title = gm_props.pop("session_title", f"GM Notes: {basename}")
    # Create the intro tex
    content_suffix = format_suffixes[output_format]
    content = [
        jinja_env.get_template(f"preamble.{content_suffix}").render(
            use_dnd_decorations=fancy_decorations,
            title=session_title,
        )
    ]
    # Add the party stats table and session summary
    party = []
    for char_file in gm_props.pop("party", []):
        # Check if it's already resolved
        if isinstance(char_file, Creature):
            member = char_file
        elif isinstance(char_file, type) and issubclass(char_file, Creature):
            # Needs to be instantiated
            member = char_file()
        else:
            # Resolve the file path
            char_file = Path(char_file)
            if not char_file.is_absolute():
                char_file = gm_file.parent / char_file
            char_file = char_file.resolve()
            # Load the character file
            log.debug(f"Loading party member: {char_file}")
            character_props = readers.read_sheet_file(char_file)
            member = _char.Character.load(character_props)
        party.append(member)
    summary = gm_props.pop("summary", "")
    content.append(
        create_party_summary_content(
            party,
            summary_rst=summary,
            suffix=content_suffix,
            use_dnd_decorations=fancy_decorations,
        ))
    # Parse any extra homebrew sections, etc.
    content.append(
        create_extra_gm_content(sections=gm_props.pop("extra_content", []),
                                suffix=content_suffix,
                                use_dnd_decorations=fancy_decorations))
    # Add the monsters
    monsters_ = []
    for monster in gm_props.pop("monsters", []):
        if isinstance(monster, monsters.Monster):
            # It's already a monster, so just add it
            new_monster = monster
        else:
            try:
                MyMonster = find_content(monster,
                                         valid_classes=[monsters.Monster])
            except exceptions.ContentNotFound:
                msg = f"Monster '{monster}' not found. Please add it to ``monsters.py``"
                warnings.warn(msg)
                continue
            else:
                new_monster = MyMonster()
        monsters_.append(new_monster)
    if len(monsters_) > 0:
        content.append(
            create_monsters_content(monsters_,
                                    suffix=content_suffix,
                                    use_dnd_decorations=fancy_decorations))
    # Add the random tables
    tables = [
        find_content(s, valid_classes=[random_tables.RandomTable])
        for s in gm_props.pop("random_tables", [])
    ]
    content.append(
        create_random_tables_content(
            tables=tables,
            suffix=content_suffix,
            use_dnd_decorations=fancy_decorations,
        ))
    # Add the closing TeX
    content.append(
        jinja_env.get_template(f"postamble.{format_suffixes[output_format]}").
        render(use_dnd_decorations=fancy_decorations))
    # Warn about any unhandled sheet properties
    gm_props.pop("dungeonsheets_version")
    gm_props.pop("sheet_type")
    if len(gm_props.keys()) > 0:
        msg = f"Unhandled attributes in '{str(gm_file)}': {','.join(gm_props.keys())}"
        log.warning(msg)
        warnings.warn(msg)
    # Produce the combined output depending on the format requested
    if output_format == "pdf":
        # Typeset combined LaTeX file
        try:
            if len(content) > 2:
                latex.create_latex_pdf(
                    tex="".join(content),
                    basename=basename,
                    keep_temp_files=debug,
                    use_dnd_decorations=fancy_decorations,
                )
        except exceptions.LatexNotFoundError:
            log.warning(f"``pdflatex`` not available. Skipping {basename}")
    elif output_format == "epub":
        chapters = {session_title: "".join(content)}
        # Make sheets in the epub for each party member
        for char in party:
            char_html = make_character_content(
                char, "html", fancy_decorations=fancy_decorations)
            chapters[char.name] = "".join(char_html)
        # Create the combined HTML file
        epub.create_epub(
            chapters=chapters,
            basename=basename,
            title=session_title,
            use_dnd_decorations=fancy_decorations,
        )
    else:
        raise exceptions.UnknownOutputFormat(
            f"Unknown output format requested: {output_format}. Valid options are:"
            " 'pdf', 'epub'")