Lets Make a Breedable - Part 8

Posted on

Update Cubes

As time goes by, you will likely find bugs or make improvements to your breedable, so you need a way to update your pets to the latest version. To keep things simple, we’re just going to update live pets. Eggs can be updated when they are hatched.

The basic flow of updating with an update cube is:

  1. Breeder clicks update on pet.
  2. Pet broadcasts looking for update cube, includes it’s version.
  3. Update cube responds if it’s version is greater than pet’s version.
  4. Pet sends data to update cube and destroys itself.
  5. Update cube rezzes new pet, and passes old pets data.
  6. Update cube is now empty and ready for next pet.

So, first we need to modify our brain script.

New global variables - the SECRET_UPDATE_NUMBER should be different from SECRET_REZ_NUMBER:

integer SECRET_UPDATE_NUMBER = 849839;
integer random_number;
integer update_lock = FALSE;

We need to add a dialog button.. out touch_start event in the running state will look like this.

    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: " + 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: " + hunger + "%\n" + sick_text, <1.000, 0.710, 1.000>, 1.0);
        }

        if (llDetectedKey(0) == llGetOwner()) {
            list dialog_buttons = ["Set Home", "Set Range", "Set Name"];

            if (is_frozen) {
                dialog_buttons = dialog_buttons + "Unfreeze";
            } else {
                dialog_buttons = dialog_buttons + "Freeze";
            }

The update button:

            dialog_buttons = dialog_buttons + "Update";
            llListenRemove(dialog_listener);
            dialog_listener = llListen(dialog_channel, "", llGetOwner(), "");
            llDialog(llGetOwner(), "Home Position: " + llLinksetDataRead("home-position") + "\nRange: " + llLinksetDataRead("range") + "\n \nWhat would you like to do?", dialog_buttons, dialog_channel);
        }
    }

Next we need to listen for the Update command in addition to the existing commands.. our modified listen event in the running state:

    listen(integer channel, string sender, key id, string message) {
        if (channel == dialog_channel) {
            if (message == "Set Home") {
                llLinksetDataWrite("home-position", (string)llGetPos());
                llOwnerSay("My home position is now: " + llLinksetDataRead("home-position"));
            } else if (message == "Unfreeze") {
                llMessageLinked(LINK_THIS, 10, "", "");
                llOwnerSay("Off I go then!");
                is_frozen = FALSE;
            } else if (message == "Freeze") {
                llMessageLinked(LINK_THIS, 11, "", "");
                llOwnerSay("I will remain very very still!");
                is_frozen = TRUE;
            } else if (message == "Set Range") {
                llSetTimerEvent(60);
                range_listener = llListen(range_channel, "", llGetOwner(), "");
                llTextBox(llGetOwner(), "Please enter a range between 1.0 and 10.0:", range_channel);
            } else if (message == "Set Name") {
                llSetTimerEvent(60);
                name_listener = llListen(name_channel, "", llGetOwner(), "");
                llTextBox(llGetOwner(), "Please enter a new name for me:", name_channel);

Here is the modifications (remember we’re storing our version number in the object description, and the random number is for security):

            } else if (message == "Update") {
                random_number = (integer)llFrand(1000000.0);
                llMessageLinked(LINK_THIS, 303, "UPDATE_PING^" + llGetObjectDesc() + "^" + (string)random_number)
            }

And the rest of the original event..

        } else if (channel == range_channel) {
            float new_range = (float)llStringTrim(message, STRING_TRIM);

            if (new_range < 1.0) {
                new_range = 1.0;
            } else if (new_range > 10.0) {
                new_range = 10.0;
            }

            llLinksetDataWrite("range", (string)new_range);

            llOwnerSay("My new range is: " + (string)new_range);
        } else if (channel == name_channel) {
            string new_name = llStringTrim(message, STRING_TRIM);

            if (llSubStringIndex(new_name, "^") != -1) {
                llOwnerSay("Sorry, that name has invalid characters!");
            } else if (llStringLength(new_name) <= 1) {
                llOwnerSay("Sorry, that name is too short!");
            } else {
                llSetObjectName(new_name);
                llOwnerSay("I like my new name!");
            }
        }
    }

