07-24-2006 00:59
Qualifying preface:
1. I have not done exhaustive (or really any) research to find out if anyone is already doing this.
2. This is a rough-draft version of this idea, not a fully-formed spec.
3. There may be scalability issues to deal with.


BACKGROUND
In a video here [http://video.google.com/videoplay?docid=-262774490184348066&q=spore], the creator of the Sims series, Will Wright, talks about procedural code in his new game, Spore. Now, I'm not really sure what he was talking about, but whatever this method of programming is, it seems to allow emergent behavior. That means new behavior which was not specified by the original program. In his example, the player creates a skeleton for a creature. The game then analyzes the skeleton of the creature and determines how it moves (swims, runs, etc.). The skeleton can be created in any way and the game will figure out how to animate it.

How could this apply to SL? Currently for two objects to interact, the author(s) of the objects must know about it before hand and add scripts accordingly. For example, if I have a pet bird and I want it to eat bird seed on the ground, I have to script the seed to disappear when it's eaten, and the bird to know how to eat it. If there is an apple on the ground next to it, the bird has no idea what it is or even that it's there.
But what if we could apply some kind of general "food" category to both the seed and the apple. The bird knows how to look for things with the "food" attribute and anything with a "food" attribute knows how to be eaten. This way, we can script a general routine into the bird to look for objects with the "food" attribute and eat whetever it finds. I may create some bird seed myself, but then someone else creates some worm objects. If they follow the rules in the protocol (a general pattern to follow when implementing this setup), then my bird will be able to eat the worms as well, even though it didn't exist when I created the bird.


IMPLEMENTATION
The "protocol" defines how objects communicate their attributes and abilities to each other. I can see a need for at least three categories of communications:
1) protocol-level - to determine how the object expects to send and receive communication, if at all. It's no use sending commands to an object which is not "aware" of the protocol.
2) attributes or tags - describe the object and it's capabilities to determine what interactions are appropriate. food items can be eaten, art can be admired, etc.
3) actions - to be performed on, by or between two protocol-aware objects. The bird eats the seed, the seed disappears as it is eaten.


EXAMPLE APPLICATIONS
Rabbits
* create "rabbit" objects which run around, nibbling at the grass, maintaining an internal timer for how old they are
* when a rabbit gets to a certain age, it starts looking for other rabbits (which respond with an attribute of "rabbit" perhaps) and which also are of the opposite gender
* when two rabbits of the appropriate age and of opposite gender meet, they kiss, creating a third rabbit.
* rabbits also have a "prey" attribute and are constantly on the lookout for any object with a "predator" attribute. When a predator is detected, they scurry away.
* at a certain age, rabbits die

Wolves
* create "wolf" objects with behavior similarly to rabbits (age, reproduction, death, etc)
* in addition, wolves get hungry after a while. when they get hungry, they go looking for rabbits, or any other object with the "prey" attribute.
* When a wolf catches up to a rabbit or other prey, the wolf eats it.

NOTE: Self-replicating objects can get very tricky! I think you would want to add a kill-all command, perhaps by llDie()ing when the command is issued on a designated channel.


DISCUSSION
Possible modifications to the protocol could include directing messages to a specific object, not just through an id key, but through a custom channel, or by only listening to a certain key; or possibly using different channels for different protocols or types of messages.


CONCERNS
I don't know how well this will scale. If there are many objects spamming a channel, or multiple channels, with messages, it may not scale gracefully.


DEMONSTRATION SCRIPTS
Put each script in an object. Clicking the "consumer" object puts it into a "hungry" state where it scans for nearby food. If a nearby object responds in the affirmative, the consumer eats the food by calling the food's "be_eaten" action. If the consumer doesn't find any food in the specified amount of time, it starves to death.

The food just sits there. When something looks for food, it announces itself as food. When something wants to eat it, it gets eaten.


This demonstrates only the inter-object communication. An actual implementation would include movement, animations, sounds, etc. The events would probably be triggered by internal counters and variables rather than touches, and the food would probably disappear after being eaten.



Predator/consumer:

CODE

integer CHANNEL = 1001; // must be consistent for all object-communication-protocol (OCP) messages

integer foodsearchcount; // number of "rounds" we've been searching for food
integer food_search_frequency = 5; // search for food for this many seconds before searching again
integer total_food_searches = 4; // total number of searches we will do before starving

look_for_food()
{
// initiate a search for food
llSay(CHANNEL, "scan-aware");
}

