Showing posts with label tutorial. Show all posts
Showing posts with label tutorial. Show all posts

Sunday, October 16, 2016

Tutorial: Exporting JSON from Hellcore for use with jquery Datatables

* Step 1: HTML or PHP template page

 You will need jquery and datatables, which can be downloaded to your local folders. These will be included in our PHP pages later. Let's say we want to display all weapons, here is the template (HTML or PHP). You must replace $weapons_db with the actual OBJNUM of your weapons database:

<html> <head><title>Weapons Table</title> <script src="/lib/jquery-3.1.1.min.js" type="text/javascript"></script> <script src="/lib/datatables.min.js" type="text/javascript"></script> <link href="/lib/datatables.min.css" rel="sylesheet" type="text/css"></link> <script type="text/javascript"> $(document).ready(function() { $('#weapons').DataTable( { "ajax": "http://yourdomain.com/query.php?objid=$weapons_db" } ); } ); </script> </head> <body> <table class="display" id="weapons"> <thead> <tr> <th>ID</th> <th>Name</th> </tr> </thead> </table> </body> </html> * Step 2: PHP ajax page

 Query.php is a PHP file that queries the MOO server on port 8080. This is already setup by default in hellcore, but you need some modifications, firstly to $json_utils:_www
$json_utils:"_www _html"       this none this
     try
       wargs = $su:explode(args[1], "/");
       if (length(wargs) > 1)
         item = toobj(wargs[2]);
         if (gamevalid(item))
           data = item:_json();
           return {data};
         endif
       endif
     except e (ANY)
       $rpg:report_error(e);
       return pass(@args);
     endtry
   return pass(@args);
This changes the $json_utils:_www verb to accept an additional objects. Now let's make query.php: You must replace $json_utils in the code above with the actual object number of your JSON utils object. The code above means that we call json_utils, and we pass it an additional argument in the form of: yourdomain.com:8080/$json_utils/desired_objectnumber

 * Step 3:
Now - our desired_objectnumber must have _json verb, and it must return formatted json (not HTML). Note that the data is structed very specifically to comply with jquery datatables. Example:
$weapons:_json
w = $ou:fertile_branches($weapon);
data = [];
data = ["data" -> {}];
for x in (w)
  id = tostr(x)[2..$];
  d = data["data"];
  d = setadd(d, {id, x:name()});
  data["data"] = d;
  yield;
endfor
return $json_utils:encode(data);

