Lets Make a Breedable - Part 2

Posted on

Aging

In this part we will be working on three scripts, one for aging, one for eating and a food bowl. The first thing we need to do is create the default state, this default state will do nothing but wait for a signal from the main script that it’s time to start. The reason for this is that we don’t want the age or hunger timers to start before the pet is born.

float age = 0;

default {
    link_message(integer sender, integer number, string message, key id) {
        if (number == 9999) {
            age = (float)llLinksetDataRead("age");
            state running;
        }
    }
}

This fragment will wait until it gets a link message with 9999 (the number we use to signal birth) and then set the age from the linkset data. The age is set here is ready for the future when we get to the update system lesson as a pet will only be age 0 when it is birthed from an egg - when it is birthed from an update cube it will have the age it had before it was updated.

The reason for using a float for age is that the whole number will be how many days old the pet is, the fraction will be how many hours and minutes.

We then move to the running stage.

state running {
    state_entry() {
        llSetTimerEvent(60);
    }

    timer() {
        age += 0.000694444444444;
        llLinksetDataWrite("age", (string)age);
    }
}

That’s it for the ager script.

Hunger

The hunger script is a little more complex, as it requires eating as well as a timer. We start off similar to the age script..

integer hunger = 0;
integer sickness = 0;
float age = 0;

integer skip = 1;
integer random_number = 0;

default {
    link_message(integer sender, integer number, string message, key id) {
        if (number == 9999) {
            age = (float)llLinksetDataRead("age");
            hunger = (integer)llLinksetDataRead("hunger");
            sickness = (integer)llLinksetDataRead("sickness");

            if (age > 120.0) {
                state retired;
            }

            if (sickness > 0) {
                state sick;
            }


            state running;
        }
    }
}

You might notice the sickness.. you have a few options, I call sickness “unhappyness” in my pets, it is increased when a pet reaches 100% hungry. You could make your pet die, or you could make it require a vitamin or something. Here we will be using sickness and pets just eat themselves happy. Retired pets, or pets that reach age 120 do not eat.

state running {
    state_entry() {
        llSetTimerEvent(3600.0);
    }

    timer() {
        hunger = hunger + 1;
        llLinksetDataWrite("hunger", (string)hunger);

        if (hunger >= 100) {
            llSetTimerEvent(0);
            state sick;
        }

        random_number = (integer)llFrand(100000000);
        llMessageLinked(LINK_THIS, 301, "HUNGRY^" + (string)random_number, NULL_KEY);

    }

    link_message(integer sender, integer number, string message, key id) {
        list data = llParseString2List(message, ["^"], []);

        if (llList2String(data, 0) == "FOOD" && (integer)llList2String(data, 1) == random_number) {
            integer servings = (integer)llList2String(data, 2);

            if (servings >= hunger) {
                llMessageLinked(LINK_THIS, 305, "EAT^" + (string)hunger, id);
                hunger = 0;
            } else {
                llMessageLinked(LINK_THIS, 305, "EAT^" + (string)servings, id);
                hunger = hunger - servings;
            }
            llLinksetDataWrite("hunger", (string)hunger);
        }
    }

    linkset_data(integer action, string name, string value) {
        if (action == LINKSETDATA_UPDATE) {
            if (name == "age") {
                age = (float)value;
                if (age >= 120.0) {
                    llSetTimerEvent(0);
                    state retired;
                }
            }
        }
    }
}

Things to note about this state: we use the random number in the first “HUNGRY” message as that is sent out broadcast. The incoming message “FOOD” verifies the number, the final EAT command doesn’t need a random number as it’s sent only to the food bowl and can not be intercepted.

Also we chose ‘^’ as our delimiter for list messages, you could chose any character, but you want to chose one that is unlikely to be used as part of a list item. We need to make sure later that people can not name their pets with a ‘^’ character in it, make sure once you’ve chosen a delimiter, you stick to it. We also take action if the age in the ager script has retired the pet.

Next we have our sick state…

