Table of Contents

Cannon Example

The following code will launch objects from the primitive that the script is in towards any scannable avatar in a 96m range while allowing to select different velocities, both theta angles and firing type.

Usage

  1. Create a primitive which will play the role of the artillery gun. More precisely, this primitive will be the nozzle or the point of departure for the fired projectile. You may also attach this primitive to yourself and make yourself a mobile artillery gun - the script recalculates dynamically on each fired round making it suitable for a mobile application.
  2. Create some projectiles. These can be primitives (including sculpts) which will be placed inside the artillery gun you created at step 1. There are no restrictions here and you can create as many projectiles as you wish because the script will read the contents of the inventory and allow you to browse between them. When creating projectiles, keep in mind that the chosen projectile parameters (such as size) will influence the projectile flight, as well as the impact force against the target. More precisely, when the projectile descends, it is influenced by the gravitational force, which is a multiplicative combination between mass and velocity. The greater the mass of the projectile, the greater the impact force on your designated target. After you have designed your projectiles, take them to inventory and drop them inside the artillery gun.
  3. Touch the gun and go through the firing configuration formalities and the artillery gun will fire your chosen projectile based on your choices.

Features and Clarifications

The cannon script asks for:

  1. The projectile name to be fired from the artillery gun's inventory. Endless menu based, allowing finitely many projectiles to be loaded into the artillery gun.
  2. The target avatar that the projectile should hit. Sensor scan based, up to 96m.
  3. The amount of charges to fire. Supporting continuous fire. The firing interval is given by the last fired projectiles estimated flight time, plus the region time dilation.
  4. The firing velocity in meters per second.
  5. A choice between the two complementary theta angles. These two angles will both hit the target, provided that the velocity is sufficient. However, the "High" theta angle might be useful to hit a target in case there are obstacles between the artillery gun and the designated target. A "Low" theta angle will attempt a direct, gravity compensated, shot at the designated target.

The projectile, being physical, is susceptible to wind which will induce an error and might deviate the projectile's trajectory. If your designed projectiles have a low mass (directly given by the object's size), then this might be an issue. To counter wind effects, increase the object's mass and/or increase the firing velocity.

Code

artillery_cannon.lsl
///////////////////////////////////////////////////////////////////////////
//  Copyright (C) Wizardry and Steamworks 2011 - License: GNU GPLv3      //
//  Please see: http://www.gnu.org/licenses/gpl.html for legal details,  //
//  rights of fair usage, the disclaimer and warranty conditions.        //
///////////////////////////////////////////////////////////////////////////
 
///////////////////////////////////////////////////////////////////////////
//                              INTERNALS                                //
///////////////////////////////////////////////////////////////////////////
 
string object = "";
integer person = 0;
list peopleK = [];
list peopleN = [];
integer comHandle = 0;
list menu_items = [];
integer numObjects = 0;
float V0 = 0;
string angle = "";
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
integer wasMenuIndex = 0;
list wasDialogMenu(list input, list actions, string direction) {
    integer cut = 11-wasListCountExclude(actions, [""]);
    if(direction == ">" &&  (wasMenuIndex+1)*cut+wasMenuIndex+1 < llGetListLength(input)) {
        ++wasMenuIndex;
        jump slice;
    }
    if(direction == "<" && wasMenuIndex-1 >= 0) {
        --wasMenuIndex;
        jump slice;
    }
@slice;
    integer multiple = wasMenuIndex*cut;
    input = llList2List(input, multiple+wasMenuIndex, multiple+cut+wasMenuIndex);
    input = wasListMerge(input, actions, "");
    return input;
}
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
integer wasListCountExclude(list input, list exclude) {
    if(llGetListLength(input) == 0) return 0;
    if(llListFindList(exclude, (list)llList2String(input, 0)) == -1) 
        return 1 + wasListCountExclude(llDeleteSubList(input, 0, 0), exclude);
    return wasListCountExclude(llDeleteSubList(input, 0, 0), exclude);
}
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
list wasListMerge(list l, list m, string merge) {
    if(llGetListLength(l) == 0 && llGetListLength(m) == 0) return [];
    string a = llList2String(m, 0);
    if(a != merge) return [ a ] + wasListMerge(l, llDeleteSubList(m, 0, 0), merge);
    return [ llList2String(l, 0) ] + wasListMerge(llDeleteSubList(l, 0, 0), llDeleteSubList(m, 0, 0), merge);
}
 
