The thing about the omnitool is that every stage where you are upgrading the tool with a chip of some kind, in the game it is actually two different omnitools, each with their own set of interaction callbacks. For example, in Upsilon, the first omnitool that you pick up (that doesn't have the tool-chip) is one omnitool, and its
OnUse function is set to automatically decline use with everything. When you insert the tool-chip, the game switches it out with a different omnitool (that already has the chip equipped), and its
OnUse function is set to open the door.
The first step to using the omnitool is to learn about the
OnUse callback. This callback is fired whenever the omnitool is used on something like, say, a door panel. One of the parameters of this callback is the name of the entity it is being used on, so you use this to determine what door panel (and from there what door) should be opened. An example of this callback might look like this:
bool omnitool_OnUse(const tString &in asTool, const tString &in asEntity)
{
if (asEntity == "door_lab_panel")
{
CathTool_UseOnPanel("omnitool", asEntity);
}
return true;
}
The
CathTool_UseOnPanel function is part of a family of CathTool helper functions in "helper_custom_depth.hps" that make using the omnitool much easier. This function in particular automates all the interaction between the omnitool and the door panel.
The next omnitool callback to know about is the
CanBeUsed callback. This callback triggers whenever the player looks at an entity while the omnitool is equipped. If the callback returns true, then the omnitool is primed for action on the panel. (Without this callback, the
OnUse callback would never happen.)
bool omnitool_CanBeUsed(const tString &in asTool, const tString &in asEntity)
{
if (CathTool_CanUse("omnitool", asEntity))
{
return true;
}
return false;
}
The
CathTool_CanBeUsed function automatically checks various things about the entity the player is looking at, such as if it is an omnitool-enabled panel or a socket where an omnitool can be placed.
Detecting when the omnitool has been used on a door panel is a job for the panel itself. In the panel's config is a callback called
ConnectionStateChangeCallback. This callback gets fired whenever the state of the panel changes, or in other words, when the panel is set to locked, unlocked, or activated.
void door_lab_panel_OnConnectionStateChange(const tString &in asEntity, int alState)
{
if (alState != 1)
return;
SlideDoor_SetClosed("door_lab", false, false);
}
The parameter
alState contains what state the panel is currently in. If the state is 1, that means the panel has been successfully activated. And since we don't care when the panel changes to any other state, if alState is not equal to 1 then we ignore it.
SlideDoor_SetClosed is a helper function for programmatically setting doors to open or close. The first parameter is the name of the door we want to affect, the second parameter sets whether we want the door to be closed or not (false for open), and the third parameter sets if we want the door to instantly open (set to false because we want it to open gradually while playing its animations).
That's pretty much all of the interaction code sorted, but we need one more thing before we can call it good. By default, the omnitool isn't equipped all the time. It's put away after it is picked up. Before the omnitool can interact with the panel, we need to make sure it is equipped. The easiest way to do this is to put a tool area in the space around the door panel that equips the omnitool when the player enters it and unequips it when the player leaves.
That's everything for when you have one omnitool and one panel. If you have multiple door panels, you will need to handle them properly in the OnUse callback of your omnitool, as well as having a
ConnectionStateChangedCallback for each door panel. If you have parts of the game that require an "upgraded" omnitool, then like I said before, you just need to have a second omnitool object that you switch with the first, and has its own set of callbacks.