Skip to content

Custom Scripting Guide

Cromha edited this page May 3, 2024 · 18 revisions

Table Of Content

Introduction

As this guide is advanced, it's recommended to check every other wiki pages before this one.

Bane Of Wargs can be easily modded and there is quite a lot of possibilities. But custom scripts allows the game engine to be even more modular, allowing plugins to have no limits of what's possible to do. Custom scripts let you interact with the game engine, how the regular game data won't allow you. Custom scripts are python scripts, as the game engine is coded in python, that allow you to interact with the game engine by importing the game engine's many class and functions, and by importing major game variables. Currently, there is two ways to get a custom script triggered: in dialogs and events and when using utility items. This guide contains every bit of information about custom scripting, going from the explanation of every class of the game engine, and how to create custom scripts properly. You can find many examples of game custom scripts here.Note that you wanna have a basic understanding of python to create custom scripts.

Creating Your First Script

Custom scripts are placed under the scripts/ directory, in the root. You can name your script pretty much however you want. Once you create it, you wanna create a basic function, that you then run at the very last line of your script:

def run():
    print("Hello !")

# Run the action
run()

This simple script will print Hello ! to the UI. Note that the game engine doesn't use print functions, and it has its own print function, that has some functionalities and improvements, as printing faster and formatting numbers (same for the input function):

from terminal_handling import cout, cinput  # import game engine console functions

def run():
    cout("Hello !")
    cinput("How are you?")

# Run the action
run()

The script will now print Hello ! to the console, and ask how the player is. We can upgrade the script by making a choice menu, and make the script respond differently, following the choice:

import terminal_handling  # import the terminal_handling class
from terminal_handling import cout  # import game engine console functions

def run():
    cout("Hello !")
    cout("How are you?")
    choice = terminal_handling.show_menu(['Doing great!', 'Amazing!', 'Fine'])  # display a choice menu with a list of choices, and store the choice to a `choice` variable
    
    if choice == 'Doing great!' or choice == 'Amazing!':
      cout("Perfect!")
    else:
      cout("Cool.")

# Run the action
run()

The script is now more complex, but it has noting to do with the game. To introduce the game re-usable variables, we're gonna store that response to a player attribute we're gonna create:

import terminal_handling  # import the terminal_handling class
from terminal_handling import cout  # import game engine console functions

def run(player):
    cout("Hello !")
    cout("How are you?")
    choice = terminal_handling.show_menu(['Doing great!', 'Amazing!', 'Fine'])  # display a choice menu with a list of choices, and store the choice to a `choice` variable
    player["mood"] = choice  # store the choice to a new player attribute
    
    if choice == 'Doing great!' or choice == 'Amazing!':
      cout("Perfect!")
    else:
      cout("Cool.")

# Run the action
run(player)  # import the `player` game variable, which contains the player save data

Our script now also stores that answer to a new player attribute we called mood. But let's rewrite the script from scratch, and make a script that randomly choose an enemy thumbnail, and ask the player which enemy it is, which multiple choices (taken from scripts/drawing_enigma.py:

import random
import time
import text_handling  # import the game engine's class that formats text
import terminal_handling  # import the game engine's class that handles console stuff
from colors import *  # import the game engine's class that contains a database of terminal compatible color
from terminal_handling import cout, cinput  # import console output and input functions

def run(enemy, player, preferences):
    completed = False  # let know that the player hasn't completed the enigma

    chosen_enemy = ""  # placeholder
    while chosen_enemy not in player["enemies list"]:  # as long is the chosen enemy is not known by the player, choose another one from the game variable 'enemy', which contains every enemy data.
        chosen_enemy = list(enemy)[
            random.randint(0, len(list(enemy))) - 1  # choose a random enemy
        ]
    chosen_enemy_data = enemy[chosen_enemy]  # load the enemy data

    options = [chosen_enemy]  # create the list that contains all the possible choices, by already adding the right enemy
    count = 0
    while count < 4:  # choose four more random enemies, to add to the possible choices
        random_enemy = list(enemy)[random.randint(0, len(list(enemy)) - 1)]
        if random_enemy not in options:  # check if the chosen enemy isn't already in the choices
            options += [random_enemy]
            count += 1
    random.shuffle(options)  # randomize the list, so that the choices are shuffled

    while not completed:  # loop while the player hasn't complete the enigma
        text_handling.clear_prompt()  # clear the terminal screen
        text_handling.print_enemy_thumbnail(chosen_enemy, preferences)  # use the text_handling game engine's class to print the enemy thumbnail just from its name.
        cout(f"\n{COLOR_STYLE_BRIGHT}Which enemy is this?{COLOR_RESET_ALL}")  # just prompt text

        choice = terminal_handling.show_menu(options)  # display the menu with the possible choices
        if choice == chosen_enemy:  # if the player's right
            cout(f"\n{COLOR_CYAN}Right answer!{COLOR_RESET_ALL}")
            completed = True  # mark the enigma as completed and close the loop
            return completed
        else:
            cout(f"\n{COLOR_YELLOW}Wrong answer!{COLOR_RESET_ALL}")  # inform the player he's wrong
            time.sleep(2)


# Actually run the action, and tells the game which arguments to use
run(enemy, player, preferences)

This is a good example of how re-usable variables can be used, as databases. Now, to actually make these scripts run, we're gonna create a simple utility item:

Custom Script Runner:
  type: "Utility"
  key: 'J'
  script name: example_script.py
  gold: 99
  arguments:  # as the script requires these three
  - enemy
  - player
  - preferences
  description: our custom script runner!
  thumbnail: placeholder string

If we load the game, and we add ourselves the item using the debug command $player$data$ we'll be able to run the script:


image image image image

Check the Wiring Dialogs wiki page to know how to run scripts from dialogs, and the Creating Events wiki page to know how to run scrips from events.

Game Re-Usable Variables

Now, a little bit more about game variables. Game variables are dictionaries, that for the most part contain the game content data, as enemies, npcs, map points, map zones, etc... and other the player save data, the preferences etc... You'll often use these variables when creating variables, and you always have to list them in an arguments key, at the same location as the script name key (you don't need an arguments key if your script doesn't require any). This page contains the list of every game variable, and its content. What I recommend you to do is to make a string that just prints to the console the content of every game variable, so you have more knowledge on how they are.

Starting from v0.24-alpha, 'custom-made' arguments can be specified like this:

arguments:
- player  # a regular argument
- zone
- custom_one_2: True     # a custom made one
- custom_one_3: "hello"  # it can really be anything
- custom_one_4: 8
- custom_one_5: [1, 7, 8]  # it can be a simplified list
- custom_one_6:            # or a regular list
  - 1
  - 7
  - 8
- custom_one_7: {"first key": "hi", "second key": "hi again"}  # or a simplified dictionary
- custom_one_8:                                                # or a regular one
  first key: "hi"
  second key: "hi again"

Game Engine Classes And Functions

This final part of the guide will be the most important part, as every class and their functions will be explained here, so that you know the main ones, and you're able to create custom scripts that interact with the game engine, and that uses useful functions. The document that contains every bit of information about the game engine functions can be found at docs/ENGINE_FUNCTIONS.md.

Clone this wiki locally