Next we have to listen for a response from the update cube, so we modify our link_message event. We also listen for a die message from the update cube.

    link_message(integer sender, integer number, string message, key id) {
        if (number == 11) {
            is_frozen = TRUE;
        }
        if (number == 300) {
            if (llGetOwnerKey(id) == llGetOwner()) {
                list data = llParseString2List(message, ["^"]. []);
                if (llList2String(data, 0) == "UPDATE_PONG" && (integerllList2String(data, 1) == random_number && update_lock == FALSE) {
                    update_lock = TRUE;
                    llMessageLinked(LINK_THIS, 305, "UPDATE_LOAD^" + llGetObjectName() + "^" + llLinksetDataRead("age") + "^" + llLinksetDataRead("sex") + "^" + llLinksetDataRead("hunger") + "^" + llLinksetDataRead("sickness") + "^" + llLinksetDataRead("cooldown") + llLinksetDataRead("range") + "^" + llLinksetDataRead("home-position") + "^" + llLinksetDataRead("colour-trait"));
                } else if (llList2String(data, 0) == "UPDATE_DIE") {
                    llDie();
                }
            }
        }
    }

Finally for the brain script, we need to check if it’s not the SECRET_REZ_NUMBER, but the SECRET_UPDATE_NUMBER and act accordingly.

default {
    on_rez(integer param) {
        if (param == SECRET_REZ_NUMBER) {
            llAllowInventoryDrop(TRUE);
            llMessageLinked(LINK_THIS, 101, "PET_READY^" + (string)llGetKey(), NULL_KEY);
        } else if (param == SECRET_UPDATE_NUMBER) {
            llAllowInventoryDrop(TRUE);
            llMessageLinked(LINK_THIS, 301, "UPDATE_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;

                }
            }
        } else if (number == 300) {
            if (llGetOwnerKey(id) == llGetOwner()) {
                list data = llParseString2List(message, ["^"], []);
                if (llList2String(data, 0) == "UPDATE_DATA") {
                    llAllowInventoryDrop(FALSE);
                    llSetObjectName(llList2String(data, 1));
                    llLinksetDataWrite("age", llList2String(data, 2));
                    sex = (integer)llList2String(data, 3);
                    llLinksetDataWrite("sex", (string)sex);
                    llLinksetDataWrite("hunger", llList2String(data, 4));
                    llLinksetDataWrite("sickness", llList2String(data, 5));
                    llLinksetDataWrite("cooldown", llList2String(data, 6));
                    llLinksetDataWrite("range", llList2String(data, 7));
                    llLinksetDataWrite("home-position", llList2String(data, 8));
                    my_colour_trait = llList2String(data, 9);
                    llLinksetDataWrite("color-trait", my_colour_trait);
                    my_colour_trait = (vector)llList2String(data, 1);
                    llLinksetDataWrite("colour-trait", (string)my_colour_trait);
                    llSetColor(my_colour_trait, ALL_SIDES);
                    llMessageLinked(LINK_THIS, 9999, "", "");
                    state running;
                }
            }
        }
    }
}

The Update Cube Script

Now this is done we need to write the update cube script.

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

integer SECRET_UPDATE_NUMBER = 849839;
integer update_lock = FALSE;

list pet_data = [];
string pet_name;

integer random_number;

Insert the XTEA encryption code here..

default {
    state_entry() {
        xtea_key = xtea_key_from_string(SUPER_SECRET_KEY);
        state empty;
    }
}

state empty {
    state_entry() {
        llSetText("Empty", <0,1,0>, 1.0);
        llListen(AUX_CHANNEL, "", "", "");
        pet_data = [];
        pet_name = "";
    }

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

The pet data is copied into a list as is, the update cube doesn’t care about what the data is (except for the name of the pet) and will send the data as is to the new pet.

                pet_data = llList2List(data, 1, -1);
                pet_name = llList2String(data, 1);
                llRegionSayTo(id, AUX_CHANNEL, xtea_encrypt_string("UPDATE_DIE"));
                state full;
            } else if (llList2String(data, 0) == "UPDATE_PING" && (float)llList2String(data, 1) < (float)llGetObjectDesc()) {
                random_number = (integer)llList2String(data, 2);

This next bit checks if the update cube is within 10 meters of the pet before responding.

                if (llVecDist(llList2Vector(llGetObjectDetails(id, [OBJECT_POS]), 0), llGetPos()) < 10) {
                    llRegionSayTo(id, AUX_CHANNEL, xtea_encrypt_string("UPDATE_PONG^" + (string)random_number));
                }
            }
        }
    }
}

state full
{
    state_entry()
    {
        llListen(AUX_CHANNEL, "", "", "");
        llSetText("Full - Touch to Complete\n" + pet_name, <1,1,0>, 1.0);
    }
    touch_start(integer detected) {
        if (update_lock == FALSE) {
            update_lock = TRUE;
            llRezObject("Pet", llGetPos(), ZERO_VECTOR, llGetRot(), SECRET_UPDATE_NUMBER);
        }
    }
    listen(integer channel, string name, key id, string msg) {
        if (llGetOwner() == llGetOwnerKey(id) && update_lock == TRUE) {
            list data = llParseString2List(xtea_decrypt_string(msg), ["^"], []);
            if (llList2String(data, 0) == "UPDATE_READY" && (key)llList2String(data, 1) == id) {
                llGiveInventory(id, "Pet");
                llGiveInventory(id, "Egg");
                llRegionSayTo(id, AUX_CHANNEL, xtea_encrypt_string("UPDATE_DATA^" + llDumpList2String(pet_data, "^")));
                llSleep(2);
                update_lock = FALSE;
                state empty;
            }
        }
    }
}

Now, to put the update cube together, rezz a prim, put this script in it, and set the object description to the version number that the update cube is updating to. Then put the Egg and Pet into the update cube as you did with the starter eggs:

Next is the tricky part…

Edit a starter egg and go to it’s contents, drag your pet object and egg object from your inventory into the starter eggs inventory. Next right click the pet object and set to copy. You must do this inside the edit window, not rezzed out on the ground. Do the same for the egg object, then repeat for the other starter egg.