default
{   
    touch_start(integer total_number) {
        if(llDetectedKey(0) != llGetOwner()) return;
        list objects = [];
        menu_items = [];
        integer i = llGetInventoryNumber(INVENTORY_OBJECT)-1;
        do {
            menu_items += llGetInventoryName(INVENTORY_OBJECT, i);
        } while(--i>-1);
        integer comChannel = (integer)("0x8" + llGetSubString(llGetOwner(), 0, 6));
        comHandle = llListen(comChannel, "", llGetOwner(), "");
        llDialog(llGetOwner(), "Please choose an object from the inventory:", wasDialogMenu(menu_items, ["<= Back", "", "Next =>"], ""), comChannel);
    }
 
    on_rez(integer num) {
        llResetScript();
    }
 
    listen(integer channel, string name, key id, string message) {
        if(message == "<= Back") {
            llDialog(id, "Please browse and select:\n", wasDialogMenu(menu_items, ["<= Back", "", "Next =>"], "<"), channel);
            return;
        }
        if(message == "Next =>") {
            llDialog(id, "Please browse and select:\n", wasDialogMenu(menu_items, ["<= Back", "", "Next =>"], ">"), channel);
            return;
        }
        integer comChannel = (integer)("0x8" + llGetSubString(llGetOwner(), 0, 6));
        if(channel == comChannel) {
            object = message;
            llSensor("", "", AGENT, 96, TWO_PI);
            return;
        }
        if(channel == comChannel+1) {
            llListenRemove(comHandle);
            person = (integer)message;
            list am = [];
            integer i = 11;
            do {
                am += (string)i;
            } while(--i>0);
            am += "Continuous";
            comHandle = llListen(comChannel+2, "", llGetOwner(), "");
            llDialog(llGetOwner(), "Please select an amount of charges to fire:\n", am, comChannel+2);
            return;
        }
        if(channel == comChannel+2) {
            llListenRemove(comHandle);
            if(message == "Continuous") {
                numObjects = -1;
                jump create_menu;
            }
            numObjects = (integer)message;
@create_menu;
            menu_items = [];
            integer i = 20;
            do {
                menu_items += (string)i;
            } while(--i>0);
            comHandle = llListen(comChannel+3, "", llGetOwner(), "");
            llDialog(llGetOwner(), "Please select the velocity in meters/second:\n", wasDialogMenu(menu_items, ["<= Back", "", "Next =>"], ""), comChannel+3);
            return;
        }
        if(channel == comChannel+3) {
            V0 = (integer) message;
            llListenRemove(comHandle);
            comHandle = llListen(comChannel+4, "", llGetOwner(), "");
            llDialog(llGetOwner(), "Please the possible angles:", ["High", "Low"], comChannel+4);
            return;
        }
        if(channel == comChannel+4) {
            angle = message;
            state fire;
        }
        llListenRemove(comHandle);
    }
 
    sensor(integer num) {
        peopleK = [];
        peopleN = [];
        list val = [];
        --num;
        do {
            peopleK = llListInsertList(peopleK, (list)llDetectedKey(num), 0);
            peopleN = llListInsertList(peopleN, (list)((string)num + ".) " + llDetectedName(num)), 0);
            val = llListInsertList(val, (list)((string)num), 0);
        } while(--num>-1);
        integer comChannel = (integer)("0x8" + llGetSubString(llGetOwner(), 0, 6));
        comHandle = llListen(comChannel+1, "", llGetOwner(), "");
        llDialog(llGetOwner(), "Please select a target:\n"+llDumpList2String(peopleN, "\n"), val, comChannel+1);
    }
}
 
state fire {
    state_entry() {
        llSetTimerEvent(1.0);
    }
    timer() {
        vector target = llList2Vector(llGetObjectDetails(llList2Key(peopleK, person), [OBJECT_POS]), 0);
        vector origin = llGetLocalPos();
        float=llVecDist(<target.x,target.y,0>,<origin.x,origin.y,0>);
        float valSin = 9.81*/llPow(V0, 2);
        if(valSin < -1 || valSin > 1) {
            llSetTimerEvent(0);
            llOwnerSay("Not enough velocity to reach target, please increase velocity.");
            state default;
        }
        float low_Θ = RAD_TO_DEG*llAsin(valSin)/2;
        float high_Θ = 90-low_Θ;
        rotation= ZERO_ROTATION;
        if(angle == "High")= llRotBetween(<1,0,0>,llVecNorm(<target.x-origin.x,target.y-origin.y, dΔ*llTan(high_Θ * DEG_TO_RAD) + llFabs(target.z-origin.z)>));
        else= llRotBetween(<1,0,0>,llVecNorm(<target.x-origin.x,target.y-origin.y, dΔ*llTan(low_Θ * DEG_TO_RAD) + target.z-origin.z>));
        float t = 2*V0/9.81;
        float h_max = llPow(V0, 2)*llPow(llSin(high_Θ), 2)/(2*9.81);
        llOwnerSay("---------- FIRE ROUND START ----------");
        llOwnerSay("+Distance to target: " + (string)+ "m");
        llOwnerSay("+Required angle delta to hit: " + (string)low_Θ + "˚");
        llOwnerSay("+Orientation detlta to target: " + (string)+ "m");
        llOwnerSay("+Time to impact: " + (string)t + "s");
        llOwnerSay("+Maximum flight height: " + (string)h_max + "m");
        llOwnerSay("----------- FIRE ROUND END -----------");
        llRezObject(object, llGetLocalPos(), llVecNorm(<1,0,0>*)*V0, qΔ, 0);
        if(~numObjects != 0) {
            if(--numObjects == 0) {
                llSetTimerEvent(0);
                return;
            }
        }
        llSetTimerEvent(t + llGetRegionTimeDilation());
    }
    touch_start(integer num) {
        if(llDetectedKey(0) != llGetOwner()) return;
        llOwnerSay("Firing ceased, touch to reconfigure.");
        state default;
    }
    on_rez(integer num) {
        llResetScript();
    }
}