Table of Contents

About

The following build is a lighter and cigarette pair designed to be used in-world by either clicking or using the local chat in order to trigger various actions. The scripts are designed to make the avatar open up the lighter, light the cigarette and then after a configurable elapsed time, to turn the cigarette off.

Screenshots

Usage

In order to smoke, both the lighter and the cigarette have to be worn. Then, the lighter can be activated by either clicking (touching) the lighter or by typing:

/1smoke

on local chat at which point a dialog will pop up with various buttons.

Currently, the buttons represent the time in minutes to smoke the cigarette for as well as a button to dispose of the cigarette at any point in time.

Design

In order to smoke:

There is some complexity to the build given the scripts that have been used even though the design was somewhat simplified from a previous build created by Wizardry and Steamworks.

The spark and the flame are activated in sequence, such that the controller script will message the spark script that will, in turn, activate the flame script that will also, in turn deactivate the spark script, every time that the user lights a cigarette.

Similarly, states are used for separation of concerns as well as to achieve a better linearization of contexts. In the listening state, the scripts will maintain two open channels, one positive and one symmetrically negative, in order to listen for the /1smoke chat command, respectively to process the response from the dialog.

Lastly, compared to the previous lighter build, there is no fiddling with the lighter and the monogram has been removed as well as all hardcoded attachment positions and rotations removed. The only hardcoded positions and rotations are the ones that pertain to the cap of the lighter that will always have to be relative to the lighter.

The following files are exports of the lighter and the cigarette:

Lighter

The lighter contains three scripts:

Controller

The controller script is responsible for presenting the dialog menu when the user clicks the lighter or types /1smoke on local chat as well as passing the needed messages, playing the flicking sounds or triggering the corresponding animations in order to use the lighter and cigarette.

///////////////////////////////////////////////////////////////////////////
//  Copyright (C) Wizardry and Steamworks 2022 - License: GNU GPLv3      //
///////////////////////////////////////////////////////////////////////////
 
integer PRIVATE_CHANNEL;
integer TRIGGER_CHANNEL;
string WORD;
 
list channels = [];
integer time;
 
default
{
    state_entry() {
        PRIVATE_CHANNEL = (integer)("0x8" + llGetSubString(llGetOwner(), 0, 6));
        TRIGGER_CHANNEL = 1;
        WORD = "smoke";
        time = 500;
 
        llPreloadSound("bc1b20c7-fc34-6df2-2296-64be51390be2");
 
        state listening;
    }
}
 
state listening {
    state_entry() {
        channels = [
            llListen(
                TRIGGER_CHANNEL,
                "",
                llGetOwner(),
                WORD
            ),
            llListen(
                -TRIGGER_CHANNEL,
                "",
                llGetOwner(),
                ""
            )
        ];
    }
    listen(integer channel, string name, key id, string message) {
        if(channel == TRIGGER_CHANNEL) {
            llDialog(
                llGetOwner(),
                "How many minutes do you wish to smoke for?",
                [ "2", "4", "8", "16", "off" ],
                -TRIGGER_CHANNEL
            );
            return;
        }
 
        if(channel == -TRIGGER_CHANNEL) {
            if(message == "off") {
                state off;
            }
 
            time = (integer)message * 1000;
            state on;
        }
    }
    touch_start(integer num) {
        llDialog(
            llGetOwner(),
            "How many minutes do you wish to smoke for?",
            [ "2", "4", "8", "16", "off" ],
            -TRIGGER_CHANNEL
        );
    }
    attach(key id) {
        llResetScript();
    }
    on_rez(integer num) {
        llResetScript();
    }
    state_exit() {
        integer i = llGetListLength(channels);
        do {
            llListenRemove(
                llList2Integer(channels, i)
            );
        } while(--i > 0);
    }
}
 
state on {
    state_entry() {
        llRequestPermissions(
            llGetOwner(),
            PERMISSION_TRIGGER_ANIMATION
        );
    }
    run_time_permissions(integer perm) {
        if(!(perm & PERMISSION_TRIGGER_ANIMATION)) {
            return;
        }
 
        llWhisper(
            PRIVATE_CHANNEL,
            (string)time
        );
        llPlaySound(
            "bc1b20c7-fc34-6df2-2296-64be51390be2",
            1.0
        );
        llStartAnimation("light");
        llMessageLinked(LINK_SET, 1, "cover", NULL_KEY);
        llMessageLinked(LINK_SET, 1, "spark", NULL_KEY);
 
        state animating;
    }
    attach(key id) {
        llResetScript();
    }
    on_rez(integer num) {
        llResetScript();
    }
}
 
