Lets Make a Breedable - Part 4

Posted on

Breeding

First, each pet must have a trait, or a series of traits. For our pet, to keep it simple will have one trait - it’s colour. We will assume this colour vector is set in linkset data as “colour-trait”, later on when we birth an egg we will actually assign the colour trait.

The method for deciding which trait your baby will have is entirely up to you, but for this example using colours, we will make it a mixture of the two colours, with a small random deviation. So, your mother pet will need to get the father’s colour trait, mix it with it’s own, then birth an egg and tell the egg what it’s mixed trait is.

We’ll start a new script and fetch the sex of the pet, the colour-trait, it’s age and the cooldown. The cooldown is a value that will be set later on so pets don’t just have babies all the time. As with other scripts, this one will wait for a signal to start. The SECRET_REZ_NUMBER is the number that will be passed to the egg when it is rezzed, this is both a security feature, and a way to differentiate on how the egg has been rezzed. This number should be changed for each breedable you create and should be kept secret. MAX_DISTANCE is the maximum distance apart pets may be to succeed in breeding, this must be smaller to the distance covered by the communication method you use.

integer SECRET_REZ_NUMBER = 8329329;
integer MAX_DISTANCE = 9;
integer sex;
float age = 0;
vector my_colour_trait;
vector father_colour_trait; // only used in females
integer cooldown = 0;
integer random_number;

default {
    link_message(integer sender, integer number, string message, key id) {
        if (number == 9999) {
            sex = (integer)llLinksetDataRead("sex");
            my_colour_trait = (vector)llLinksetDataRead("colour-trait");
            age = (float)llLinksetDataRead("age");
            cooldown = (integer)llLinksetDataRead("cooldown");
            if (age >= 120) {
                state retired;
            } else if (age >= 10) {
                if (sex == 0) {
                    state male_mature;
                } else {
                    state female_mature;
                }
            } else {
                state immature;
            }

        }
    }
}

The first two states we’ll do are the immature and retired states as they are the easiest.

state immature {
    linkset_data(integer action, string name, string value) {
        if (action == LINKSETDATA_UPDATE) {
            if (name == "age") {
                age = (float)value;

                if (age >= 10) {
                    if (sex == 0) {
                        state male_mature;
                    } else {
                        state female_mature;
                    }
                }
            }
        }
    }
}

The retired state does nothing.

state retired {
    state_entry() {
    }
}

Ok, now for the mature stages, we’ll start with the female, as she will initiate breeding.

state female_mature {
    state_entry() {
        if (age >= 120) {
            state retired;
        }

        if (cooldown == 0) {
            if ((integer)llLinksetDataRead("sickness") == 0) {
                random_number = (integer)llFrand(10000000);
                llMessageLinked(LINK_THIS, 202, "FEMALE_CALL^" + (string)random_number, NULL_KEY);
                cooldown = 10;
                llLinksetDataWrite("cooldown", (string)cooldown);
            }
        }
        llSetTimerEvent(60);
    }

    timer() {
        if (cooldown == 0) {
            if ((integer)llLinksetDataRead("sickness") == 0) {
                random_number = (integer)llFrand(10000000);
                llMessageLinked(LINK_THIS, 203, "FEMALE_CALL^" + (string)random_number, NULL_KEY);
                cooldown = 10;
                llLinksetDataWrite("cooldown", (string)cooldown);
            }
        } else {
            cooldown = cooldown - 1;
            llLinksetDataWrite("cooldown", (string)cooldown);
        }
    }

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

                if (age >= 120) {
                    state retired;
                }
            }
        }
    }

    link_message(integer sender, integer number, string message, key id) {
        if (number == 200) {
            if ((integer)llLinksetDataRead("interbreeding") == 1 || llGetOwnerKey(id) == llGetOwner()) {
                list data = llParseString2List(message, ["^"], []);

                if (llList2String(data, 0) == "MALE_RESP" && (integer)llList2String(data, 1) == random_number) {
                    father_colour_trait = (vector)llList2String(data, 2);
                    llMessageLinked(LINK_THIS, 205, "FEMALE_ACCEPT", id);
                    cooldown = 14400;
                    llLinksetDataWrite("cooldown", (string)cooldown);
                    llSetTimerEvent(0);

                    state pregnant;
                }
            }
        }
    }
}

The sequence of events for breeding is,

  • FEMALE_CALL (broadcast)
  • MALE_RESP (direct) - includes male trait(s)
  • FEMALE_ACCEPT (direct) - sent to the first male that answers the call. sets cooldown.