state sick {
    state_entry() {
        llSetTimerEvent(1800.0);
    }

    timer() {
        if (skip == 0) {
            if (hunger < 100) {
                hunger = hunger + 1;
                llLinksetDataWrite("hunger", (string)hunger);
            }
            skip = 1;
        } else {
            skip = 0;
        }

        if (hunger > 0) {
            random_number = (integer)llFrand(100000000);
            llMessageLinked(LINK_THIS, 301, "HUNGRY^" + (string)random_number, NULL_KEY);
        }

        if (hunger < 100 && sickness > 0) {
            sickness = sickness - 1;
        } else if (sickness < 100) {
            sickness = sickness + 1;
        }

        llLinksetDataWrite("sickness", (string)sickness);

        if (sickness == 0) {
            llSetTimerEvent(0);
            state running;
        }
    }

    link_message(integer sender, integer number, string message, key id) {
        list data = llParseString2List(message, ["^"], []);

        if (llList2String(data, 0) == "FOOD" && (integer)llList2String(data, 1) == random_number) {
            integer servings = (integer)llList2String(data, 2);

            if (servings >= hunger) {
                llMessageLinked(LINK_THIS, 305, "EAT^" + (string)hunger, id);
                hunger = 0;
            } else {
                llMessageLinked(LINK_THIS, 305, "EAT^" + (string)servings, id);
                hunger = hunger - servings;
            }
            llLinksetDataWrite("hunger", (string)hunger);
        }
    }

    linkset_data(integer action, string name, string value) {
        if (action == LINKSETDATA_UPDATE) {
            if (name == "age") {
                age = (float)value;
                if (age >= 120.0) {
                    llSetTimerEvent(0);
                    state retired;
                }
            }
        }
    }
}

In our sickness state, we set the timer twice as fast, so every 30 minutes, however we only increase hunger every hour (so we skip every other timer event). if we are hungry, we attempt to eat, then check if we are still at 100% hunger, increase sickness (if sickness is not at 100%), otherwise, decrease sickness. Finally, if we’re no longer sick, return to normal running state.

We still need to eat while we are sick, so the eating code from the normal running state applies here too. Finally we also check if we’ve become retired and if so, move to the retired state.

Finally the retired state is simply:

state retired {
    state_entry() {
    }
}

ie. do nothing.

The Food Bowl

Of course we will need a food bowl object, in these lessons it will simply be an owner only food bowl. You could later expand it to check group, or allow anyone to eat from it.

Start off with:

integer AUX_CHANNEL = -3902092;
string SUPER_SECRET_KEY = "supersecret";

integer servings = 336;

These variables are from part one and must be the same. Next include the XTEA source code.

Finally, the rest of the food bowl script..

default {
    state_entry() {
        xtea_key = xtea_key_from_string(SUPER_SECRET_KEY);
        llListen(AUX_CHANNEL, "", "", "");
        llSetText((string)servings + " servings.", <0,1,0>, 1.0);
    }

    listen(integer channel, string name, key id, string message) {
        if (llGetOwnerKey(id) == llGetOwner()) {
            list data = llParseString2List(xtea_decrypt_string(message), ["^"], []);

            if (llList2String(data, 0) == "HUNGRY") {
                llRegionSayTo(id, AUX_CHANNEL, xtea_encrypt_string("FOOD^" + llList2String(data, 1) + "^" + (string)servings));
            } else if (llList2String(data, 0) == "EAT") {
                servings = servings - (integer)llList2String(data, 1);
                llSetText((string)servings + " servings.", <0,1,0>, 1.0);
                if (servings <= 0) {
                    llDie();
                }
            }
        }
    }
}

So it’s fairly straight forward, we listen for the word hungry, return the word food along with the hungry random number and the serving count, then when we hear the word eat and the number of servings eaten, we decrease the servings, if it’s less than or equal to zero, destroy itself.

Note: we check if it’s less than zero in the event that two pets attempt to eat from the same food bowl at the same time and end up eating more than it contains. The added complexity of avoiding this situation isn’t worth the extra code.