state off {
    state_entry() {
        llWhisper(
            PRIVATE_CHANNEL,
            (string)0
        );
        state listening;
    }
}
 
state animating {
    state_entry() {
        llSetTimerEvent(3 + llFrand(1));
    }
    run_time_permissions(integer perm) {
        if(!(perm & PERMISSION_TRIGGER_ANIMATION)) {
            return;
        }
 
        llMessageLinked(LINK_SET, 0, "cover", NULL_KEY);
        llMessageLinked(LINK_SET, 0, "flame", NULL_KEY);
        llStopAnimation("light");
        state listening;
    }
    timer() {
        llRequestPermissions(
            llGetOwner(),
            PERMISSION_TRIGGER_ANIMATION
        );
    }
    attach(key id) {
        llResetScript();
    }
    on_rez(integer num) {
        llResetScript();
    }
    state_exit() {
        llSetTimerEvent(0);
    }
}

Rotate

The rotate script is triggered by the controller script as the animation plays in order to open the lighter.

///////////////////////////////////////////////////////////////////////////
//  Copyright (C) Wizardry and Steamworks 2022 - License: GNU GPLv3      //
///////////////////////////////////////////////////////////////////////////
 
default
{
    link_message(integer sender_num, integer num, string str, key id) {
        if(str != "cover") {
            return;
        }
 
        if(num) {
            llSetLinkPrimitiveParamsFast(
                LINK_THIS, 
                [
                    PRIM_POS_LOCAL, <-0.00007, 0.05758, 0.04373>, 
                    PRIM_ROT_LOCAL, <0.51410, -0.00003, -0.00008, 0.85773>
                ]
            );
            return;
        }
 
        llSetLinkPrimitiveParamsFast(
            LINK_THIS, 
            [
                PRIM_POS_LOCAL, <-0.00010, -0.00039, 0.04481>, 
                PRIM_ROT_LOCAL, <1.00000, -0.00007, -0.00002, 0.00000>
            ]
        );
    }
}

Spark

The spark script is timed and will activate after the lighter cap is opened and the animation plays.

///////////////////////////////////////////////////////////////////////////
//  Copyright (C) Wizardry and Steamworks 2022 - License: GNU GPLv3      //
///////////////////////////////////////////////////////////////////////////
default
{
    state_entry() {
        llParticleSystem([]);
    }
    link_message(integer sender_num, integer num, string str, key id) {
        if(str != "spark") {
            return;
        }
 
        if(num) {
           llParticleSystem([
           PSYS_SRC_TEXTURE, llGetInventoryName(INVENTORY_TEXTURE, 0),
           PSYS_PART_START_SCALE, <0.1, 0.1, FALSE>, PSYS_PART_END_SCALE, <0.0, 0.0, FALSE>, 
           PSYS_PART_START_COLOR, <1.0, 1.0, 1.0>,    PSYS_PART_END_COLOR, <0.0, 1.0, 1.0>, 
           PSYS_PART_START_ALPHA, (float) 1.0,         PSYS_PART_END_ALPHA, (float) 1.0,     
 
           // Production Parameters:
           PSYS_SRC_BURST_PART_COUNT, (integer)  1, 
           PSYS_SRC_BURST_RATE,         (float) 0.9,  
           PSYS_PART_MAX_AGE,           (float)  0.3, 
        // PSYS_SRC_MAX_AGE,            (float)  0.00, 
 
           // Placement Parameters:
           PSYS_SRC_PATTERN, (integer) 2, // 1=DROP, 2=EXPLODE, 4=ANGLE, 8=CONE,
 
           // Placement Parameters (for any non-DROP pattern):
           PSYS_SRC_BURST_SPEED_MIN, (float) 00.1,   PSYS_SRC_BURST_SPEED_MAX, (float) 00.1, 
        // PSYS_SRC_BURST_RADIUS, (float) 00.00,
 
           // Placement Parameters (only for ANGLE & CONE patterns):
           PSYS_SRC_ANGLE_BEGIN, (float) 0.25 * PI,   PSYS_SRC_ANGLE_END, (float) 0.00 * PI,  
        // PSYS_SRC_OMEGA, <00.00, 00.00, 00.00>,  
 
           // After-Effect & Influence Parameters:
           PSYS_SRC_ACCEL, < 00.00, 00.00, 00.50>,
        // PSYS_SRC_TARGET_KEY, (key) llGetLinkKey(llGetLinkNumber() + 1), 
 
           PSYS_PART_FLAGS, (integer) ( 0                  // Texture Options:     
                                | PSYS_PART_INTERP_COLOR_MASK   
                                | PSYS_PART_INTERP_SCALE_MASK   
                                | PSYS_PART_EMISSIVE_MASK   
                                | PSYS_PART_FOLLOW_VELOCITY_MASK
                                                  // After-effect & Influence Options:
                                | PSYS_PART_WIND_MASK            
                                | PSYS_PART_BOUNCE_MASK          
                             // | PSYS_PART_FOLLOW_SRC_MASK     
                                | PSYS_PART_TARGET_POS_MASK     
                             // | PSYS_PART_TARGET_LINEAR_MASK    
                            ) 
            //end of particle settings                     
            ]);
            llSetTimerEvent(1);
            return;
        }
        llParticleSystem([]);
    }
    timer() {
        llSetTimerEvent(0);
        llMessageLinked(LINK_SET, 1, "flame", NULL_KEY);
    }
}

