Drama — Balladeer 0.39.0

# Drama

The previous sections of this manual have introduced you to the classes which Balladeer defines so that you can build your narrative.

The place where you write your code is called the Drama.

class Drama(self, *args, world=None, config=None, **kwargs)

One important aspect of the Drama class is that inherits from `Entity`. This means you can assign `State` to it.

It’s a good idea to customize your drama with Python properties so that you have access to useful characteristics. One of the supplied examples shows how the Drama class keeps track of the current location of the narrative.

        @property
        def here(self):
            return self.get_state("Spot")

In addition to properties, there are three ways of adding functionality.

- Overriding the Interlude method.

- As a Directive handler.

- In a Dialogue generator method.

## Interlude

In Balladeer the interlude gets called every turn of a Story. This makes it the ideal place to put game rules which are invariant.

drama.interlude(self, *args, **kwargs)

  Apply rules once every story turn.

  Returns:

    The Drama object

  Return type:

    `Entity`.

## Directive handler

SpeechMark has a feature called directives. When a drama object implements a directive handler, it may be invoked from Speech.

Handlers must be instance methods whose name begins with the prefix `on_`. They take an argument which is the primary entity of the directive. Any other entities are supplied as positional arguments.

drama.on_xxxing(self, entity: Entity, *args: tuple[Entity], **kwargs):

  Handles the SpeechMark directive ‘xxxing’.

  Parameters:

    - entity – The primary entity of the directive

    - args – The associated entites of the directive

  Return type:

    None

This is a snippet from the Balladeer examples. In the scene, one character attacks another:

    <WEAPON.attacking@FIGHTER_2>

        _Whack!_

Here is the Drama class with the corresponding handler:

    class Fight(Drama):
        def on_attacking(self, entity: Entity, *args: tuple[Entity], **kwargs):
            for enemy in args:
                enemy.set_state(0)

## Dialogue generator

Dialogue generators must be instance methods whose name begins with the prefix `do_`. They take keyword arguments, each of which must have an annotation.

The method must contain a docstring which defines the text that triggers the method. Docstrings may contain format specifiers which reference the keyword arguments.

drama.do_xxx(self, this, text, director, *args, **kwargs):

  Annotations for keyword arguments may be:

  - An iterable type eg: an Enum/State.

  - A string which gives attribute access via the drama object to an iterable of entities.

  This method is a generator of Speech objects. The speech objects must be of these three classes:

  `Prologue`

    Speech which belongs at the top of a page.

  `Dialogue`

    Speech interleaved with the main content of the scene.

  `Epilogue`

    Speech which belongs at the bottom of a page.

This sounds complicated, but it’s easily demonstrated with a couple of examples.

This `do_move` method declares in its docstring that text like `n`, `north`, `go n` or `go north` will activate the method. The method will be invoked with the corresponding `Compass` class member supplied via the heading parameter.

        def do_move(self, this, text, director, *args, heading: Compass, **kwargs):
            """
            {heading.name} | {heading.label}
            go {heading.name} | go {heading.label}

            """
            options = {
                compass: spot for compass, spot, transit in self.world.map.options(self.here)
            }
            if heading not in options:
                yield Prologue(f"<> You can't go {heading.label} from here.")
            else:
                self.set_state(options[heading])

Next we have `do_drop`, an example of an annotation which is a string accessor on the drama object. The text which triggers the method is `drop` or `discard`, so long as it’s followed by the name of any item in the inventory location of the drama object’s `WorldBuilder`.

        def do_drop(
            self, this, text, director, *args, item: "world.statewise[Spot.inventory]", **kwargs
        ):
            """
            drop {item.names[0]}
            discard {item.names[0]}

            """
            item.set_state(self.here)
            item.aspect = f"It lies on the floor."
            yield Prologue(f"<> You drop the {item.names[0]}.")