Lets Make a Breedable - Part 5

Posted on

The Egg

The egg object will only have one script in it with four states, a default state, a full state, a hatching state and a dead state. We will also need some variables from our other scripts, namley the “EGG_CHANNEL”, the “SUPER_SECRET_KEY” from Part 1 and the SECRET_REZ_NUMBER from part 4. We will also need the XTEA encryption code from part 1 and your avatar’s UUID. (You could use llGetCreator(), but only if you are actually the creator of the object).

So, start with:

integer EGG_CHANNEL = -894339;
string SUPER_SECRET_KEY = "supersecret";
integer SECRET_REZ_NUMBER = 8329329;
key YOUR_UUID = "43b92d01-1047-4939-87a7-bff06497c80e";

vector my_colour_trait;
integer my_sex;
integer dialog_channel;
integer dialog_listener;

Next include the xtea code.

Now lets get started on the states.

default {
    state_entry() {
        xtea_key = xtea_key_from_string(SUPER_SECRET_KEY);
        llListen(EGG_CHANNEL, "", "", "");
    }

    on_rez(integer param) {
        if (param == SECRET_REZ_NUMBER) {
            llAllowInventoryDrop(TRUE);
            llSay(EGG_CHANNEL, xtea_encrypt_string("EGG_READY^" + (string)llGetKey()));
        } else if (llGetOwner() != YOUR_UUID) {
            state dead;
        }
    }

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

        if (llList2String(data, 0) == "EGG_DATA") {
            llAllowInventoryDrop(FALSE);
            vector colour1 = (vector)llList2String(data, 1);
            vector colour2 = (vector)llList2String(data, 2);

            float deviation = (llFrand(10) - 5) / 100;

            my_colour_trait.x = (colour1.x / 2 + colour2.x / 2) + deviation;
            my_colour_trait.y = (colour1.y / 2 + colour2.y / 2) + deviation;
            my_colour_trait.z = (colour1.z / 2 + colour2.z / 2) + deviation;

            llSetColor(my_colour_trait, ALL_SIDES);

            my_sex = (integer)llFrand(2);

            state full;
        }
    }
}

This is the default state, if the egg has been rezzed by a pet, it will initialize, if it is rezzed by you, it will do nothing (so you can edit scripts) if it is rezzed by someone else, it will go into a dead state - note: this is only in it’s initial state, to rez in it’s initial state, the breeder would have to remove the egg from the pet and rez it themselves. Once initialized, breeders can rezz the egg as they like.

To initialize, first it enables inventory drop, and sends a message to the pet, the pet will send across another copy of the egg and a copy of the pet, and the data for the egg. Once we receive the data, we turn off inventory drop, and calculate our colour and sex, and then move on to the full state.

state full {
    state_entry() {
        if (my_sex == 0) {
            llSetText("Egg\nColour: " + (string)my_colour_trait + "\nSex: Male\n \nClick to Birth", <0.384, 0.694, 1.000>, 1.0);

        } else {
            llSetText("Egg\nColour: " + (string)my_colour_trait + "\nSex: Female\n \nClick to Birth", <1.000, 0.710, 1.000>, 1.0);
        }
        dialog_channel = (integer)llFrand(1000000) - 1000001;
    }

    touch_start(integer detected) {
        if (llDetectedKey(0) == llGetOwner()) {
            dialog_listener = llListen(dialog_channel, "", llGetOwner(), "");
            llSetTimerEvent(60);

            llDialog(llGetOwner(), "Hatch this Egg?", ["YES", "NO"], dialog_channel);
        }
    }

    timer() {
        llListenRemove(dialog_listener);
        llSetTimerEvent(0);
    }

    listen(integer channel, string sender, key id, string message) {
        llListenRemove(dialog_listener);

        llSetTimerEvent(0);

        if (message == "YES") {
            state birth;
        }
    }
}

First we set some pretty text, then wait for a touch from the owner to display a dialog to birth, then if the owner clicks birth, we move to the birth state.

state birth {
    state_entry() {
        llListen(EGG_CHANNEL, "", "", "");
        llSetTimerEvent(60);
        llRezObject("Pet", llGetPos(), ZERO_VECTOR, ZERO_ROTATION, SECRET_REZ_NUMBER);
    }

    listen(integer channel, string sender, key id, string message) {
        if (llGetOwnerKey(id) == llGetOwner()) {
            list data = llParseString2List(xtea_decrypt_string(message), ["^"], []);
            if (llList2String(data, 0) == "PET_READY" && (key)llList2String(data, 1) == id) {
                llGiveInventory(id, "Pet");
                llGiveInventory(id, "Egg");
                llRegionSayTo(id, EGG_CHANNEL, xtea_encrypt_string("PET_DATA^" + (string)my_colour_trait + "^" + (string)my_sex));
                llSetTimerEvent(0);
                llSleep(2);
                llDie();
            }
        }
    }

    timer() {
        llInstantMessage(llGetOwner(), "Birthing failed. Please try again.");
        llSetTimerEvent(0);
        state full;
    }
}

