Difference between revisions of "Script:Persist.js"

From Spherical
Jump to: navigation, search
(Reference: fill)
(How to…: fill)
Line 129: Line 129:
  
 
==How to…==
 
==How to…==
 +
===Deal with maps in sub-folders===
 +
Regular maps have scripts located as follows:
 +
 +
maps/map1.rmp -> scripts/maps/map1.js
 +
 +
Maps in sub-folders just have to be in the same relative location:
 +
 +
maps/overworld.rmp    -> scripts/maps/overworld.js
 +
maps/tower/level1.rmp -> scripts/maps/tower/level1.js
 +
maps/tower/level2.rmp -> scripts/maps/tower/level2.js
 +
maps/mine/b1.rmp      -> scripts/maps/mine/b1.js
 +
maps/mine/b2.rmp      -> scripts/maps/mine/b2.js
 +
 +
===Save and load the whole game state===
 +
You can use [[Script:json.js|json.js]] to serialise the persist world state. You may want to adapt this to your game's needs.
 +
<syntaxhighlight>
 +
RequireScript("persist.js");
 +
RequireScript("json2.js");
 +
 +
// ...
 +
 +
function save(filename)
 +
{
 +
  var f = OpenRawFile(filename, true);
 +
  try {
 +
    var objectString = JSON.stringify(persist.getWorldState());
 +
    f.write(CreateByteArrayFromString(objectString));
 +
  } finally {
 +
    f.close();
 +
  }
 +
}
 +
 +
function load(filename)
 +
{
 +
  var f = OpenRawFile(filename);
 +
  try {
 +
    var objectString = CreateStringFromByteArray(f.read(f.getSize()));
 +
    persist.setWorldState(JSON.parse(objectString));
 +
  } finally {
 +
    f.close();
 +
  }
 +
}
 +
</syntaxhighlight>
 +
 +
===Use map/person states and functions outside of event scripts===
 +
If you want to, say, use a local map script in a trigger, in your map script:
 +
<syntaxhighlight>
 +
/** scripts/maps/example.js */
 +
 +
({
 +
  myCustomFunction: function (a, b, c) {
 +
    // your code here
 +
  }
 +
})
 +
</syntaxhighlight>
 +
 +
Then in your trigger:
 +
<syntaxhighlight>
 +
persist.map().myCustomFunction(1, 2, 3);
 +
</syntaxhighlight>
 +
 +
===Write one-time "factory" person functions===
 +
If you notice a pattern in your person scripts for a single map, you might want to put the common bits in a single function.
 +
 +
In your map script:
 +
<syntaxhighlight>
 +
/** scripts/maps/example.js */
 +
 +
function sequenceTalker()
 +
{
 +
  var lines = arguments;
 +
 +
  return {
 +
    which_line: 0,
 +
 +
    talk: function (self) {
 +
      Say(lines[which_line]);
 +
      if (which_line < lines.length)
 +
        which_line += 1;
 +
    }
 +
  };
 +
}
 +
 +
({
 +
  tom: sequenceTalker("Hi."),
 +
  dick: sequenceTalker("Hello.", "My name is Dick."),
 +
  harry: sequenceTalker("Hey.", "I'm Harry.", "You forgot my name already?")
 +
})
 +
</syntaxhighlight>
 +
 +
This example shows the creation of script events for 3 persons. Each one speaks their lines in order when spoken to.
 +
 +
The key is to have the function return a JavaScript object. That person factory function won't be dumped into the global namespace, so feel free to give it a simple name.
 +
 +
===Write common "factory" person functions===
 +
If you have a sort of person that keeps showing up, e.g. treasure chests, you may want to write a function that can handle making all of those.
 +
 +
In your main script:
 +
<syntaxhighlight>
 +
/** scripts/main.js */
 +
 +
function game()
 +
{
 +
  // ...
 +
}
 +
 +
function makeChest(item)
 +
{
 +
  return {
 +
    opened: false,
 +
 +
    create: function (self) {
 +
      if (self.opened) {
 +
        SetPersonDirection(GetCurrentPerson(), "open");
 +
      } else {
 +
        SetPersonDirection(GetCurrentPerson(), "closed");
 +
      }
 +
    },
 +
 +
    talk: function (self) {
 +
      if (!self.opened) {
 +
        GiveItem(item);
 +
        Say("Received " + item + ".");
 +
        self.opened = true;
 +
        SetPersonDirection(GetCurrentPerson(), "open");
 +
      } else {
 +
        Say("This chest is empty.");
 +
      }
 +
    }
 +
  };
 +
}
 +
