Monday, September 24, 2012

The Hellcore $action Tutorial

Hellcore provides a 'game loop' in the form of the $heart object.  This object accepts registrations (for example of creatures and players) and every 30 seconds does a 'heartbeat' on every registered object.  When an object beats, the heartbeat verb is called.  NPCs, when doing nothing, can select an action via the verb suggest_next_action.  If the creature or player is already executing an action, the action is checked for duration and moves to the next step or resolves.

When to create an $action:
- a looping or multi step process
- a process where you'd like a delay between starting and finishing
- when you would like players and creatures to share the same code for executing a process

To initiate an action on a creature or player, you call :queue_action, like so:
player:queue_action($actions.some_action, {callback1, callback2, callback3});

Callbacks can be anything that the action is setup to handle.  For instance, here is a mock attack action being queued:
player:queue_action($actions.mock_attack, {player.weapon, player.target});

This would send the player's weapon and target properties to the action.  The player or creature executing the action is always available inside the action.

Actions begin at the _start verb.  Using the mock attack action as an example again, here is what one might look like, with commentary:

$actions.mock_attack:_start
"The player or creature executing the action is always the first argument.";
who = args[1];
"Next we retrieve the callback arguments provided when the action was queued.";
{weapon, target} = args[2];
"Print a message to the room or something, usually.";
who:aat( $su:ps( "%DN attacks %it!", who, target ) );
"At the end of start, after the duration has passed, _finish will be called with the same arguments.";
return {this:duration(), {weapon, target}};

After _start finishes and the duration has passed, the action's _finish verb is called.  We'll use the mock attack as an example again:

$actions.mock_attack:_finish

"The player or creature executing the action is always the first argument.";
who = args[1];
"Next we retrieve the callback arguments provided when the action was queued.";
{weapon, target} = args[2];
"Let's roll some dice and call it a hit on an arbitrary number.";
if(random(100) <= 25)
  who:aat( $su:ps( "%DN lands a hit with %p %t!", who, weapon ) );
  "For this example, we'll assume weapon has two properties indicating the damage type and amount.";
  target:take_damage(weapon.damage_type, weapon.hit_damage);
  "Returning E_NONE from any stage of an action ends the action.";
  return E_NONE;
endif
"If we got this far, we missed.";
who:aat( $su:ps( "%DN misses %it!", who, target ) );
"Now we could just print a miss message and end the action, but for example purposes, let's try hitting again until we succeed.";
"When an action object is returned from _finish, the action is not ended and instead calls _continue.  This allows for repeating or looping actions.";
return {this, {weapon, target}};

You do not have to use a _continue verb.  The attack could simply terminate one way or another in _finish and be resolved.  But for this example, we'll use a _continue verb to loop the action until a hit is made.

$actions.mock_attack:_continue
"The player or creature executing the action is always the first argument.";
who = args[1];
"Next we retrieve the callback arguments provided when the action was queued.";
{weapon, target} = args[2];
"Print a message before passing back to _finish and rolling our attack dice again.";
who:aat( $su:ps( "%DN attempts another attack with %p %t.", who, weapon ) );
"From continue, we return a duration and our callbacks just like in _start.";
return {this:duration(), {weapon, target}};

Now we can use our mock attack action, either through a verb the player can access, or by returning it from an NPC's suggest_next_action verb.  If written correctly, you can use the same $action for players and creatures without modification, and using an $action allows for great control of the timing and interaction of the executing game codes.

For more information, see the HELP $action command as an admin on Hellcore.  I have also written an Advanced $action Tutorial which contains a full breakdown of the $action properties, verbs, and special uses.