Hotspot Scripting

From Wolfire Games Wiki
Jump to: navigation, search
This page covers the creation of selfmade hotspots. For a list and description of every hotspot currently shipped with the game, see Hotspots.

Overview

Hotspots are the most versatile game object in the Phoenix engine.

It is highly recommended to use hotspots for most of your game logic, when you can help it. They will only affect levels that they have been placed into. Level creators can manually place them inside their levels, and they can mix and match which hotspots they choose to place.

Hotspots can handle either local behavior (when a character or item is inside the hotspot), or global behavior (since they have script functions that runs on all updates, as long as they're placed inside the level). Hotspots can also be programmed to interact with each other through script messages.

Hotspots scripts can choose to remain invisible while playing, or can be made to be visible. They can support fancy custom drawing, so they look like something that fits into the game, rather than some strange editor object. They can also be set up to draw differently while inside the editor.

The Overgrowth main campaign and Lugaru campaign both use hotspots extensively:

  • The Overgrowth story checkpoint and goal system uses invisible Checkpoint Hotspots
  • The Lugaru and Overgrowth campaigns both use them to control music
  • Kill boxes use hotspots which listen to "enter" events to kill characters that end up inside them
  • Lava and water are both set up using hotspots, and are drawn with special shaders

Occasionally a hotspot is not the right tool for the job:

  • Several game event messages are only passed to level scripts by default - TODO: Add section below describing how to get around this by a hotspot registering itself level events
  • If you want to make GUI menus (Dear ImGui, or Phoenix IMGUI), then you need a level script - TODO: Add a page for describing debug drawing functions and HUD image functions, that will show how to get around this in a hotspot
  • If you want to do more complicated file access (such as parsing custom files), the functions you need only work in Level Scripts

Hotspots have to placed inside a level in order to work. You can get around this by using a mod level hook script to spawn a hotspot inside a given set of levels, or spawn it inside all levels. TODO: Add example section below

Documentation

Callable functions and data available inside hotspot scripts

For the most part, a hotspot script can call all the same code as a level script. There is some code level scripts can call that hotspots cannot - see the links below for more info.

In addition, a hotspot gets an instances of itself (a variable defined as [email protected] hotspot;), so it can call functions on the Hotspot interface, get its own id, etc.

A list of functions, data, etc, is created automatically whenever you run the game, in a file on your hard drive. This lists what external code you get "for free" and can call from inside a hotspot script. (Some of this code in this list is not usable from a hotspot script - see the links below for more info).

  • Windows: My Documents\Wolfire\Overgrowth\aslevel_docs.h
  • Mac: ~/Library/Application Support/Overgrowth/aslevel_docs.h
  • Linux: ~/.local/share/Overgrowth/aslevel_docs.h

This documentation will change with each version of the game, so keep checking back on this aslevel_docs.h file to see the most up to date information.

There are also wiki pages which have more detailed documentation on many of these functions. These pages have a danger of going out of date, but give more detailed documentation on some of the code:

Hotspot instances in other scripts

Hotspots are exposed to other scripts as instances of the Hotspot class.

Hotspot object instances are like other objects in the game. They support everything that is supported by Object, and also have their own special set of functions, properties, etc.

If you have a handle to a [email protected] object in angelscript, you can convert it to an [email protected] by calling ReadObjectFromID(hotspot.GetID());.

How to create a hotspot

Hotspots require only a few files to work: The hotspot definition (.xml), and the hotspot angelscript file (.as).

Hotspot XML file

Here's the format for the hotspot XML file.

<?xml version="2.0" ?>
<Type>generic</Type>
<Hotspot>
    <BillboardColorMap>Data/UI/spawner/thumbs/Hotspot/empty.png</BillboardColorMap>
    <Script>hotspots/your_mod_name/your_hotspot_script.as</Script>
</Hotspot>
  • BillboardColorMap is the relative path (from the base directory) to an image file. This image will be visible inside the hotspot's bounding box while you have the editor active.
  • Script is the relative path (rooted at the Data/Scripts directory) to a script fie. This is the script that will provide the script logic to the hotspot. See the Hotspot script hook functions section below to see what functions you should make inside your script.

Remember to place your hotspot XML file and hotspot script file in the correct directories, so they don't conflict with other mods. See the File path conventions section on this page.

How to set a default scale for the hotspot

TODO: Document how to save a transform for a hotspot (scale, mostly)

Short version: You select the object, and choose Edit -> Save Selection

File path conventions

Be careful to place your hotspot XML files and hotspot script files in a place where they won't conflict with other mods. The easiest way to do this is add a your_mod_name directory to the path, right before the file.

Data/Mods/<your_mod_name>/Objects/Hotspots/<your_mod_name>/my_hotspot.xml

Data/Mods/<your_mod_name>/Scripts/hotspots/<your_mod_name>/my_hotspot.as <- Note, lowercase "Script/hotspots"

How to add a hotspot to the spawner menu

Short version:

You must add it in a mod.

You can add as many hotspots to the spawner menu as you want in a single mod, you just need to follow the below instructions for each, and add multiple <Item> tags to your mod.xml file.

In your mod.xml file, add this xml tag:

<Item category="My Mods Items"
      title="Some Item To Spawn"
      path="Data/Objects/example_item_pack/mod_item_example.xml"
      thumbnail="Data/UI/example_item_pack/thumbs/mod_item_example.png" />
  • category is the top level category where the item will show up, in the Load menu.
  • title is the name of the item, as it will show up in the spawner menu.
  • path is the path to the object XML that will get spawned. See the How to create a hotspot section for which XML file to target (either the hotspot XML itself, or a version you saved off that has the modified scale).
  • thumbnail is the image that will be used for a tooltip when you hover over your item in the spawner menu.

See GameInstallDir/Data/ExampleMods/mod_xml_specification.txt for full information.

Hotspot script hook functions

All functions inside a hotspot script are optional, but will be called if they are present.

GetTypeString function

string GetTypeString() { return "MyHotspotType"; }

Optional

GetTypeString is called when a different script invokes hotspot_instance.GetTypeString()

This is useful for identifying specific hotspots by type in a different script.

You can find hotspots with this type from a different script using code like this:

int num_hotspots = GetNumHotspots();
for(int i=0; i < num_hotspots; ++i) {
    [email protected] hotspot = ReadHotspot(i);
    if(hotspot.GetTypeString() == "SomeCustomHotspotType") {
        // ...

This is useful when you want to send messages to the hotspot, to control or move it, to delete it, etc

Init function

void Init() { }

Optional

Init is Called when the hotspot is first loaded, upon level load.

It is also called when an undo/redo is fired in the editor, to reload script variables from that point in history.

Be careful, this may be called before some objects or script params are present in the level.

It is most useful for setting initial values for file-scope angelscript state.

SetParameters function

void SetParameters() { }

Optional

SetParameters is called when the hotspot is first loaded, and whenever the script parameter values are changed via the GUI.

It is also called when an undo/redo is fired in the editor, to reload script params from that point in history.

This function allows the hotspot script to work with script parameter values. You can use it to define, change, verify, protect (undo writes), or locally cache script parameter values.

You can also use this to work with game objects that you use hotspot script params to control.

SetEnabled function

void SetEnabled(bool is_enabled) { }

Optional

SetEnabled is called when SetEnabled gets called on the hotspot object by another script.

This function allows the hotspot to enable or disable any objects that should be associated with the hotspot (objects that are used like children of the hotspot, such as PlaceholderObject instances).

This function can also be used to internally set variables so that its functions have no effect. The engine will generally not call the hotspot if it is not enabled (except when it gets re-enabled), but other scripts could theoretically call functions on the hotspot using hotspot_instance.Execute("some code");.

Reset function

void Reset() { }

Optional

Reset is called whenever the level is reset. This does not happen when a level is first loaded, or when undo/redo are fired in the editor.

This happens when the player hits the "L" debug key to manually reset the level.

This also happens upon player respawn (but this is up to the level script, and modded level scripts might not do that).

Dispose function

void Dispose() { }

Optional

Dispose is called when the hotspot is destroyed, either by manually deleting it, or when the level is unloaded.

WARNING: Do not DeleteObjectID for other game objects from this function! If you try to, then the game will likely crash on level unload when calling Dispose() on the object you deleted. Also don't try to QueueDeleteObjectID() through Dispose, or it will really mess with undo/redo state.

Used to free any non-game-object resources that you have a handle to that are owned by this hotspot.

One example is Debug Draw objects with _persistent lifetime.

Update function

void Update() { }

Optional

Update is called regularly by the engine so you can perform work.

It may be useful to do initialization once in this function, if you need objects or level script params to be present.

Be careful to keep this function short, sweet, and fast. Update gets called 120 times a second as of the time of this writing.

HandleEvent function

void HandleEvent(string event, MovementObject @mo) { }

Optional

HandleEvent is triggered when a movement object (usually a character) interacts with this hotspot.

For the most part, this is event = "enter" or event = "exit", when a character enters or leaves the hotspot.

I cannot find any other Hotspot + MovementObject events at this time, but there may be more in the future. (-Merlyn)

HandleEventItem function

void HandleEventItem(string event, ItemObject @obj) { }

Optional

HandleEventItem is triggered when an item object interacts with this hotspot.

For the most part, this is event = "enter" or event = "exit", when an item enters or leaves the hotspot.

I cannot find any other Hotspot + ItemObject events at this time, but there may be more in the future. (-Merlyn)

ReceiveMessage function

void ReceiveMessage(string message) { }

Optional

ReceiveMessage is called when messages have been sent to this hotspot by the engine, level, or objects in levels.

Objects in levels (such as characters or hotspots) can send this using hotspot.SendMessage("some message string");.

Parameters can be sent by separating them with spaces, and putting quotes around parameters that might contain spaces, then using the TokenIterator object. TODO: Write a TokenIterator example and link to it.

If you're setting up the code to send a message to a hotspot, you might want to choose to only send messages to a hotspot with a specific custom type. See the GetTypeString function for an example of how to do this filtering.

PreDraw function

void PreDraw(float curr_game_time) { }

Optional

PreDraw serves as a before-update function for script-defined drawing. This lets you correct properties of the object before draw is called, so movement, etc, doesn't lag behind by a frame.

Be careful to keep this function short, sweet, and fast. PreDraw gets called on every frame as of this writing.

Draw function

void Draw() { }

Optional

Draw serves as an update function for script-defined drawing. This is called both while in editor, and while not in the editor.

Be careful to keep this function short, sweet, and fast. Draw gets called on every frame as of this writing.

DrawEditor function

void DrawEditor() { }

Optional

DrawEditor serves as an update function for drawing. This is called only while the editor is active.

Be careful to keep this function short, sweet, and fast. DrawEditor may get called on every frame.

Example hotspots

TODO: Update this to something more useful/complete. Multiple examples? Demo editor drawing, and in-game drawing. Demo using enter/exit events, and hotspots that don't use them.

TODO: Demo using a level script to insert a hotspot.

TODO: Demo using the hotspot function that lets you register for receiving level events

//Variables can be created outside any function, these will be available to all following functions.
float force;
int friendly;
void Init() {
    //Inside the init function you can put something that only needs to happen once.
    Print("Initializing hotspot\n");
}
//Inside the SetParameters function you can set declare variables that the user can change. By selecting the hotspot and press Ctrl + U the parameters will be available to the user.
void SetParameters() {
    params.AddString("Upward force", "5.0");
    //You can either assign the parameter to a variable straight away or fetch it when it is used.
    force = params.GetFloat("Upward force");
    //Sliders can be usefull for the user to quickly change parameters.
    params.AddFloatSlider("Recovery Time", 1.0f, "min:0.0,max:50.0");
    //You can add checkboxes for simple on/off functions.
    params.AddIntCheckbox("Friendly", false);
    friendly = (params.GetInt("Friendly") == 1);
}
//You can check for a character or an item to enter or exit the hotspot.
void HandleEvent(string event, MovementObject @mo){
    //Here we check if a character enters the hotspot.
    if(event == "enter"){
        OnEnterChar(mo);
    }
    //The function "OnExitChar()" will be called if a character exits teh hotspot
    else if(event == "exit"){
        OnExitChar(mo);
    }
}
//The same technique can be used for an item. 
Note: There is a difference between an ItemObject and an Object. An ItemObject is affected by gravity, such as weapons and collectables. 
An Object is static for example a hexcrete or joshua tree.
void HandleEventItem(string event, ItemObject @obj){
    if(event == "enter"){
        OnEnterItem(obj);
    } 
    if(event == "exit"){
        OnExitItem(obj);
    } 
}

void OnEnterItem(ItemObject @obj) {
    if(obj.GetType() == _collectable){
        Print("A " + obj.GetType() + " item has entered the hotspot\n")
    }
    //An item can just like a character have it's properties changed via a hotspot.
    vec3 vel(0.0f,15.5f,0.0f);
    obj.SetLinearVelocity(vel);
}

void OnExitItem(ItemObject @obj) {
    
}
void OnEnterChar(MovementObject @mo) {
    //To check if the character is the user you can check if it is a controller MovementObject.
    if(mo.controlled){
        Print("A user has entered the hotspot\n")
        //If you choose to retrieve the parameter as needed you can request by name.
        mo.velocity.y = params.GetFloat("Upward force");
    }
    else{
        Print("An NPC has entered the hotspot\n");
    }
    //You can create effects with MakeParticle.The first parameter is the particle file to load. The second parameter is the position in the world to spawn this particle.
    //The last parameter is the direction the particle is moving. The higher the values the faster it will move.
    MakeParticle("Data/Particles/fire.xml", mo.position,vec3(0.0f,15.0f,0.0f));

    //Certain functions are only accessible if you go through the Execute() function. These can contain multiple functions and even parameters.
    mo.Execute("SetOnGround(false);DropWeapon();recovery_time = "+params.GetFloat("Recovery time")+";");
    
    //The level itself can also receive various function
    level.SendMessage("loadlevel \"/Data/Levels/sea_cliffs.xml");
    
    //To put some text on the screen in stead of the console you can use.
    level.SendMessage("displaytext Test Message");
    //And to clear it.
    level.SendMessage("cleartext");

    //You can also reset the level.
    level.SendMessage("reset");

    //If you want to put an image onto the screen
    level.SendMessage("displayhud /Data/Textures/twisted_paths.png");
    //And to remove this again use
    level.SendMessage("clearhud");

    //To play a sound you use the PlaySound function. The second parameter to specify the location is optional. Make sure the audio file mono or else the positional effect won't work.
    PlaySound("Data/Sounds/fall_swoosh.wav", mo.position);

    //To get a random value you can use RangedRandomFloat. This will be usefull for example with particle effects.
    float direction = RangedRandomFloat(-100.0f,100.0f);

    //To spawn objects you can use the CreateObject function. This can be an Object, ItemObject or Character.
    int objectid = CreateObject("Data/Objects/rock.xml");
    //The function return an id number which you can use to get the Object. Now the Object can be manipulated.
    [email protected] objectid = ReadObjectFromID(obj_id);
    //If the object is character use the MovementObject type.
    [email protected] character = ReadCharacterID(objectid);
    //If the object is a plant use the EnvObject type.
    [email protected] plant = ReadEnvObjectID(objectid);
    //If the object is weapon or collectable use the ItemObject type.
    [email protected] weapon = ReadItemID(objectid);

    //The hotspot is also an object inside the game. You can also move and resize it.
    [email protected] this_hotspot = ReadObjectFromID(hotspot.GetID());
}
void OnExitChar(MovementObject @mo) {


}
//The Update() function will continue to execute as long as the hotspot is loaded. It does not need to be triggered.
void Update() {    

}