Butterflies Redux

A long time ago, I posted a script system to make butterflies appear when the sun came up in SL, and disappear when it went down.  The system consisted of a script to place in some copy/mod butterflies available on the marketplace, and a script to rez them and delete them at the appropriate times.

In the nine (!) years since I posted those, things have changed.  As a naïve scripter back then, I didn’t think about co-ordination between the script in the rezzer and the script in the butterflies.  The scripts needed to communicate because back then, the only piece of information you could pass to a rezzed object was an integer.  Because the butterflies need a position to move to after being rezzed, they established a communication channel using the integer so that the rezzer could pass the butterflies a position vector, and also pass them a “delete yourself” command.

Usually, this just worked.  But as the computers that run the simulator code have become faster, the potential co-ordination issue became more apparent, and Linden Labs even came out with official advice to scripters as to how to perform proper communication co-ordination.  The link to the official advice seems to have disappeared from the Lab’s blog, but the example scripts have been incorporated in the object_rez documentation if you want to read about how to do co-ordination properly.

As you might have guessed, I wrote the scripts for myself, and posted them on the blog because, well, LSL is one of my passions in SL.  I don’t sell the system, and I neglected to update the scripts in my in-world system (and on this blog) to follow the Lab’s advice.

So, this article updates the scripts, and having just talked about communications co-ordination, eliminates the old way of passing data to a rezzed object by using new functionality introduced by the Lab in the last twelve months.

These changes are new LSL functions, llRezObjectWithParams (), llGetStartString (), and llDerezObject ().  These were mostly introduced to help people making combat systems, but they can completely remove the need for the scripts to do communication via the listen event on rez, thus removing the potential co-ordination issue.

Let’s dive right in to the scripts.  You can ignore the debug.lsl and profile.lsl and associated stuff in the following, or click the links to get the included code.

First, here’s the new script to place in the butterfly object that will be rezzed.

#undef DEBUG
#include "debug.lsl"
#undef PROFILE
#define MEMLIMIT 6000
#include "profile.lsl"

default {

    on_rez (integer s) {
        init_profile ();
        if (s != 0) {
            vector pos = (vector)llGetStartString ();
            llSetRegionPos (pos);
            llRemoveInventory (llGetScriptName ());
        }
        show_profile ("");
    }
}

This got significantly simpler.  Now, if the object is rezzed by the rezzer (s is not equal zero), then the script gets the start string (which you’ll see in the next script), interprets it as a vector, moves to the position specified, and deletes itself (as once the object is moved, the script has nothing else to do).

Here’s the rezzer script:

#undef DEBUG
#include "debug.lsl"

#define MEMLIMIT 16000
#undef PROFILE
#include "profile.lsl"

// To rez and derez immediately the sun comes up or goes down,
// #undef IGNORE_TWILIGHT
#define IGNORE_TWILIGHT

integer g_nc_line;
string g_nc_name = "config";
key g_nc_id;
integer g_pos_count;
list g_positions;
list g_inv_objs;
integer g_obj_count;
list g_rezzed_objs;
integer g_prev_sun = -1;

integer sun_up () {
    // Return true if the sun is up, false otherwise.
    vector sun = llGetSunDirection ();
    integer result;
#ifdef IGNORE_TWILIGHT
    if (llFabs (sun.z) < 0.1) { return FALSE; } else { result = (sun.z > 0);
    }
#else
    result = (sun.z > 0);
#endif
    return (result);
}

default {

    on_rez (integer n) {
        llResetScript ();
    }

    state_entry () {
        init_profile ();
        llSetObjectDesc (VERSION);

        g_inv_objs = [];
        g_positions = [];
        g_pos_count = 0;
        g_obj_count = llGetInventoryNumber (INVENTORY_OBJECT);
        if (g_obj_count == 0) {
            llOwnerSay ("There are no butterflys objects in the inventory!");
            return;
        } else {
            integer i;
            string name;
            for (i = 0; i < g_obj_count; i++) {
                name = llGetInventoryName (INVENTORY_OBJECT, i);
                if (llGetSubString (name, 0, -2) == "butterflys") {
                    g_inv_objs += name;
                }
            }
        }
        g_obj_count = llGetListLength (g_inv_objs);

        g_nc_line = 0;
        if (llGetInventoryType (g_nc_name) == INVENTORY_NOTECARD) {
            g_nc_id = llGetNotecardLine (g_nc_name, g_nc_line);
        } else {
            llOwnerSay ("Notecard " + g_nc_name + " not found - stopping");
            return;
        }
    }

    dataserver (key id, string data) {
        if (id == g_nc_id) {
            if (data != EOF) {
                if (llGetSubString (data, 0, 0) != "#") {
                    g_positions += (vector)data;
                }
                g_nc_id = llGetNotecardLine (g_nc_name, ++g_nc_line);
            } else {
                g_pos_count = llGetListLength (g_positions);
                llSetTimerEvent (0.1);
            }
        }
    }

    timer () {
        llSetTimerEvent (0.0);
        integer sun = sun_up ();
        if (sun != g_prev_sun) {
            if (sun) {
                // Sun is now up, rez
                integer i;
                integer r = (integer)llFrand (g_obj_count);
                string which;
                for (i = 0; i < g_pos_count; i++) {
                    which = llList2String (g_inv_objs, r);
                    llRezObjectWithParams (which,
                                           [REZ_POS, <0.0, 0.0, 0.5>, TRUE, FALSE,
                                            REZ_PARAM, -1,
                                            REZ_PARAM_STRING, llList2String (g_positions, i)]);
                }
            } else {
                // Sun is now down, derez
                integer i;
                integer n = llGetListLength (g_rezzed_objs);
                key id;
                for (i = 0; i < n; i++) {
                    id = llList2Key (g_rezzed_objs, i);
                    debug ("derez " + (string)id);
                    llDerezObject (id, DEREZ_DIE);
                }
                g_rezzed_objs = [];
            }
            g_prev_sun = sun;
        }
        llSetTimerEvent (60.0);
        show_profile ("");
    }

    object_rez (key id) {
        g_rezzed_objs += id;
    }
}