* Step 4: Getting JSON from our MOO server
http://yourdomain.com:8080/query.php?objid=$weapons
Will now return JSON output, something like this (partial): {"data":[["13477","generic melee weapon"],["14111","shock baton"], This has hopefully given you some hints for exporting this data. Modifying it, in MOO, on the fly, is also possible, with callbacks in datatables, but is more advanced. If you get the basics working, you'll see something like an auto paginating page like this:



*Step 5: Security
Use iptables to ONLY allow requests to query.php, and yourdomain.com:8080 from your own domain. Google this!
Questions? Comments?
Post them below!

Friday, January 22, 2016

Adding 2d Perlin Noise to Hellcore/LambdaMOO

Add to extensions.c:
  static int p[512];
  static int permutation[] = { 151,160,137,91,90,15,
  131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,
  21,10,23,190,6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,
  35,11,32,57,177,33,88,237,149,56,87,174,20,125,136,171,168,68,175,
  74,165,71,134,139,48,27,166,77,146,158,231,83,111,229,122,60,211,133,
  230,220,105,92,41,55,46,245,40,244,102,143,54,65,25,63,161,1,216,
  80,73,209,76,132,187,208,89,18,169,200,196,135,130,116,188,159,86,
  164,100,109,198,173,186,3,64,52,217,226,250,124,123,5,202,38,147,
  118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,223,
  183,170,213,119,248,152,2,44,154,163,70,221,153,101,155,167,43,
  172,9,129,22,39,253,19,98,108,110,79,113,224,232,178,185,112,104,
  218,246,97,228,251,34,242,193,238,210,144,12,191,179,162,241,81,51,
  145,235,249,14,239,107,49,192,214,31,181,199,106,157,184,84,204,176,
  115,121,50,45,127,4,150,254,138,236,205,93,222,114,67,29,24,72,243,
  141,128,195,78,66,215,61,156,180
  };
  /* Function declarations */
  double fade(double t);
  double lerp(double t, double a, double b);
  double grad(int hash, double x, double y, double z);
  void init_noise();
  double pnoise(double x, double y, double z);
  void init_noise()
  {
  int i;
  for(i = 0; i < 256 ; i++)
  p[256+i] = p[i] = permutation[i];
  }
  double pnoise(double x, double y, double z)
  {
  int X = (int)floor(x) & 255, /* FIND UNIT CUBE THAT */
  Y = (int)floor(y) & 255, /* CONTAINS POINT. */
  Z = (int)floor(z) & 255;
  x -= floor(x); /* FIND RELATIVE X,Y,Z */
  y -= floor(y); /* OF POINT IN CUBE. */
  z -= floor(z);
  double u = fade(x), /* COMPUTE FADE CURVES */
  v = fade(y), /* FOR EACH OF X,Y,Z. */
  w = fade(z);
  int A = p[X]+Y,
  AA = p[A]+Z,
  AB = p[A+1]+Z, /* HASH COORDINATES OF */
  B = p[X+1]+Y,
  BA = p[B]+Z,
  BB = p[B+1]+Z; /* THE 8 CUBE CORNERS, */
  return lerp(w,lerp(v,lerp(u, grad(p[AA ], x, y, z), /* AND ADD */
  grad(p[BA ], x-1, y, z)), /* BLENDED */
  lerp(u, grad(p[AB ], x, y-1, z), /* RESULTS */
  grad(p[BB ], x-1, y-1, z))), /* FROM 8 */
  lerp(v, lerp(u, grad(p[AA+1], x, y, z-1 ),/* CORNERS */
  grad(p[BA+1], x-1, y, z-1)), /* OF CUBE */
  lerp(u, grad(p[AB+1], x, y-1, z-1),
  grad(p[BB+1], x-1, y-1, z-1))));
  }
  double fade(double t){ return t * t * t * (t * (t * 6 - 15) + 10); }
  double lerp(double t, double a, double b){ return a + t * (b - a); }
  double grad(int hash, double x, double y, double z)
  {
  int h = hash & 15; /* CONVERT LO 4 BITS OF HASH CODE */
  double u = h < 8 ? x : y, /* INTO 12 GRADIENT DIRECTIONS. */
  v = h < 4 ? y : h==12||h==14 ? x : z;
  return ((h&1) == 0 ? u : -u) + ((h&2) == 0 ? v : -v);
  }
  int noise2d(int x, int y, double scalex, double scaley, int size) {
  double xf = (double)x;
  double yf = (double)y;
  double sizef = (double)size;
  double noiseval = pnoise(xf/(sizef * scalex), yf/(sizef * scaley), 0.5);
  noiseval = sizef * ((noiseval + 1.0)/2.0);
  return (int)noiseval;
  }
  int fbm2d(int x, int y, double scalex, double scaley, int size, int octaves)
    
  double xf = (double)x;
  double yf = (double)y;
  double sizef = (double)size;
  double noiseval = 0.0;
  int i;
  for(i = 1; i <= octaves; i++) {
  double n = pnoise(i * xf/(sizef * scalex), i * yf/(sizef * scaley), 0.5);
  n = sizef * ((n/2.0) + 0.5);
  noiseval = noiseval + n / (double)i;
  }
  return (int)noiseval;
  }
  static package bf_perlin_2d(Var arglist, Byte next, void *vdata, Objid progr)
  {
  Var r;
  int x = (int)arglist.v.list[1].v.num;
  int y = (int)arglist.v.list[2].v.num;
  double alpha = *arglist.v.list[3].v.fnum;
  double beta = *arglist.v.list[4].v.fnum;
  int n = (int)arglist.v.list[5].v.num;
  int octaves = (int)arglist.v.list[6].v.num;
  init_noise();
  r.v.num = (int)fbm2d(x, y, alpha, beta, n, octaves);
  r.type = TYPE_INT;
  free_var(arglist);
  return make_var_pack(r);
  }

Add to void register_extensions() in extensions.c:
  register_function("perlin_2d", 6, 6, bf_perlin_2d, TYPE_INT,
  TYPE_INT, TYPE_FLOAT, TYPE_FLOAT, TYPE_INT, TYPE_INT);

How to use: you have a list of biomes or room types. You get a size from this and then call this function:
biomes = {"flatland","forest","mountain"};
size = length(biomes); px = 10; py = 33; alpha = 1.0; beta = 1.5; octaves = 1; index = perlin_2d(px, py, alpha, beta, size, octaves);

 alpha, and beta are floats that affect the jagged/smoothness of the generated area.  octaves runs the noise function multiple times - WARNING - if you have a set number, for instance when selecting a list of biomes, this will cause values to fall outside the range of the list you've created.  For instance using multiple octaves with the list of 3 biomes above would result in many values at x, y co-ordinates of 4-8.  If you're using the noise to set altitude, you may wish to use the octave setting.

If you want to generate an entire map you would call this function repeatedly over the size of your map. Or, more usefully, you call it when a specific co-ordinate is needed. As long as the input to perlin_2d is the same, the index return will always be the same.

Wednesday, May 21, 2014

Hellcore Tutorial - Creating an API

This is a short tutorial on creating an $api object for Hellcore MOO.  This is useful for standard verbs or properties you might want to have on objects that aren't related by tree.

$api is a database that tracks the available APIs - standardized sets of verbs of properties to apply to other objects.

$interface is a definition for the API.  You @create an $interface to define the verbs and properties granted by the API.


@create $interface called linkable
@prop $api.linkable #<createdobjectnum>
@verb #<createdobjnum>:link this none none
@prop #<createdobjnum>:input_types {}


All the verbs and properties on the $interface you create should remain blank or unset - they are just to define that the interface has them.

$implementing_interface is where the actual verbs are written and any default property values set.  You have to set your previously created interface's .implementation property to the implementing interface in order to automatically apply the interface to objects.

@create $implementing_interface called linkable
;$api.linkable.implementation = #<createdimplementinginterface-objnum>

You then add verbs and properties to the implementing interface.  Example:

@verb #interface:link this none none
@program #interface:link
what = args[1];
what:aat(what:dnamec()+" beeps hella loud, because it's about to be linked to an input or output.");
.

The actual object being acted on is passed as the first argument.  Write the verb to act appropriate regardless of parentage.

Now that you have your interface, you can refer to it in code as $api.interfacename, and use it like so:

$api.interfacename:is_on(OBJ target) - check if the target implements $api.interfacename.
$api:implement_on(OBJ target) - apply the properties and verbs from the $implementing_interface to target.


Monday, April 1, 2013

Using cooldown_check tutorial for Hellcore

Hellcore provides an easy way to implement cooldowns for whatever use you desire, using the verb 'cooldown_check'.  The verb is located on $creature and can be used by $player as well.

The verb itself:

:cooldown_check(ANY action, INT seconds-of-cooldown[, INT silent, INT dont-start, INT force-new)

The arguments:

action - this can be any type, either an object representing an action, or a string, or whatever.
seconds-of-cooldown - self explanatory, this is the actual cooldown itself.  Best stored on a property relevant to your use.
silent - if true, no cooldown message will be printed to the player.
dont-start - if true, no cooldown countdown will be started.
force-new - if true, a new cooldown coutndown will be forcibly started regardless of cooldown remaining.

Usage example:

For the example consider an action, let's call it "pray" since that's what I worked on last.  Your _finish verb might look something like this:

pray:_finish
who = args[1];
target = args[2][1];
if ( ! who:cooldown_check( this, this.cooldown_period) )
  return E_NONE;
endif

This would do a check on the cooldown for the pray $action object.  If the cooldown hasn't elapsed, a message is printed to 'who' by cooldown_check, and we abort our action by returning E_NONE.  It is ideal to preform the cooldown check in _finish, since if the check passes in _start but the player aborts the action they will be on cooldown without actually having preformed the action.

Monday, December 31, 2012

Hellcore MOO On Demand Room System Tutorial, Part 1


On Demand Room System


Problem:  You have an ASCII/ANSI based MOO that represents many rooms graphically, often.  Storing an object for each room consumes database space.  Database space is loaded into RAM at a cost of approx. (object's size in bytes) * 2.  On Wayfar, with 9 planets at 100x100, this would be nearly 10,000 objects per planet, almost 100,000 objects just to store blank space.

Solution:  Only store the rooms that players or other objects are using.  Procedurally generate other rooms as required.  The savings are huge!  This is a rough guide to implementing such a system in MOO.  There will be a lot of variation based on your specific gameworld.

Step 1:  Track spawned rooms and delete unused space.


On Wayfar, an object named $ods (on demand spawn) is used to track the status of spawned rooms.  The active rooms are stored in a hash property called spawned_rooms.

Each room must have a unique identifier for easy tracking.  I did this by creating a verb to concatenate a string together from the room's planet/location, and x, y, z co-ordinates.  Example:

$ods:key_string(OBJ room)
room = args[1];
key = tostr(room.location, "-", room.x, "-", room.y,"-", room.z);
return key;


This would return a string along these lines: #4444-1-3--1 for a room located in object #4444 at 1, 3, -1.  This key will let you refer to the active rooms in $ods.spawned_rooms easily.

Next we create a spawning verb for getting the rooms.  This part varies heavily according to how your rooms are setup, and how they are generated.  On Wayfar, we use a simple biome grid generated at planetary creation.  We always know the terrain type for a given X, Y location on a planet, and from that we can generate the appropriate resources and creatures.  This could be improved by proceduralizing all aspects of the room, so that even a despawned room would be re-created exactly from spawn to spawn.  Rough example:

$ods:spawn_3d_room(OBJ location, INT x, INT y, INT z)
{planet, x, y, z} = args;

room_key = $ods:key_string(planet,x,y,z);
"if the room already exists, we can just return it";
if(room_key in keys($ods.spawned_rooms))
  room = $ods.spawned_rooms[room_key];
  if(gamevalid(room) && is_a(room,$room))
    return room;
  endif
endif
"otherwise, we should create a new room and return that";
room = $room:populate();
"on hellcore, that might be: room = $rpg:spawn($room)";
room:set_point(x, y, z);
room:moveto(planet);
$ods.spawned_rooms[room_key] = room;


Now we need to clean up unused rooms.  Example:

$halfhourly:clean_ods
rooms = $ou:leaves_suspended($room);
for r in (rooms)
yield;
if(length(r.contents) < 1)
"you could also add, as we have on Wayfar, a timer check to keep rooms persistent for some period of time";
$rpg:junk(r);
endif
endfor


Important note:  Once the elements above are implemented for your system, you still need to hook them into the actual movement actions for the player.  I setup some vector based verbs to figure out the rooms I need to be spawning based on the direction the player is moving (on a planet a player can move in any cardinal direction).

This guide will hopefully be expanded as time goes on and I am able to write more examples.

Tuesday, October 9, 2012

The Advanced Hellcore $action tutorial, Part 2

This article covers the following in the first section: pro-tips for action use, common problems, and suggested trouble shooting.  This tutorial is third in the series: Part 1, Part 2.


$action Pro Tips


Extra utility verbs on $creatures and $players:
* is_doing(OBJ action, [ ?OBJ callback ]) - this requires the first argument, and the second is optional.  It checks a player or creature to see if they are executing the action sent as the first argument.  If the callback argument is also present, that is checked against the player's executing action.  If the creature or player matches the criteria, is_doing returns 1, otherwise 0.

Example:
" pretend this is a sanity check in an eat command";
if( player:is_doing($actions.mock_attack) )
     player:tell("You can't eat right now, because you are attacking!");
endif

"and this one checks to see if the player is attacking with a sword";
"$weapons.sword would be a valid weapon object, and we're assuming the first callback in $actions.mock_attack is the weapon being used.";
if( player:is_doing($actions.mock_attack, $weapons.sword) )
    player:tell("You use your sword to chop your food before eating it.  Oh boy!");
endif

Changing call back arguments in an $action
Scenario: You want to loop through an action exactly 5 times.

"you queue it like this";
player:queue_action($actions.mock_loop, {1, 5});

"in your _finish code you check the stage status and increment stage (in this examples, args[2][1])";
who = args[1];
stage = args[2][1];
max_stage = args[2][2];

if(stage < max_stage)
  stage = stage + 1;
  "keep looping, by returning an action object from _finish. _continue will be called on the action you return.";
  "Usually you return the action you're executing!";
  return {this, {stage, max_stage}};
else
  who:tell("Looping complete!");
  "we can return E_NONE to finish the action, or fall through to more codes dealing with the specifics";
  return E_NONE;
endif

Broadcasting events from within an $action

Events and callbacks should be familiar topics for anyone who has done any event driven programming.  The idea is simple: code 'broadcasts' an 'event', and other code 'listens' and then reacts to the event.  You broadcast with this verb, which is defined on #1 or $room depending on your hellcore version:
broadcast_event(INT is_start, OBJ action, OBJ who, LIST callbacks)

is_start is 0 for a completing action, and 1 for a beginning action.  action is the action object (or really whatever object) being broadcast, who is the action executor, and callbacks is a list of the callbacks from the action (exactly the same as args[2] within the $action itself).

Example:

$actions.mock_walk
"let's announce we're going to walk around - pretend this is in a _start verb";
who = args[1];
direction = args[2][1];
"we broadcast a 'hey i'm going to be walking here' event";
who.location:broadcast_event(1, this, who, {direction});

"now pretend this is in _finish, we announce the completion of the move";
who = args[1];
direction = args[2][1];
who.location:broadcast_event(0, this, who, {direction});

These events can then be received by other objects in the same location as who from our example above.  The two main ways to receive these are:
forbid_action_* - this should be specific to the action you want to forbid.  For our example, the verb would be named forbid_action_mock_walk.  If forbid_action returns 1, the action is halted and the actor cannot execute it.
hear_event_* - For our example the verb would be hear_event_mock_walk.

Example on a player or creature, to receive walking messages:

hear_event_mock_walk
{starting, action, who, callbacks} = args;
direction = callbacks[1];
" as you can see we receive the args just like they are sent to broadcast_event";
if(!starting)
  "if the walk is actually occuring, let's tell ourselves about it";
  this:tell(who:name(), " walks off to the ", direction, ".");
endif

Calling an impromptu action from any object

You have an object, lets call it MEDICAL TERMINAL.  You have a verb allowing the player to INVESTIGATE MEDICAL TERMINAL, and you want a short action (not require start, continue, finish, but you'd like the delay and action messaging).  Here's how you do that.

@verb terminal:investigate
player:queue_action($actions.verb, {this, "_search_terminal", {player}, 3.0, 1}, "searching the terminal for valuable information");

@verb terminal:_search_terminal
{is_start, object, actor, callbacks} = args;
"object" will be equal to the medical terminal's object number
actor will be equal to the player who queued the action
and callbacks will be array containing the player who queued the action

This will display the player as "searching the terminal for valuable information, and within this _search_terminal verb you can do whatever simple operations you like without having to write a full action.  It will take 3 seconds for the action to begin (specified by the 3.0).

Monday, September 24, 2012

The Advanced Hellcore $action Tutorial

This post is a continuation of the Hellcore $action tutorial.  It is broken up into three parts: a complete listing of the default $action properties, a complete breakdown of the extended $action verbs that can be overridden by your custom actions, and special usage of action on non $action objects.

$action properties

These properties can be set on your action objects to control messages and behavior:

  • .duration (FLOAT) - number of seconds the action takes (from _start to _finish or _continue to _finish)
  • .preemptible (INT) - can this action be pre-empted by another action?  [unused]
  • .unstoppable (INT) - can this action be stopped by the player?
  • .doing_msg (STR, default "%ting") - the doing message (eg the action play would become "playing")
  • .doing_to_msg (STR, default "%ting %il") - the message printed when doing an action to something (eg the action play, on space bagpipes, would print "playing the space bagpipes")
  • .oabort_msg (STR) - the message displayed to other players when the player executing the action types STOP.
$action verbs

These verbs can be overridden with your own versions for customized behavior.
  • _forbidden(OBJ source, LIST callbacks) - do any clean up when the action is forbidden by the room or area.
  • is_being_done_by(OBJ who)  - returns 1 if who is executing this action.
  • unstoppable(OBJ who) - by default, returns this.unstoppable.  Can be overridden to allow stopping of an action in certain circumstances.
  • _abort(OBJ who) - by default tells who that the action has been stopped.  If this.oabort_msg is valid, prints it to the room.
  • duration() - by default returns this.duration.  Customize and pass arguments to vary duration based on circumstances.
  • doing_msg(OBJ who, LIST callbacks) - by default prints doing_msg or doing_to_msg based on the length of callbacks.
Special $action usage

* Executing non $action objects as an $action

Aside from using $action objects, you can use any object, providing it has appropriately defined _start and _finish verbs.  An example might be creating a throwing spear item with a THROW verb on it:
player:queue_action(this, {player.target});

The above example would queue the spear itself as an action (first calling _start, which would then return duration as normal) and passing player.target as the first callback argument.

* $actions.verb

A default action called verb is available in hellcore.  Similiar to executing a non action, this allows you to call any verb as an action (instead of requiring _start or _finish).  Example:
player:queue_action($actions.verb, {some_object, "_start_action", {callback1, callback2}, 5.0, 1}, "custom action message");

Where some_object is an object number or variable of type OBJ, "_start_action" is the actual name of the verb you want to call, callbacks are the callback arguments, 5.0 is any float representing the duration, and custom action message is a string to display as the doing_msg.

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.

Wednesday, June 27, 2012

Changelog 6/27/12

* flyers vehicles and most normal things now work perfectly on the new on demand planets
* new look for flyer cockpits:
* Some larger planets, too big for screenshots: CT-732 Planet Map, IT-1067 Planet Map, WY-1444 Planet Map
* a double (21 x 21) sized minimap for running around in the wilderness:

Friday, February 3, 2012

Note blurb: setting bits and permissions on lambdamoo/hellcore objects


Lambdamoo Programmer's Manual passage:

"The `r' bit controls whether or not players other than the owner of this object can obtain a list of the properties or verbs in the object."

The bits are set with a chmod command:
@chmod #thing +r sets r bit on, @chmod #thing -r sets r bit off
All items are created readable by default.  In addition, #thing.property and #thing:verb can also be @chmod'ed, for instance if you wanted to make a specific verb or property non readable on something you made.

Good news: You never have to worry about this flag because don't make your shit unreadable 8)  More often you'll be doing @chmod #somethingimade +f which allows something to be spawned into the world. Another common command will be @chmod #somethingimade.someproperty -c in order to give other programmers permission to change properties you have defined when they create a child of that object.

Monday, January 30, 2012

A short guide to implementing MXP on hellcore and notes concerning overall implementation

Note! I have posted the hellcore (with some tweaks and additions) $mxp utility here: modular moo-code respository.

Fixing $mxp on Hellcore

Hellcore has a utility called $mxp. $mxp:activate is called on a player when that player changes his MXP pref to 1 (@prefs mxp is 1). In $mxp:activate, telnet negotiation codes are sent to the client indicating the server can send MXP. The $mxp implementation doesn't handle this correctly for Mushclient. In fact it doesn't really handle it correctly, at all, but we can fake it.
$mxp:activate should look like this:


  who = args[1];
  this:tell(who, encode_binary(this.code_IAC, this.code_WILL, "[", 0));
  this:tell(who, encode_binary(this.code_IAC, this.code_SB, "[", this.code_IAC,
this.code_SE, 0));
  this:tell(who, this.tag_lock_locked_mode);
  this:define_elements(who);
 who:tell("MXP mode activated!");
The change to regular hellcore is the second tell, sending DO MXP or something. I also made modification to define_elements. First:
@prop $mxp.elements {}

$mxp:define_elements should look like this:


  who = args[1];
  this:tell_secure_line(who, $su:from_list(this.elements, ""));
This allows you to define elements on $mxp.elements.  Here are some example elements formerly hardcoded into $mxp:define_elements:

<!ELEMENT roomexit FLAG=RoomExit>

<!ELEMENT roomnum FLAG=RoomNum>


See the MXP spec for details on defining elements, attribute lists, etc. $mxp.elements is where you will store them so they are defined to the user when MXP is turned on.

Notes on implementing MXP in game

You must use $mxp:tell_secure_line (or $mxp:tell_lines_secure_line for multiple lines) to send text with MXP tags included.  This means you need to detect when MXP is on, figure out when you're going to be sending MXP, and then tell_secure_line instead of tell in appropriate places.

Then you'll want to define the MXP to be sent for various things.  I did this by adding props to $thing for instance:
.mxp_look_place_string - the MXP string to be sent when the item is seen in a room.
.mxp_inventory_string - the MXP string to be sent when in inventory

And on $creature:
.mxp_tactical_string - the MXP string to be sent when the creature is listed in the tactical display.

The main use of MXP is to add links or right click menus to in game content.
Example $exit.mxp_look_place_string:
"<send>east</send>"
When clicked, the east link will send the text between and , which in this case is "east".

Or, an example weapon mxp_inventory_string:
 "<weapon><send href=\"x &text;|wield &text;|remove &text\">colonial rifle</send></weapon>"
In this example we use a pre defined element to setup the link color, underlining, bolding etc, and then use the item's .mxp_inventory_string to define the right click menu.

Examples of some of the changes I made to common functions:

  • #1:do_examine - if mxp is on print .mxp_examine_menu if it is present and valid
  • $room:list_obvious - 1) print an mxp_look_self_menu string if it exists and mxp is on, 2) if mxp is on, use secure lines to tell the description and objects to player, 3) if mxp is on and an mxp_hint_msg exists, print that string
  • $room:mxp_list_exits (called from $room:list_exits) - to display mxp formatted exits
  • $creature:tell - to send all text in "locked" mode, ignoring mxp tags - when mxp is displayed it must be sent via $mxp:tell_secure_line or $mxp:tell_lines_secure_line
  • $thing:look_place_msg - I overrode this, adding an mxp_look_place_string property.  If mxp is on and the string is valid, it returns the string, otherwise passing the results of #1:look_place_msg