You will probably notice, the hatching of an egg is very similar to birthing the egg. The main difference is this time, we destroy the egg on success. Given that they are essentially the same, you could technically avoid the egg altogether, and just do live births. However, breeders often like an egg / cradle etc that they can sell.

Finally the dead state, is very simple..

state dead {
    state entry() {
        llSetText("Dead", <1,0,0>, 1.0);
    }
}

You could just kill the egg with llDie, but that could be annoying if you want to transfer your creation to another player, and haven’t set the UUID up first.

That is the end of the egg script…

The Pet’s Brain

The pet’s brain is the script that will respond to our egg when it is rezzed, it also will send the command to start all other scripts and handle interaction with the breeder. For now, we’ll work on the awakening.

integer SECRET_REZ_NUMBER = 8329329;
key YOUR_UUID = "43b92d01-1047-4939-87a7-bff06497c80e";

integer sex;
float age = 0;
vector my_colour_trait;
integer hunger = 0;
integer sickness = 0;

default {
    on_rez(integer param) {
        if (param == SECRET_REZ_NUMBER) {
            llAllowInventoryDrop(TRUE);
            llMessageLinked(LINK_THIS, 101, "PET_READY^" + (string)llGetKey(), NULL_KEY);
        } else if (llGetOwner() != YOUR_UUID) {
            state dead;
        }
    }

    link_message(integer sender, integer number, string message, key id) {
        if (number == 100) {
            if (llGetOwnerKey(id) == llGetOwner()) {
                list data = llParseString2List(message, ["^"], []);
                if (llList2String(data, 0) == "PET_DATA") {
                    llAllowInventoryDrop(FALSE);
                    my_colour_trait = (vector)llList2String(data, 1);
                    llLinksetDataWrite("colour-trait", (string)my_colour_trait);
                    sex = (integer)llList2String(data, 2);
                    llLinksetDataWrite("sex", (string)sex);
                    llLinksetDataWrite("age", "0");
                    llLinksetDataWrite("home-position", (string)llGetPos());
                    llLinksetDataWrite("range", "3.0");
                    llLinksetDataWrite("cooldown", "0");
                    llLinksetDataWrite("hunger", "0");
                    llLinksetDataWrite("sickness", "0");
                    llSetColor((vector)llList2String(data, 1), ALL_SIDES);
                    llMessageLinked(LINK_THIS, 9999, "", "");
                    state running;

                }
            }
        }
    }
}

Here you see again we do pretty much the same as when an egg is birthed. We allow inventory drop, send a pet ready signal broadcast, then upon receiving the pet data, we turn off inventory drop, write out the initial values into the linkset data, set the colour of the pet, start the other scripts, and go into a running state.

We will start the running state next and add a dead state.

state running {
    touch_start(integer detected) {
        llSetTimerEvent(60);

        string sick_text;

        if (sickness > 0) {
            sick_text = "I am sick :(";
        } else {
            sick_text = "";
        }

        if (sex == 0) {
            llSetText(llGetObjectName() + "\nSex: Male, Age: " + (string)llFloor(age) + " days\nColour: " + (string)my_colour_trait + "\nHunger: " + (string)hunger + "%\n" + sick_text, <0.384, 0.694, 1.000>, 1.0);
        } else {
            llSetText(llGetObjectName() + "\nSex: Female, Age: " + (string)llFloor(age) + " days\nColour: " + (string)my_colour_trait + "\nHunger: " + (stringhunger + "%\n" + sick_text, <1.000, 0.710, 1.000>, 1.0);
        }
    }

    timer() {
        llSetTimerEvent(0);
        llSetText("", <1,1,1>, 1.0);
    }

    linkset_data(integer action, string name, string value) {
        if (action == LINKSETDATA_UPDATE) {
            if (name == "age") {
                age = (float)value;
            } else if (name == "hunger") {
                hunger = (integer)value;
            } else if (name == "sickness") {
                sickness = (integer)value;
            }
        }
    }
}

state dead {
    state_entry() {
        llSetText("Dead", <1,0,0>, 1.0);
    }
}

As you can see, the running state is unfinished. We will need to add dialogs for setting home position, name, range, etc. That will be in part 6. For now we just display hover text on touch, and after a minute turn it off again. We also check if the age, hunger & sickness has changed and update our variables.

There are multiple ways we could improve this code, we could make the age use days, hours, minutes. We could make the text update dynamically when it is shown. We could change the colour to orange when sick, etc.