The most important change here is the call to llRezObjectWithParams (). Note the we are passing -1 to REZ_PARAM, which is how the new function passes the integer, and we are passing a vector with the REZ_STRING_PARAM.

I also made a few other changes, including only performing actions when the sun changes (from up to down or down to up).  Additionally, I wanted to offset the time to exclude twilight hours.  If you like the old behavior, you can implement it by undefining IGNORE_TWILIGHT.  Oh, and the config notecard is now mandatory, and you need to specify all the positions, including the one that used to default to directly over the rezzer.

Lastly, here’s a link to the butterflies on marketplace.  They are only L$10 🙂


llDerezObject

Feel free to skip this is you have no interest in scripting 🙂

When we script an object to rez another object, we tend to want to delete the rezzed object afterwards.  There are a couple of ways to do this.

The first involves no script at all; you can rez the object marked as a temporary object, and some time in the next 120 seconds, the simulator code will delete it.  This is great for very short lived things like bullets rezzed from a gun, but if you need the rezzed object to stick around until you decide to delete it, you need something else.

The naïve way to do it is to pre-load the object to be rezzed with a simple script that listens to the rezzer, and when it receives an appropriate message, it called llDie () to delete itself.  This is simple, works, but has the downside of increasing the number of scripts in the object, which can negatively affect land impact.

I wrote a post a while back discussing how to delete an object by loading a script into it via llRemoteLoadScriptPin ().  This avoids pre-loading a script; you load the script into the rezzed object when you want the object to delete itself.  Unfortunately, this also has a downside.  Because llRemoteLoadScriptPin () causes the calling script to sleep three seconds, it can be exceedingly slow to delete a large number of objects.  It’s also far more complex to set up that the naïve way, but it does eliminate the possibility of increasing land impact with pre-loaded scripts.

Having struggled with this problem forever, scripters demanded a better method, and the Lab responded by defining a new function in a simulator release to delete rezzed objects: llDerezObject ().

Yay!  Now instant deletes, no delays, no extra scripts required!

(I wrote this so I can link my old post mentioned above to it.  If people find the old post then they also have the opportunity to discover this new and better method).

 

 

 


My Scripting Workflow

Introduction

A while ago, I described my switch to a new scripting workflow. That article explained to my customers why I was re-releasing a lot of my products with no functional change, and that due to my changed workflow, solving bugs they might encounter could be slower than was previously the case.

This article is for my SL scripter colleagues.  It lays out in precise detail just how my scripting workflow works, and includes a functional makefile and bash scripts to implement it.

Required Software

I use Linux (specifically Ubuntu) as my operating system of choice.  You will need the following software installed to implement this workflow.

  • Mercurial
  • GNU Make
  • GCC
  • Firestorm
  • LSL-PyOptimizer
  • Python
  • TortoiseHG (optional)

All my LSL projects are in Mercurial repositories, one repo per project.  Yes, yes, I know that the world seems hellbent on standardizing on Git, but 1) I’ve been using Mercurial forever, and 2) Git is far more complex that it needs to be for a single developer.  If you prefer Git (or some other VCS), feel free to take what follows and replace the hg commands.

LSL-PyOptimizer and the GNU Make utility are also a crucial part of this workflow, as is the Firestorm Preprocessor.

If you’re a scripter and unaware of LSL-PyOptimizer, you’re missing out.  This program, written in Python, takes your LSL as input and spits out LSL that is memory optimized.  Depending on your script, you can save large percentages of your original memory footprint by using this utility.  LSL-PyOptimizer also supports some sorely missed features in the LSL language, which can make coding easier and the end result more readable.  Get it, use it.

One note on LSL-PyOptimizer.  As the package is delivered, the main python script is called main.py, which is not very useful.  I copy main.py to lslpo and include the directory in my path so I can invoke it with a non-generic name.  Additionally, if you are running Python3 (which you should be in this day and age), you need to change the hash bang at the start of the file to remove the trailing “2” from “Python2”.

Python is required to run LSL-PyOptimizer.

GNU Make is a dependency build system.  You write instructions to tell Make how to build your project.  Because LSL-PyOptimizer recognizes most C preprocessor directives, and LSL is C-like syntax, you can use the GCC Preprocessor to automatically build dependencies for you, which Make will then use to build your project.  Make will also be responsible for automatically generating a version number for the resultant code (based on Mercurial tags).

Using Firestorm enables you to use its preprocessor so you can #include files.  The output of LSL-PyOptimizer will be included in its entirety to eliminate the need of copy/pasting from a file on your PC to the edit window in SL.

TortoiseHG is a graphical user interface to Mercurial.  In the following, I will use the Mercurial command line interface, but normally I will use TortoiseHG to manage my repos.

Step by Step