Flame

The flame script is triggered after the spark particles play in order to provide the visual effect of a flame for lighting the cigarette.

///////////////////////////////////////////////////////////////////////////
//  Copyright (C) Wizardry and Steamworks 2022 - License: GNU GPLv3      //
///////////////////////////////////////////////////////////////////////////
 
default
{
    state_entry() {
        llParticleSystem([]);
    }
    link_message(integer sender_num, integer num, string str, key id) {
        if(str != "flame") {
            return;
        }
 
        if(num) {
            llParticleSystem([  // start of particle settings
           // Texture Parameters:
           PSYS_SRC_TEXTURE, llGetInventoryName(INVENTORY_TEXTURE, 0),
           PSYS_PART_START_SCALE, <0.04, 0.04, FALSE>, PSYS_PART_END_SCALE, <0.07, 0.08, FALSE>, 
           PSYS_PART_START_COLOR, <1.00,1.00,0.00>,    PSYS_PART_END_COLOR, <0.40,0.00,0.00>, 
           PSYS_PART_START_ALPHA, (float) 0.8,         PSYS_PART_END_ALPHA, (float) 0.0,     
 
           // Production Parameters:
           PSYS_SRC_BURST_PART_COUNT, (integer)  2, 
           PSYS_SRC_BURST_RATE,         (float)  0.12,  
           PSYS_PART_MAX_AGE,           (float)  0.25, 
        // PSYS_SRC_MAX_AGE,            (float)  0.00, 
 
           // Placement Parameters:
           PSYS_SRC_PATTERN, (integer) 8, // 1=DROP, 2=EXPLODE, 4=ANGLE, 8=CONE,
 
           // Placement Parameters (for any non-DROP pattern):
           PSYS_SRC_BURST_SPEED_MIN, (float) 00.05,   PSYS_SRC_BURST_SPEED_MAX, (float) 00.30, 
        // PSYS_SRC_BURST_RADIUS, (float) 00.00,
 
           // Placement Parameters (only for ANGLE & CONE patterns):
           PSYS_SRC_ANGLE_BEGIN, (float) 0.05 * PI,   PSYS_SRC_ANGLE_END, (float) 0.00 * PI,  
        // PSYS_SRC_OMEGA, <00.00, 00.00, 00.00>,  
 
           // After-Effect & Influence Parameters:
           PSYS_SRC_ACCEL, < 00.00, 00.00, 00.50>,
        // PSYS_SRC_TARGET_KEY, (key) llGetLinkKey(llGetLinkNumber() + 1), 
 
           PSYS_PART_FLAGS, (integer) ( 0                  // Texture Options:     
                                | PSYS_PART_INTERP_COLOR_MASK   
                                | PSYS_PART_INTERP_SCALE_MASK   
                                | PSYS_PART_EMISSIVE_MASK   
                                | PSYS_PART_FOLLOW_VELOCITY_MASK
                                                  // After-effect & Influence Options:
                                | PSYS_PART_WIND_MASK            
                                | PSYS_PART_BOUNCE_MASK          
                             // | PSYS_PART_FOLLOW_SRC_MASK     
                             // | PSYS_PART_TARGET_POS_MASK     
                             // | PSYS_PART_TARGET_LINEAR_MASK    
                            ) 
            //end of particle settings                     
            ]);
            llMessageLinked(LINK_SET, 0, "spark", NULL_KEY);
            return;
        }
 
        llParticleSystem([]);
    }
}