</syntaxhighlight>
 +
 +
And in your map scripts:
 +
<syntaxhighlight>
 +
/** scripts/maps/map1.js */
 +
 +
({
 +
  chest_1: makeChest("sword"),
 +
  chest_2: makeChest("shield"),
 +
  chest_3: makeChest("potion"),
 +
})
 +
</syntaxhighlight>
 +
 +
<syntaxhighlight>
 +
/** scripts/maps/map2.js */
 +
 +
({
 +
  chest_1: makeChest("knife");
 +
  chest_2: makeChest("fork");
 +
})
 +
</syntaxhighlight>
 +
 +
===Send feedback or report bugs===
 +
Visit [http://forums.spheredev.org/ the forums] for support!
  
 
[[Category:Scripts]]
 
[[Category:Scripts]]

Revision as of 23:03, 3 June 2013

persist.js lets you store variables in persons and maps, and use them in event scripts.

At its simplest, you can store simple quest information, e.g. if you've talked to an NPC, or collected an item. Power users can take full advantage of JavaScript, and write templates for common person patterns, e.g. one-line townsfolk, store keepers, inn keepers, save points, chests, items, door switches, bosses blocking passages, and warps.

It helps you to organise your game data. If you ever got sick of making global variables and confusing data structures to hold your game info, this is your answer.

Download

  • persist.js (17.9 KB JavaScript file)
  • persist.js demo game (15.6 KB ZIP archive, requires Sphere)

The code (embedded Gist)

<gist>5702052</gist>

Quick start

  1. Download persist.js into your scripts/ folder.
  2. Put this in your game function:
    RequireScript("persist.js");
    function game()
    {
      persist.init();
      // ...
    }
  3. Create a maps/ folder in your scripts folder, so it looks like scripts/maps/
  4. For each some_map.rmp, make a new script file scripts/maps/some_map.js
  5. Start writing in scripts/maps/some_map.js:
    ({
      visited: false,
     
      enter: function (self) {
        if (!self.visited) {
          Say("Welcome to " + GetCurrentMap() + "!");
          self.visited = true;
        }
      },
     
      Billy: {
        times: 0,
     
        talk: function (self) {
          self.times += 1;
          Say("Hi! We've spoken " + self.times + " before.");
        }
      }
    })
    This will greet the player when they enter the map, and let the person named "Billy" say how many times the player has spoken to them.

Reference

API

  • persist.init() - start using the persist.js framework. Hooks into events and activates automatic script loading.
  • persist.stop() - stop using the persist.js framework.
  • persist.map([mapname]) - get the state of mapname to get/set their state variables, e.g.
    if (persist.map("bleh.rmp").visited) {
      // actions to do if player has visited bleh.rmp
    }
  • persist.person([personname[, mapname]]) - get the state of personname in mapname. Use like it like persist.map().
  • persist.getWorldState() - get an object containing the variables of all maps and persons. Useful for saving games.
  • persist.setWorldState(newstate) - set newstate as the set of variables for all maps and persons. Useful for loading games.
  • persist.getScriptPath() - get the path where map scripts are to be loaded from, relative to other/. Defaults to "../scripts/maps/".
  • persist.setScriptPath(newpath) - set the path where map scripts are to be loaded from to newpath, relative to other/.
  • persist.setLog(log) - set the Sphere log object to record persist.js execution. Useful for debugging.
  • persist.startLogging([takenotes, [log]]) - begin logging of persist.js. If takenotes is true, notes will be recorded as well as warnings and errors. log is accepted for convenience.
  • persist.stopLogging() - stop logging execution of persist.js.

Map events

There are 6 map events:

  • enter - SCRIPT_ON_ENTER_MAP
  • leave - SCRIPT_ON_LEAVE_MAP
  • leaveNorth - SCRIPT_ON_LEAVE_MAP_NORTH
  • leaveSouth - SCRIPT_ON_LEAVE_MAP_SOUTH
  • leaveEast - SCRIPT_ON_LEAVE_MAP_EAST
  • leaveWest - SCRIPT_ON_LEAVE_MAP_WEST

They accept 2 parameters, both optional:

  • 1st - the map state. Usually named map.
  • 2nd - the world state. Usually named world.

Example:

({
  leaveNorth: function (map, world) {
    // your map event code here
  }
})

Person events

There are 5 person events:

  • create - SCRIPT_ON_CREATE
  • destroy - SCRIPT_ON_DESTROY
  • touch - SCRIPT_ON_ACTIVATE_TOUCH
  • talk - SCRIPT_ON_ACTIVATE_TALK
  • generator - SCRIPT_COMMAND_GENERATOR

They accept 3 parameters, all optional:

  • 1st - the person state. Usually named self.
  • 2nd - the map state. Usually named map.
  • 3rd - the world state. Usually named world.

Example:

({
  Bob: {
    talk: function (self, map, world) {
      // your person event code here
    }
  }
})

How to…

Deal with maps in sub-folders

Regular maps have scripts located as follows:

maps/map1.rmp -> scripts/maps/map1.js

Maps in sub-folders just have to be in the same relative location:

maps/overworld.rmp    -> scripts/maps/overworld.js
maps/tower/level1.rmp -> scripts/maps/tower/level1.js
maps/tower/level2.rmp -> scripts/maps/tower/level2.js
maps/mine/b1.rmp      -> scripts/maps/mine/b1.js
maps/mine/b2.rmp      -> scripts/maps/mine/b2.js

Save and load the whole game state

You can use json.js to serialise the persist world state. You may want to adapt this to your game's needs.

RequireScript("persist.js");
RequireScript("json2.js");
 
// ...
 
function save(filename)
{
  var f = OpenRawFile(filename, true);
  try {
    var objectString = JSON.stringify(persist.getWorldState());
    f.write(CreateByteArrayFromString(objectString));
  } finally {
    f.close();
  }
}
 
function load(filename)
{
  var f = OpenRawFile(filename);
  try {
    var objectString = CreateStringFromByteArray(f.read(f.getSize()));
    persist.setWorldState(JSON.parse(objectString));
  } finally {
    f.close();
  }
}

Use map/person states and functions outside of event scripts

If you want to, say, use a local map script in a trigger, in your map script:

/** scripts/maps/example.js */
 
({
  myCustomFunction: function (a, b, c) {
    // your code here
  }
})

Then in your trigger:

persist.map().myCustomFunction(1, 2, 3);

Write one-time "factory" person functions

If you notice a pattern in your person scripts for a single map, you might want to put the common bits in a single function.

In your map script:

/** scripts/maps/example.js */
 
function sequenceTalker()
{
  var lines = arguments;
 
  return {
    which_line: 0,
 
    talk: function (self) {
      Say(lines[which_line]);
      if (which_line < lines.length)
        which_line += 1;
    }
  };
}
 
({
  tom: sequenceTalker("Hi."),
  dick: sequenceTalker("Hello.", "My name is Dick."),
  harry: sequenceTalker("Hey.", "I'm Harry.", "You forgot my name already?")
})

This example shows the creation of script events for 3 persons. Each one speaks their lines in order when spoken to.

The key is to have the function return a JavaScript object. That person factory function won't be dumped into the global namespace, so feel free to give it a simple name.

Write common "factory" person functions

If you have a sort of person that keeps showing up, e.g. treasure chests, you may want to write a function that can handle making all of those.

In your main script:

/** scripts/main.js */
 
function game()
{
  // ...
}
 
function makeChest(item)
{
  return {
    opened: false,
 
    create: function (self) {
      if (self.opened) {
        SetPersonDirection(GetCurrentPerson(), "open");
      } else {
        SetPersonDirection(GetCurrentPerson(), "closed");
      }
    },
 
    talk: function (self) {
      if (!self.opened) {
        GiveItem(item);
        Say("Received " + item + ".");
        self.opened = true;
        SetPersonDirection(GetCurrentPerson(), "open");
      } else {
        Say("This chest is empty.");
      }
    }
  };
}

And in your map scripts:

/** scripts/maps/map1.js */
 
({
  chest_1: makeChest("sword"),
  chest_2: makeChest("shield"),
  chest_3: makeChest("potion"),
})
/** scripts/maps/map2.js */
 
({
  chest_1: makeChest("knife");
  chest_2: makeChest("fork");
})

Send feedback or report bugs

Visit the forums for support!