The easiest way to demonstrate this workflow is to work a real example, and additionally it can work as a test to see if you have everything set up correctly.  Let’s create a dead simple project from scratch, assuming nothing exists.


$ cd ~
$ mkdir src           # Standard source directory
$ cd src
$ mkdir include       # Dir containing included scripts
$ mkdir script        # We will place a supporting bash script here
$ mkdir test          # The name of our test project
$ hg init test        # Initialize the project dir as a Mercurial repo
$ cd test
$ mkdir Script        # This is where our original LSL will live
$ mkdir build         # This is where the LSL-PyOptimizer output will end up
$ mkdir stable        # This is where the current release code will end up

This sets up a source directory (/home/username/src/ is the defacto source directory name on Linux, and the makefile assumes that’s where it is). It also sets up a directory to contain included files such as debug.lsl, which I include in every LSL script I write. And finally it creates the basic structure for a project called “test” and initializes it as a Mercurial repository.

To follow along, visit the source for debug.lsl, copy and paste it into ~/src/include/debug.lsl

Now let’s create a basic LSL script in ~/src/test/Script/test.lsl.


#define DEBUG
#include "debug.lsl"

default {

    state_entry () {
        llSetObjectDesc (VERSION);
        llOwnerSay ("State_entry");
        debug ("Done");
    }
}

We initialized the test directory as a Mercurial repository already, so we can just add our new script as a tracked file and commit our changes. If you haven’t set a username for Mercurial, it will complain about that. Just follow its instructions to correct the issue.


$ hg add Script/test.lsl
$ hg commit -m 'Initial version'

And now the makefile. Even though a copy of this needs be in the project directory, they are not project specific, so when you create a new project you can just copy an existing makefile to the new project’s directory. I’ll list it out here and you can copy and paste it into ~/src/test/makefile


BUILD_DIR := ./build
SRC_DIR := Script/
INC_DIR := $${HOME}/src/include/

SRCS := $(shell find $(SRC_DIR) -not -path '*/.*' -name '*.lsl' -printf '%f\n')

OBJS := $(SRCS:.lsl=.olsl)
OBJS := $(OBJS:%=$(BUILD_DIR)/%)
DEPS := $(OBJS:.olsl=.d)

VERSION := $(shell bash $${HOME}/src/script/version.sh)

.PHONY: all
all: $(DEPS) $(OBJS)

$(BUILD_DIR)/%.d: $(SRC_DIR)%.lsl
	@echo "Rebuild" $@
	@mkdir -p $(dir $@)
	@set -e; rm -f $@; \
	cpp -MM -nostdinc -iquote$(INC_DIR) -iquote$(dir $(abspath $@)) $< > $@.$$$$; \
	sed 's,\($*\)\.o[ :]*,$(BUILD_DIR)/\1.olsl $@ : ,g' < $@.$$$$ > $@; \
	rm -f $@.$$$$

include $(DEPS)

$(BUILD_DIR)/%.olsl: $(SRC_DIR)%.lsl version.txt
	@echo "Rebuild" $@
	@mkdir -p $(dir $@)
	@lslpo \
                --preproc=gcpp \
	        --postarg='-DVERSION="$(VERSION)"' \
                --postarg="-nostdinc" \
		--postarg="-iquote$(INC_DIR)" \
		--postarg="-iquote$(dir $(abspath $<))" \
		--timestamp \
		$< -o $@

.PHONY: clean
clean:
	rm -rf $(BUILD_DIR)

STABLE_DIR := ./stable