Cigarette

The cigarette contains a single script that is placed at the tip, within the ashes and is responsible for hiding or showing the entire cigarette as well as displaying the smoke particles once the cigarette is ignited.

///////////////////////////////////////////////////////////////////////////
//  Copyright (C) Wizardry and Steamworks 2015 - License: GNU GPLv3      //
///////////////////////////////////////////////////////////////////////////
 
on() {
    integer i = llGetNumberOfPrims();
    do {
        llSetLinkAlpha(i, 1.0, ALL_SIDES);
    } while(--i > 0);
 
    llSetLinkPrimitiveParamsFast(
        LINK_THIS,
        [
            PRIM_GLOW, ALL_SIDES, 0.06
        ]
    );
 
    llParticleSystem(
        [
            PSYS_PART_FLAGS, PSYS_PART_INTERP_COLOR_MASK | PSYS_PART_INTERP_SCALE_MASK | PSYS_PART_EMISSIVE_MASK | PSYS_PART_BOUNCE_MASK | PSYS_PART_FOLLOW_VELOCITY_MASK,
            PSYS_SRC_PATTERN,           PSYS_SRC_PATTERN_ANGLE,
            PSYS_PART_START_COLOR,      <0.7, 0.7, 0.7>,
            PSYS_PART_END_COLOR,        <0.3, 0.3, 0.3>,
            PSYS_PART_START_ALPHA,      1.0,
            PSYS_PART_END_ALPHA,        0.7,
            PSYS_PART_START_SCALE,      <0.035, 0.035, 0.0>,
            PSYS_PART_END_SCALE,        <1, 1, 0.0>,
            PSYS_PART_MAX_AGE,          60.0,
            PSYS_SRC_ACCEL,             <0.0, 0.0, 0.08>,
            PSYS_SRC_TEXTURE,           "5de058da-95f0-2736-b0e0-e218184ddece",
            PSYS_SRC_BURST_RATE,        0.05,
            PSYS_SRC_ANGLE_BEGIN,       PI,
            PSYS_SRC_ANGLE_END,         (PI - 0.1),
            PSYS_SRC_BURST_PART_COUNT,  1,
            PSYS_SRC_BURST_RADIUS,      0.01,
            PSYS_SRC_BURST_SPEED_MIN,   0.0,
            PSYS_SRC_BURST_SPEED_MAX,   0.04
        ]
    );
}
 
off() {
    llParticleSystem([]);
 
    integer i = llGetNumberOfPrims();
    do {
        llSetLinkAlpha(i, 0.0, ALL_SIDES);
    } while(--i > 0);
 
    llSetLinkPrimitiveParamsFast(
        LINK_THIS,
        [
            PRIM_GLOW, ALL_SIDES, 0.0
        ]
    );
}
 
integer isOwner(key object) {
    key owner = llList2Key(
        llGetObjectDetails(
            object, [
                OBJECT_OWNER
            ]
        ),
        0
    );
 
    return owner == llGetOwner();
}
 
integer PRIVATE_CHANNEL;
 
integer handle;
integer time;
 
default
{
    state_entry() {
        PRIVATE_CHANNEL = (integer)("0x8" + llGetSubString(llGetOwner(), 0, 6));
 
        state resting;
    }
}
 
state resting {
    state_entry() {
        handle = llListen(
            PRIVATE_CHANNEL,
            "",
            "",
            ""
        );
    }
    listen(integer channel, string name, key id, string message) {
        if(!isOwner(id)) {
            return;
        }
 
        time = (integer)message;
        if(time == 0) {
            off();
            return;
        }
 
        state smoking;
    }
    on_rez(integer param) {
        llResetScript();
    }
    attach(key id) {
        llResetScript();
    }
    state_exit() {
        llListenRemove(handle);
    }
}
 
state smoking {
    state_entry() {
        handle = llListen(
            PRIVATE_CHANNEL,
            "[WaS] Lighter & Cigarette - Lighter",
            "",
            ""
        );
 
        on();
 
        llSetTimerEvent(
            time
        );
    }
    listen(integer channel, string name, key id, string message) {
        if(!isOwner(id)) {
            return;
        }
 
        if((integer)message == 0) {
            off();
            state resting;
        }
    }
    timer() {
        off();
 
        state resting;
    }
    on_rez(integer param) {
        llResetScript();
    }
    attach(key id) {
        llResetScript();
    }
    state_exit() {
        llSetTimerEvent(0);
        llListenRemove(handle);
    }
}