So the female must send a broadcast message, we’re using say here, you may like to use whisper or shout to get a smaller or bigger range, but remember people may like to seperate their breedables, and we’re not including a pairing system in these articles. The whisper needs a random number for security.

The rest of the transaction takes place in llRegionSayTo, so no need for more random numbers. Once the interaction is complete, the female will move to a pregnant state. Although we are not having a pregnancy period, changing states will drop any queued link messages (ie other males calling) and the time it takes to rez an egg etc, should be enough to ensure that there are no pending messages that will come through when she goes back into her mature state.

If a male does not respond, she will wait 10 minutes and try again.

Notice also, we are preparing for an interbreeding option, which will allow pets to breed with other breeder’s pets.

Now, for the male’s mature state.

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

    timer() {
        if (cooldown > 0) {
            cooldown = cooldown - 1;
            llLinksetDataWrite("cooldown", (string)cooldown);
        }
    }

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

                if (age >= 120) {
                    state retired;
                }
            }
        }
    }

    link_message(integer sender, integer number, string message, key id) {
        if (number == 200) {
            if (((integer)llLinksetDataRead("interbreeding") == 1 || llGetOwnerKey(id) == llGetOwner()) && (integer)llLinksetDataRead("sickness") == 0) {
                list details = llGetObjectDetails(id, [OBJECT_POS]);
                list data = llParseString2List(message, ["^"], []);
                if (llVecDist(llGetPos(), llList2Vector(details, 0)) <= MAX_DISTANCE && cooldown == 0) {



                    if (llList2String(data, 0) == "FEMALE_CALL") {
                        llMessageLinked(LINK_THIS, 205, "MALE_RESP^" + llList2String(data, 1) + "^" + (string)my_colour_trait, id);
                        cooldown = 10;
                        llLinksetDataWrite("cooldown", (string)cooldown);
                    }
                }

                if (llList2String(data, 0) == "FEMALE_ACCEPT") {
                    cooldown = 4320;
                    llLinksetDataWrite("cooldown", (string)cooldown);
                }
            }
        }
    }
}

The male state listens for a female call, then responds appropriatly and sets it’s cooldown timer to 10 - this is a rejection timer.. if the female accepts, the cooldown is set to 4320 minutes which will be the next time the male can participate in the breeding interaction.

You may wonder why we are setting the cooldown in the linkset data, that is because it will be variable that is carried over to the update system to ensure the cooldown lasts between updates.

Finally the pregnant stage..

state pregnant {
    state_entry() {
        llSetTimerEvent(60);
        llRezObject("Egg", llGetPos(), ZERO_VECTOR, ZERO_ROTATION, SECRET_REZ_NUMBER);
    }

    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) == "EGG_READY" && (key)llList2String(data, 1) == id) {
                    llGiveInventory(id, "Pet");
                    llGiveInventory(id, "Egg");
                    llMessageLinked(LINK_THIS, 105, "EGG_DATA^" + (string)my_colour_trait + "^" + (string)father_colour_trait, id);
                    llSetTimerEvent(0);
                    state female_mature;
                }
            }
        }
    }

    timer() {
        llInstantMessage(llGetOwner(), "I tried to lay an egg, but failed! Please check I have rez rights for the land!");
        llSetTimerEvent(0);
        state female_mature;
    }

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

The pregnant stage starts by setting a failsafe timer, then rezzes an egg. When the egg starts it will enable inventory drop and broadcast that it is ready, we don’t use a secret number here, but rather the egg UUID as it will be different every time this message is sent, and we can check it is correct by comparing the uuid in the message to the uuid of the sender.

Once we receive the egg ready message, we give the egg and pet from our inventory to the eggs inventory. Note, at this time the egg and pet are copyable, but thanks to slam bits1 as soon as the egg is rezzed, the no copy script inside will make the egg no-copy. Once the inventory has been passed, we send the egg data, this is the mother and father traits, which the egg will use to decide it’s own trait. As soon as the egg receives the data message it will disable inventory drop, and move to a full state where it will be ready to birth.

The pregnant state also has a linkset_data check for the age, but does not change the state to retired. It would be cruel to have a failed rezzed egg just because it happened at the exact time the pet went into retirement, so the state is changed when it moves back to the female mature state instead.

In the next article we will work on the script for the egg and the beginnings of our brain script which will awaken the pet at birth.