default
{
state_entry()
{
llSetColor(<1,1,1>, ALL_SIDES);
llSetText("satisfied - click to make hungry", <1,1,1>, 1);
}

touch_start(integer total_number)
{
llSetText("looking for something to eat", <1,1,1>, 1);
state hungry;
}
}



state hungry
{
// this state may also include visual or auditory indications of a search for food, such as wandering around, growling, etc.
state_entry()
{
llListen(CHANNEL, "", NULL_KEY, ""); // start listening for OCP messages
llSetText("I'm hungry", <1,1,1>, 1);
llSetTimerEvent(food_search_frequency); // do food searches this many seconds
foodsearchcount = total_food_searches;
look_for_food(); // does a query
}
timer() { // this timer event indicates a food search being completed
foodsearchcount --;
if(foodsearchcount <= 0) {
state starved;
} else {
look_for_food();
llSetText("hungry - still looking for food. " + (string)foodsearchcount + " tries left.", <1,1,1>, 1);
}
}
listen(integer channel, string name, key id, string message)
{
integer colon = llSubStringIndex(message, ":");
if(colon == -1) return; // not a well-formed message
string messagetype = llGetSubString(message,0,colon - 1);
string params = llGetSubString(message,colon + 1,llStringLength(message) - 1);
if(messagetype == "protocol")
{
llSay(CHANNEL,"inquire-tags");
}
else if(messagetype = "tags")
{
list tagList = llCSV2List(params);
if(llListFindList(tagList, ["food"]) >= 0)
{
// the object found has a "food" tag, which implies having a be_eaten action, which we will call.
// ideally, would probably want to change to a "chasing" state here where we pursue the "food"
// and when we are close enough, then we will actually "eat" it.
llSetTimerEvent(0); // stop the starvation counter
llSay(CHANNEL,"to:"+(string)id+":action:be_eaten");
llSetText("I'm eating '"+name+"'", <1,1,1>, 1);
llSleep(2); // delay to simulate eating, again, would probably be replaced by an "eating" animation
llSetText("I have eaten '"+name+"' and am no longer hungry.", <1,1,1>, 1);
state default;
}
}
}
}

state starved {
state_entry() {
llSetColor(<1,0,0>, ALL_SIDES);
llSetText("starved to death - click to reset", <1,1,1>, 1);
}
touch_start(integer n) {
state default;
}
}



Prey/food:
CODE

integer CHANNEL = 1001; // MUST BE CONSISTENT FOR ALL Object-Communication-Protocol (OCP) COMMUNICATIONS!

default
{
state_entry()
{
llListen(CHANNEL, "", NULL_KEY, ""); // start listening for OCP messages
// show that we are "ripe" to be eaten
llSetText("alive", <1,1,1>, 1);
llSetColor(<0,1,0>, ALL_SIDES);
}

listen(integer channel, string name, key id, string message)
{
if(message == "scan-aware") // something is looking for protocol-aware objects
{
llSay(CHANNEL, "protocol:1.0"); // respond with which version of the protocol we support
}
else if(message == "inquire-tags") // something wants to know our tags
{
llSay(CHANNEL, "tags:food");
}
else if(llGetSubString(message,0,2) == "to:") // something is sending a directed message
{
string afterTo = llGetSubString(message, 3, llStringLength(message) - 1);
integer colon2 = llSubStringIndex(afterTo,":");
string targetid = llGetSubString(afterTo, 0, colon2 - 1);
key target = (key)targetid;
if(target != llGetKey()) return; // if this message is not intended for me, ignore it

string privMsg = llGetSubString(afterTo,colon2+1,llStringLength(message) - 1);
integer privColon = llSubStringIndex(privMsg,":");
string prefix = llGetSubString(privMsg, 0, privColon - 1);
if(prefix == "action")
{
string action = llGetSubString(privMsg,privColon + 1,llStringLength(privMsg) - 1);
if(action == "be_eaten") // "eat" action is requested
{
state be_eaten; // change state to prevent multiple things from triggering this
}
}
}
}
}

state be_eaten {
state_entry() {
// in here, we want to do whatever happens when this object is "eaten"
// - could be an animation, sounds, etc.
llSetText("I'm being eaten!!", <1,0,0>, 1);
llSleep(2); // delay for 2 seconds to simulate eating to take place...
state dead; // instead of changing states, we could just disappear with an llDie();
}
}

state dead {
state_entry() {
llSetColor(<1,0,0>, ALL_SIDES);
llSetText("dead - click to reset", <1,1,1>, 1);
}
touch_start(integer n) {
state default;
}
}