.PHONY: stable
stable:
	mkdir -p $(STABLE_DIR)
	rm -f $(STABLE_DIR)/*.olsl
	cp $(BUILD_DIR)/*.olsl $(STABLE_DIR)

The makefile executes a bash script to automatically generate a version based on the last Mercurial tag in the repo of the form “VM.m”, which M is the major version and m is the minor version. For example, V1.0.

The script also appends a patch number to the version string if there are committed changes to the working directory after the last tag. For example, if the last tag is “V1.0”, and there are 5 commits after that tag (not including the commit that created/moved the tag), then the generated version will be “V1.0.5”. If there are no tags in the repo, the version number will start at “V0.1”.

Here’s the bash script. Copy and paste this to ~/src/script/version.sh.


#!/bin/bash
if [ ! -f version.txt ]
then
    echo "V0.1" > version.txt
fi
FILE_VERSION=$(<version.txt)
REGEX="re:^(V\d+\.)?(\d)$"
VERSION=$(hg log -r "." --template "{latesttag('$REGEX')}")
CHANGES=$(hg log -r ". and not keyword('added tag')" --template "{changessincelatesttag}")
CHANGES=$(($CHANGES-1))

if [ ${VERSION} == 'null' ]
then
    VERSION="V0.1"
    CHANGES=$(hg log -r "." --template "{rev}")
fi

PLUS=""
if [ $CHANGES -gt 0 ]
then
    PLUS="."$CHANGES
fi

if [ $FILE_VERSION != $VERSION$PLUS ]
then
    echo $VERSION$PLUS > version.txt
fi

echo "$VERSION$PLUS"

With all of this in place, we can finally run the make command to build the project.


$ make
Rebuild build/test.d
Rebuild build/test.olsl
$ ls build/
test.d  test.olsl
$ cat build/test.d
./build/test.olsl build/test.d : Script/test.lsl /home/blue/src/include/debug.lsl
$ cat build/test.olsl
// Generated on 2024-11-17T19:51:03.056390Z
debug(string text)
{
    llOwnerSay(text);
}

default
{
    state_entry()
    {
        llSetObjectDesc("V0.1");
        llOwnerSay("State_entry");
        debug("Done");
    }
}

Note that in the dependency description file test.d, the build targets not only depend on the LSL in the Script directory, but also debug.lsl in our include directory. This means that if we ever change debug.lsl, make will rebuild the project for us.

Also note that because we didn’t add a version tag to the repo, we get the default version of V0.1 in the generated code. Let’s fix that.


$ hg tag V1.0
$ touch Script/test.lsl    # Force make to rebuild even though the code didn't change
$ make
Rebuild build/test.d
Rebuild build/test.olsl
$ cat build/test.olsl
// Generated on 2024-11-17T20:12:33.719787Z
debug(string text)
{
    llOwnerSay(text);
}

default
{
    state_entry()
    {
        llSetObjectDesc("V1.0");
        llOwnerSay("State_entry");
        debug("Done");
    }
}

The makefile has a stable target too. It simply copies the generated LSL currently in the build directory to the stable directory. When I tag a changeset as a release version, I’ll simply run this.


$ make stable
mkdir -p ./stable
rm -f ./stable/*.olsl
cp ./build/*.olsl ./stable
$

Lastly, the repo is a bit of a mess.


$ hg status
? build/test.d
? build/test.olsl
? makefile
? stable/test.olsl
? version.txt

You can choose how to deal with untracked files to suit your style. I use an .hgingore file that contains the following standard lines.


$ cat .hgignore
syntax: glob
build/**
stable/**
version.txt
makefile

And we should add .hgignore as a tracked file and move our tag to the last changeset (or tag with V1.1 if you prefer).


$ hg add .hgignore
$ hg commit -m 'Track .hgignore'
$ hg tag -f V1.0

All this work has so far been done on our PC. As I’m developing/testing the script, to get the results in-world, I will create a script in the target object that looks like this.


#include "/home/blue/src/test/build/test.olsl"

Note you need to enable the Firestorm preprocessor in the scripts editor window settings panel to do this.

Once I’m happy with a release and copy the generated code to the stable directory, the production version of the product will have the include path changed from “build” to “stable”.

Conclusion

It can take a little while to get used to editing the script on your PC, issuing the make command, and then switching to SL and hitting the “Recompile” button in the script editor, but it’s worth it.  Everything is tracked, every version is labeled, and you get memory optimized code, and automatic versioning so you never have to guess which version of script is in the twenty different copies of the object you generated while testing 🙂

I hope this helps some of you manage your code better. If you have questions or comments about any of this, feel free to email me or contact me in-world, or leave a comment here on the blog.


Updated Wind Sculpt

I’ve been fiddling around with Linden Scripting Language for over nine years now.  It’s an interesting, quirky language, with a huge number of functions in its library (nearly 500).  While I think I know it pretty well, language and library features can always surprise even the most experienced coder.

A week or so ago, I found myself in the position of wanting to be able to rez and derez 64 linked objects.  Previously, I’ve done this by putting a script in the objects to be deleted that listened to the master script and then deleted themselves when told. But…

64 scripts, even though they were very small (about 5 lines of code and less than 6K of memory each), nearly doubled the land impact of my 65 piece object!  There had to be a better way.  And there is.

There are two functions called llSetRemoteScriptAccessPin() and llRemoteLoadScriptPin() that will allow you to load a script to an object on demand and set it to running.  I won’t give you an example here, you can see some at the links to the functions.

(Update: These is now a new a better method for deleting rezzed objects.  If you’re a scripter that found this page, please check it out before using the method described here).

All this is leading up to the fact that I have an existing product on MP that also does rez and derez of multiple linked objects, and uses the old method.  Can we use these functions to drop its land impact?  No prize for a correct guess!

This is the BF Wind Sculpt, a kinetic wind sculpture based on Anthony Howe’s Di-Octo.  It rezzes and derezzes its vanes on demand.  With the old system with a script in each vane listening to the support to say “delete yourself”, the object weighs in at 13 LI.  Using the new method, it comes in at 10.  Yay!  If you own a copy, you should now have version V1.3.


Instantly improve your avatar as a newcomer

Oh my, why oh why do I see three year old and older avatars that still look like they created their account yesterday?  I know it must be laziness or perhaps lack of computer skills, but getting a good looking avatar is pretty simple!

Here, I’ll not only give you a step by step, I’ll assume you’re running Firestorm and give you a step by step to switch off things and switch on things that will instantly improve your look.

Let’s start with that.

Firestorm settings

Head follows mouse

  1. Open Preferences via the main “Avatar” menu, or hit Ctrl-P.
  2. Go to the “Firestorm” tab.
  3. Select the “Avatar” tab.
  4. Set the two sliders at the top to zero by dragging them all the way to the left.  They are labeled “Vertical/Horizontal range that avatar’s head follows mouse”.

Eye Movement

Ensure you have eye movement.  A fixed stare is not only unnatural, it’s creepy.

  1. Open Preferences via the main “Avatar” menu, or hit Ctrl-P.
  2. Go to the “Firestorm” tab.
  3. Select the “Avatar” tab.
  4. Untick the “Disable random avatar eye movements” checkbox.

Selection Beam

This stops your arm waving wildly around when you select something.

  1. Open Preferences via the main “Avatar” menu, or hit Ctrl-P.
  2. Go to the “Firestorm” tab.
  3. Select the “Avatar” tab.
  4. Untick the “Enable selection beam” checkbox.

Animation Overrider (AO)

This is a bit complex, but follow along step by step if you don’t have an AO.  Although there are a lot of steps, the procedure is straightforward, and uses computer skills and SL skills that you should already by familiar with.

  1. Find a FREE AO on marketplace, we’ll use the Basically Girl AO as an example, and buy it for L$0.
  2. Go to a sandbox, for example, Skybeam, or somewhere you have rez rights.
  3. Open your inventory (Ctrl-I) and locate the object “AX-001 Basically Girl AO”, either via the “Recent” tab if you just bought it, or using the search bar at the top of your inventory window.
  4. Left click on the object in your inventory, and while holding the left mouse button, drag it onto the ground.
  5. Right click the object on the ground and “Open” it, and click the “Copy to inventory” button in the “Object Contents” window that opens.
  6. Close the “Object Contents” window.
  7. Delete the object on the ground with right click and “Delete” (or if you’re using pie menus, right click, “More >”, “Delete”).
  8. You should now have a folder in your inventory called “AX-001 Basically Girl AO” containing an object called “AX-001 Basically Girl AO (Oracul Animations HUD)”.
  9. Left click this object in your inventory, and while holding the left mouse button, drag it to the ground (be careful, the object is small).
  10. Right click the object on the ground and open it, and click the “Copy to inventory” button.
  11. Close the “Object Contents” window.
  12. Delete the object on the ground.
  13. You now should have a folder in your inventory called “AX-001 Basically Girl AO (Oracul Animations HUD)” containing lots of animations and three notecards called “SET01-” followed by a height in centimeters.
  14. If you don’t have a toolbar button labeled “AO” in your toolbar, create it by selecting “Toolbar Buttons” from the main “Avatar” menu, locating the “AO Animation Overrider” button, and dragging it to your toolbar.
  15. Click the AO button on your toolbar to open the “Animation Overrider” window.
  16. Choose one of the notecards, for example, “SET01-155cm” that is closest to your height.  If you don’t know your height, right click your avatar and select “Edit shape”.  The shape editor window will open and your height is in the top right corner.  You may have to click “Metres” to see the metric height.
  17. Left click your chosen notecard and holding the left mouse button down, drag it into the “Animation Overrider” window.
  18. Close the “Animation Overrider” window (and your inventory, you’re done with it) and tick the checkbox in the AO button on the toolbar.
  19. Congratulations!  You now have an AO.  You can disable or enable it with the checkbox on the toolbar button.
  20. You can find other AOs and repeat this procedure to create multiple AO sets in Firestorm.

There’s addition information about the in-built Firestorm AO on the wiki, including links to tutorial videos on how to set it up and customize it.

Fix Bento Idle Animation

Bento is cool, it allows us to move our fingers, and do a lot more facial expressions than before.  But if you don’t have something to play an animation on your Bento bones, some of the default positions can look… weird.  The most common newcomer look is splayed hands.  Do this to ensure that if you have no Bento animations playing, your hands (and face, etc) will run a low priority animation that will fix the weirdness.

  1. Open Preferences via the main “Avatar” menu, or hit Ctrl-P.
  2. Go to the “Firestorm” tab.
  3. Select the “Extras” tab.
  4. Tick the box labeled “Fix Bento Idle Animation”

Typing animation and sound

This should probably be the first thing on the list 🙂

  1. Open Preferences via the main “Avatar” menu, or hit Ctrl-P.
  2. Go to the “Chat” tab.
  3. Select the “Typing” tab.
  4. Untick the box labeled “Play typing animation when chatting”.
  5. Untick the box labeled “Hear typing sound when people type in nearby chat”.
  6. On the left hand panel, select “Sound & Media”.
  7. Select the “UI Sounds 2” tab.
  8. Untick the “Play this sound” checkbox next to “Typing a message”

Appearance

There are certain things, mostly very old objects, available at various freebie places around the grid.  Because they’re free, newcomers tend to snap them up, but to anyone with any time in SL, all they do is mark you as completely inexperienced (and oblivious to appearance).  Once you know what these things are, you can avoid them, or modify them to improve the look of your avatar.

Bling

Bling, which is a particle effect meant to represent sparkly things like jewelry (hence the name), was apparently huge… in 2007.  Today, most people avoid it.  My advice is to chuck things with bling.  If you really like the piece and want to keep it, and it has modify permissions, you can potentially get rid of the bling effect, but to do so requires some basic scripting knowledge.

For help, try going to somewhere like Builder’s Brewery and politely asking an experienced person to help you.  Just remember, the people that you ask may not know how, or may not have time to help.  Thank them and politely ask someone else.  Just don’t be a nuisance to people as they are trying to build.  If you can’t find someone to help you, stash the thing back in your inventory and try another day.

Full bright

Full bright is a texture property.  It’s like switching a backlight on behind a picture.  This is great for lighted signs and stuff, but it looks horrible in hair.  Before mesh hair came along, prim hair was all that was available.  To make it look “better”, some creators allow you to switch full bright on and off by clicking your hair.  If you have “glow in the dark” hair, look for this option, else look for another hairstyle.

There are tons of good mesh hairstyles available for FREE if you take the time to look around.

Avatar expression

Specifically, the HUD that makes you smile every couple of seconds.  I remember finding this as a newcomer and thinking, “Great, my expression won’t be static any more!”.  But, strangely, smiling every couple of seconds comes across to me now as… creepy.  If you must use an expression HUD, try AnyPose Expression HUD, which you can get for FREE.  You can trigger an expression manually, rather than smiling creepily every few seconds.  As a bonus, you get eye position control with this, which is a boon for posed photography.

Mesh eyes

Get some for FREE at Mayfly Mesh Eyes, you’ll be impressed by the difference they make.  Using them requires basic editing skills.

Eyelashes

If your avatar doesn’t have eyelashes, get some prim ones for FREE, for example, these free eyelashes.  Using them requires basic editing skills.

Nails

Back in the day, prim nails were the way to go until you could afford SLink hands, which were the hand replacement of choice until mesh bodies came along with in-built hands.  I still love my SLink hands and wear them every day although I have a Maitreya mesh body.

As the new default avatars still aren’t Bento enabled, you should still be able to find some on marketplace.  And yes, Bows and Butterflies have a number of prim nail sets available for L$1 each.  Again, using them requires basic editing skills.

Makeup

It’s easy to be a blonde in RL and a redhead in SL.  And it’s easy to use your normal makeup tones from RL and look dumb 🙂  If you’re a blonde in RL, and you decide to be a redhead in SL, take the time to research makeup tones that suit redheads, just like you would if you dyed your hair in RL.  For heaven’s sake, try not to wear bright red lipstick with ginger hair, OK?!

Shape

We tend to exaggerate body shapes in SL.  Everyone can be the perfect supermodel, or you can go the other way and exaggerate… other attributes.  You can see what I look like on every other post on this blog.  I’m not a total glamor, and I’m not totally exaggerated either.  Both extremes mark you either as someone in the fashion industry, or a total newcomer.   To look good, I highly recommend you go for a “girl next door” look, with nothing totally over the top (pardon the pun).  You can create your own shape, but you can also find good starter shapes on marketplace for little or no cost.

Before fitted mesh, we had standard sizing.  It’s still a good idea to stick close to one of the standard sizes, as you do tend to come across good clothing for those sizes occasionally.  So, a good starter shape is this one for FREE.  It’s modify so of course you can tweak the sliders to make it your own.

Physics

This is a system attachment that makes your breasts and butt react to gravity in appropriate ways.  Get the Firestorm Team’s Physics pack for free on the marketplace and play around with settings until you get something you like.  To do this, wear a physics attachment from the pack, and right click it in your inventory and edit it.  Then you can adjust the sliders to your liking.  Settings can differ markedly for system bodies and mesh bodies, and you should think about what you’re wearing too.  You don’t want bouncing boobies while you’re wearing a corset, for example, so you should create a few physics objects and wear the appropriate one for the occasion.

Skin

Try lots of demos (in private) while you save up your L$ to buy the best skin you can that has both system layers and at a minimum, Omega appliers for both body and head.  Seriously.

You may be really lucky and find one that has system layers and appliers for free, but it’s really rare.  I think I’ve found one in six years.

Mesh body

For FREE.  Yes, really!  You can get all the details over at Ryan’s blog post.  And now FabFree are maintaining a page listing the current free body and head giveaways around the grid.  So there’s no reason not to have a mesh body!

Clothing

Of course, there is a ton of free clothing available via various means on SL and the marketplace as this blog proves every day.  I’ve written an entire article about how to find free clothing before.  And while this blog today is mostly focused on fitted mesh clothing for the major bodies (because that’s what creators generally put out as group gifts), you can still find standard sizes as I mentioned.  Also, with the introduction of BoM, a lot of system layers have been resurrected, and lots of nice things can be found for FREE to improve your appearance.

Thanks

If you got this far, thanks for reading.  I really enjoyed writing this as it bought back fond memories of when I was first starting out, and the steps I took to gradually improve.  Here’s six years difference (note that full bright hair on the 2014 shot eeps!):

Blue – 2014 vs 2020


Getting started

Do you know we have an article to help newcomers get started in Second Life?  Well, we do!  It was this blog’s first published post.  Even though that’s quite some time ago, we still try and keep the article updated when things change.   To that end, I’ve just reviewed and made some minor revisions to it.

If you run into me in-world, I also have a version of the article as a notecard, so feel free to ask me for it.

Check out the article, and please pass it on to new people that could benefit from it.


Script memory

Thinking about script memory in LSL is very important.  In Mono, which is the modern script engine, each script gets 64K of memory allocated by default.  This is true even if the script uses only a few kilobytes of memory.

With thousands of scripts in a region, this can easily consume all the memory allocated to the region, and cause the region to start swapping memory out to disk (this all happens at the Linden Lab server running the region).  Swapping is a Bad Thing™ and will drastically slow down the sim, which you’ll see as lag.

So, what as script creators can we do?  If you have a script that takes no external input, you can limit the amount of memory your script will be allocated with a llSetMemoryLimit () function call.

Firstly, what do I mean by “takes no external input”?  An example of something that takes external input is something like a picture frame that displays the owner’s selection of textures.  The owner can drag textures into the frame and display them.  Typically to do this, the script will load the names of the textures into a list, and therefore the amount of memory used by the list is out of the control of the script creator.  It depends on how many textures the owner loads into the frame’s contents, and not how the script creator codes the script.

So, if you have something that doesn’t allocate memory out of your control, how do you know what upper limit to set with your call to llSetMemoryLimit ()?  There’s a set of memory profiling calls that will tell you.  And, as you may have guessed, this post was prompted by someone triggering the swapping problem on my home sim, which made me see if I could drop the memory that my scripts allocate.

To make things easy, I created an include file for my scripts.  Let me show you an example of how I use it.  You can access the included files by following the link in the comments next to them.

#define DEBUG
#include "debug.lsl"      // See the source of debug.lsl

#define MEMLIMIT 8000
#define PROFILE
#include "profile.lsl"    // See the source of profile.lsl

#define BLUE "c3623b1f-db83-4003-bb6d-d0d60d32c621"

phantom (integer p) {
    llSetLinkPrimitiveParamsFast (LINK_THIS, [PRIM_PHANTOM, p]);
}

default {
    state_entry () {
        init_profile ();
        llCollisionFilter ("", BLUE, TRUE);
        phantom (FALSE);
    }

    collision_start (integer n) {
        phantom (TRUE);
        llSetTimerEvent (2.0);
    }
    
    timer () {
        llSetTimerEvent (0.0);
        phantom (FALSE);
        show_profile ("timer");
    }
}

This is a simple script that goes in an invisible barrier.  It lets me walk through it by turning the barrier phantom when I collide with it, but for everyone else, it does nothing.  It takes no external input, all the memory it will use is its code, it doesn’t even use any variables!  So this is a perfect candidate for limiting script memory allocation.

The code at the top includes my standard debugging code, and the

#define MEMLIMIT 8000
#define PROFILE
#include "profile.lsl"

defines an initial memory limit of 8000 bytes, tells profile.lsl to enable profiling, and includes the profiling code.

Now notice the calls to init_profile () and show_profile ().  The init_profile () call sets the script’s memory limit based on the MEMLIMIT definition, and starts memory profiling if PROFILE is defined (and, just a warning here, memory profiling can drastically slow down your script so make sure you turn it off when done!)

The show_profile () call displays the current maximum amount of memory the script has used.  It can be tricky to figure out where to put this call, but in this case, it’s easy, as the script flow first sets everything up and then waits for a collision by me, followed by the timer firing.  So the logical place to put the show_profile () call is at the end of the timer.  All the code has run by this stage.

When you save this, and collide with it (if you’re trying this, don’t forget to replace my UUID with yours!), you’ll see something similar to the following…

Barrier: Memory profile init: 0
Barrier: Memory profile show (timer): 7598

This tells us that at the end of the timer event, the most memory the script has ever used is 7598 bytes, so our starting figure of 8000 was close.  If you see

Barrier: MEMLIMIT = 8000 too small

increase the size of MEMLIMIT until this message goes away.

I will leave a little buffer between the size reported and the limit, just in case 🙂  So in my example, I left MEMLIMIT at 8000.  Doing this simple exercise saved over 56K!  That may not sound like a lot, but don’t forget there can be thousands of scripts running in a region, so it adds up.

Once you’ve figured out a good number for MEMLIMIT, don’t forget to turn off debugging and profiling by changing the #define DEBUG and #define PROFILE to #undef DEBUG and #undef PROFILE, and leave the init_profile () and show_profile () calls in place.  The init_profile () call just sets the memory limit when PROFILE is undefined (and the show_profile () call is replaced by a blank line of code).

If every script creator did this, we’d be living much less laggy second lives!


Basic animation chooser

Sometimes, you just need something simple to pick an animation to play.  Here’s a script to do just that.  Rez a prim, drop this script in it, and a bunch of animations, then take and wear it as a HUD.

When you touch it, it will offer you a dialog where you can play any of the animations, or stop the one currently playing.

Note the links to the included scripts.  If you don’t use the Firestorm preprocessor, you can get the source for the #included bits by following the link in the comment next to the #include.  Just cut and paste the code into the top of this script, replacing the #include.

#include "dialog_plus.lsl"  // see the source of dialog_plus.lsl
#include "privchan.lsl"     // see the source of privchan.lsl

integer g_have_perms;
string g_current;
list g_animations;
key g_owner;
integer g_channel;
integer g_listen_handle;
string g_msg = "Select";

default {
    
    attach (key id) {
        if (id != NULL_KEY) {
            llResetScript ();
        } else {
            if (g_have_perms && g_current != "") {
                llStopAnimation (g_current);
            }
        }
    }

    state_entry () {
        integer n = llGetInventoryNumber (INVENTORY_ANIMATION);
        integer i;
        string name;
        g_current = "";
        g_animations = ["Stop"];
        g_have_perms = FALSE;
        g_owner = llGetOwner ();
        g_channel = privchan ();
        for (i = 0; i < n; i++) { 
            name = llGetInventoryName (INVENTORY_ANIMATION, i);
            if (llStringLength (name) > 24) {
                llOwnerSay ("Animation \"" + name + "\" longer than 24 chars, not loaded");
            } else {
                g_animations += name;
            }
        }
        llRequestPermissions (g_owner, PERMISSION_TRIGGER_ANIMATION);
    }
            
    run_time_permissions (integer p) {
        if (p & PERMISSION_TRIGGER_ANIMATION) {
            g_have_perms = TRUE;
        }
    }
    
    touch_start(integer total_number) {
        llListenRemove (g_listen_handle);
        g_listen_handle = llListen (g_channel, "", g_owner, "");
        DialogPlus (g_owner, g_msg, g_animations, g_channel, g_menu_idx = 0);
    }

    listen (integer chan, string name, key id, string data) {
        if (chan == g_channel) {
            if (data == "Back") {
                DialogPlus (g_owner, g_msg, g_animations, g_channel, --g_menu_idx);
            } else if (data == "Next") {
                DialogPlus (g_owner, g_msg, g_animations, g_channel, ++g_menu_idx);
            } else if (data == "Stop") {
                if (g_current != "" && g_have_perms) {
                    llListenRemove (g_listen_handle);
                    llStopAnimation (g_current);
                    g_current = "";
                }
            } else {
                if (g_have_perms) {
                    llListenRemove (g_listen_handle);
                    if (g_current != "") {
                        llStopAnimation (g_current);
                    }
                    g_current = data;
                    llStartAnimation (g_current);
                }
            }
        }
    }
}

Physics lesson

Warning, topless shots below the fold.

Let’s talk boobs.  I see a lot of female avatars getting it sooo wrong in SL.  In RL, we don’t have to worry, we have built-in shapes and physics, and our cleavage is controlled by the clothes we wear, not a texture, so we don’t usually have to think about it (with the exception of picking out that awesome pushup bra for a hot date lol)

In SL, none of this works automatically without some input from us.  We have shapes, physics layers, and skin options to worry about.

A decent quality skin is one of the things you should definitely splash out some money for.  You can get a reasonable skin for a couple hundred Linden Dollars, but a good one will probably set you back over L$800.  Believe me, it’s worth the time and money to find one that you like, as, along with your eyes, it’s the one thing you’ll wear all the time.

When it comes to selecting a skin, pick one with multiple cleavage options.  You want at least small for when you’re bare breasted, medium for when you’re wearing a bra or a bikini top, and “lots” for corsets and some formal gowns.  There’s nothing funnier than seeing an otherwise naked girl wearing a skin with lots of cleavage.  I keep having to ask myself “did she stick them together with superglue, or what?”  lol

Do NOT look like this, there’s someone on that beach (like me) quietly laughing at you!

Much more natural looking 🙂

With Lara by Maitreya, there’s a great option in the HUD to store skins in save slots under the “Skins & Options” tab.  My skin (Adore Peach by Lumae) has four cleavage options, and all of those are stored in the HUD in Firestorm’s “Favorite Wearables” for easy switching without having to dig through my inventory.

Now, you all know we need support, and when that support is taken away, things tend to sag, much as we wish they didn’t 🙂  Before fitmesh clothing, we had standard sizes, and to fit them, many of us saved off a modified shape along with the clothes, and the most modified sliders, at least for me, were breast size and gravity.

Using the same principle, you should have at least a few variations on your base shape for the bare/bra/corset thing with various levels of gravity applied.

And also, when that support is taken away, things tend to jiggle.  This is where physics comes in.  Physics is a system layer that you wear.  Not only can it control breast physics, but also stomach and butt as well.  Basically, the settings tell the viewer how much things should jiggle and which way they should jiggle when you move.

Again, you want at least the three options for bare/bra/corset, with plenty of bounce with little damping for bare breasted, and basically no bounce and high damping for corsets.

You can buy physics packages on the marketplace, but they’re pretty easy to set up yourself.  There’s a page on the Firestorm website with instructions on creating physics layers, so I won’t repeat all the “how-to” here, you can hop over there and read it if you need help.  The Firestorm team also have a free physics collection available on the marketplace which is a great starting point to experiment with all this.

I strongly believe it’s worth the time to set up your avatar so it looks as natural as possible.


Land settings

Woohoo!  You’ve bought yourself some land, or rented a nice parcel with full control on a private sim!  These are the minimal settings I’d recommend you tweak so you get the best experience on your new living or working quarters.

In Firestorm, you can just click the little “Land” button in the top navigation bar to pull up the land settings window while you are standing on your land.  Else you can right click on the land and select “About Land” to do the same thing.

Firstly, just the ascetics bits.  You want people to see a nice name and description for the place, not something like “Parcel #59, 1024sqm, 512 prims” or something that the landlord has set to advertise the parcel.  So change your parcel’s name and description in the General tab.

I recommend that you set your land to group owned too.  This will allow you much finer control on what you and friends can do on your land, and what others can’t.  Even for a privately owned parcels like Aeon and I have here, it’s worth the L$100 group setup fee to do it.  To do this, click on the “Set” button next to “Group”, select the group you want to own the land, and hit the “Deed” button.  There is a full article about group owned land in the knowledge base that you should read before you do this.  And of course having said that, I now probably have to write a post about group roles and permissions 🙂

Next up, and most important, is what people can do on your land.  In the “Options” tab, you should untick “Build” and “Object Entry” for everyone.  If you don’t do this, you’re just leaving yourself at the mercy of griefers.  And unless you’re a combat sim, I’d also suggest you tick “Safe (no damage)” and “No Pushing” too.  Also, that “Use with caution!” line next to “Edit Terrain”?  It’s not a joke.  Make sure it’s unticked unless you are oh, let’s see, a sandbox who wants to teach people about terrain editing? 🙂

Do you want privacy?  In residential areas, mostly you do.  So make sure “Avatars on other parcels can see and chat with avatars on this parcel” is unchecked.

And lastly, everyone needs to get along with their neighbors.  The most irritating thing you can do in my opinion to upset your neighbors is let sounds, be they from gestures, radio, media like TVs playing youtube videos, or voice, bleed into surrounding parcels.

Tick “Restrict gesture and object sounds to this parcel”.  And if you enable voice, “Restrict Voice to this parcel”.  Please, please, please do this, your neighbors will thank you!

This post sprang from a friend of ours who devastatingly had her land reclaimed, was lucky enough to get it back before the landlord returned her objects, and wanted my help to set up again.