fish.lsl
///////////////////////////////////////////////////////////////////////////
//  Copyright (C) Wizardry and Steamworks 2014 - License: GNU GPLv3      //
//  Please see: http://www.gnu.org/licenses/gpl.html for legal details,  //
//  rights of fair usage, the disclaimer and warranty conditions.        //
///////////////////////////////////////////////////////////////////////////
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
string wasKeyValueSet(string k, string v, string data) {
    if(llStringLength(k) == 0) return "";
    if(llStringLength(v) == 0) return "";
    if(llStringLength(data) == 0) return k + "=" + v;
    integer i = llListFindList(
        llList2ListStrided(
            llParseString2List(data, ["&", "="], []), 
            0, -1, 2
        ), 
    [ k ]);
    if(i != -1) return llDumpList2String(
        llListReplaceList(
            llParseString2List(data, ["&"], []), 
            [ k + "=" + v ], 
        i, i), 
    "&");
    return data + "&" + k + "=" + v;
}
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2015 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
string wasKeyValueGet(string k, string data) {
    if(llStringLength(data) == 0) return "";
    if(llStringLength(k) == 0) return "";
    list a = llParseStringKeepNulls(data, ["&", "="], []);
    integer i = llListFindList(llList2ListStrided(a, 0, -1, 2), [ k ]);
    if(i != -1) return llList2String(a, 2*i+1);
    return "";
}
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2011 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
string wasProgress(integer percent, integer length, list symbols) {
    percent /= (integer)((float)100.0/(length));
    string p = llList2String(symbols,0);
    integer itra = 0;
    do {
        if(itra>percent-1) p += llList2String(symbols,2);
        else p += llList2String(symbols,1);
    } while(++itra<length);
    return p + llList2String(symbols,3);
}
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
vector wasPercentToGradient(float percent, string rgb) {
    if(llStringLength(rgb) != 2) {
        llSay(DEBUG_CHANNEL, "Assert failed, rgb parameter must consist of a pair of either r, g, or b.");
        return ZERO_VECTOR;
    }
    string a = llGetSubString(rgb, 0, 0);
    string b = llGetSubString(rgb, 1, 1);
    list col = [ "r", "g", "b" ];
    integer ax = llListFindList(col, (list)a);
    integer bx = llListFindList(col, (list)b);
    if(ax == -1 || bx == -1) {
        llSay(DEBUG_CHANNEL, "Asset failed, rgb parameters must contain either r, g, or b letters.");
        return ZERO_VECTOR;
    }
    col = llListReplaceList(col, (list)((100-percent)/100), ax, ax);
    col = llListReplaceList(col, (list)(percent/100), bx, bx);
    return 2*<llList2Float(col, 0), llList2Float(col, 1), llList2Float(col, 2)>;
}
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
string wasRepeatString(string in, string sep, integer num) {
    if(num == 0) return "";
    list i = llParseString2List(in, (list)sep, []);
    integer l = llGetListLength(i);
    if(l >= num) return llDumpList2String(llList2List(i, 0, l-(l-num)-1), sep);
    return wasRepeatString(in + sep + in, sep, num);
}
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
string wasKeyValueEncode(list data) {
    list k = llList2ListStrided(data, 0, -1, 2);
    list v = llList2ListStrided(llDeleteSubList(data, 0, 0), 0, -1, 2);
    data = [];
    do {
        data += llList2String(k, 0) + "=" + llList2String(v, 0);
        k = llDeleteSubList(k, 0, 0);
        v = llDeleteSubList(v, 0, 0);
    } while(llGetListLength(k) != 0);
    return llDumpList2String(data, "&");
}
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2014 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
string wasKeyValueURIEscape(string data) {
    list i = llParseString2List(data, ["&", "="], []);
    list output = [];
    do {
        output += llList2String(i, 0) + "=" + llEscapeURL(llList2String(i, 1));
        i = llDeleteSubList(i, 0, 1);
    } while(llGetListLength(i) != 0);
    return llDumpList2String(output, "&");
}
 
// Set this to the centre or origin point from where all  
// random points will be picked.
vector _iPos = ZERO_VECTOR;
// Set this to the maximal distance distance in meters  
// from the origin point that the object will travel.
float WANDER_DISTANCE = 5;
 
integer _targetID = 0;
 
///////////////////////////////////////////////////////////////////////////
//    Copyright (C) 2013 Wizardry and Steamworks - License: GNU GPLv3    //
///////////////////////////////////////////////////////////////////////////
vector wasCirclePoint(float radius) {
    float x = llPow(-1, 1 + (integer) llFrand(2)) * llFrand(radius*2);
    float y = llPow(-1, 1 + (integer) llFrand(2)) * llFrand(radius*2);
    if(llPow(x,2) + llPow(y,2) <= llPow(radius,2))
        return _iPos + <x, y, 0>;
    return wasCirclePoint(radius);
}
 
moveTo(vector position) {
    llTargetRemove(_targetID);
    _targetID = llTarget(position, .8);
    llLookAt(position, .6, .6);
    llMoveToTarget(position, speed);    
}
 
integer time = 60;
integer value = 0;
integer channel = 0;
string name = "";
string kill = "";
integer k = 0;
list gossip = [];
float speed = 0;
 
//"Bloody Me", "Gramps", "Mr. Fatman", "Wormington", "Lil' Chubby", "Gossipish"
list MODS = [];
 
default {
    listen(integer channel, string name, key id, string message) {
        if(llGetOwner() != llGetOwnerKey(id)) return;
        if(wasKeyValueGet("creator", message) != llGetCreator()) return;
        string mod = wasKeyValueGet("use", message);
        if(llStringLength(mod) == 0) return;
        MODS += mod;
 
        // "Lil' Chubby" - scale x2
        if(mod == "Lil' Chubby") {
            llStopMoveToTarget();
            llTargetRemove(_targetID);
            llSetStatus(STATUS_PHYSICS, FALSE);
            llSetScale(3*llGetScale());
            llSetLinkPrimitiveParamsFast(2, [PRIM_SIZE, 3 * llList2Vector(llGetLinkPrimitiveParams(2, [PRIM_SIZE]), 0)]);
            llSetStatus(STATUS_PHYSICS|STATUS_BLOCK_GRAB, TRUE);
            moveTo(wasCirclePoint(WANDER_DISTANCE));
            return;
        }
        // "Mr. Fatman" - die
        if(mod == "Mr. Fatman") {
            string user = wasKeyValueGet("user", message);
            if(llStringLength(user) == 0) return;
            kill = wasKeyValueEncode([
                "killer", user, 
                "name", llKey2Name(user),
                "weapon", "Mr. Fatman",
                "value", (string)value, 
                "region", llGetRegionName(), 
                "owner", llGetOwner(),
                "fish", llGetObjectName(),
                "creator", llGetCreator()
            ]);
            state score;
         }
         // "Gramps" - freeze
         if(mod == "Gramps") {
             llStopMoveToTarget();
             llTargetRemove(_targetID);
             llSetStatus(STATUS_PHYSICS, FALSE);
             return;
         }
         // "Bloody Me" - bleed and explode
         if(mod == "Bloody Me") {
            llParticleSystem([
                PSYS_PART_START_SCALE, <.2,.2, FALSE>,  PSYS_PART_END_SCALE, <.2,.2, FALSE>, 
                PSYS_PART_START_COLOR, <1,0,0>,      PSYS_PART_END_COLOR, <.1,0,0>, 
                PSYS_PART_START_ALPHA, .7,       PSYS_PART_END_ALPHA, .2,
                PSYS_SRC_BURST_PART_COUNT, 1, 
                PSYS_SRC_BURST_RATE, (float) 1.5,
                PSYS_PART_MAX_AGE, 1, 
                PSYS_SRC_MAX_AGE, 0,
                PSYS_SRC_PATTERN, 1,
                PSYS_SRC_ACCEL, <0.0,0.0,-.5>,
                PSYS_PART_FLAGS,( PSYS_PART_INTERP_COLOR_MASK   
                                  | PSYS_PART_INTERP_SCALE_MASK   
                                  | PSYS_PART_EMISSIVE_MASK )                     
            ]);
            return;
         }
         // "Gossipish" - read notecard, talk randomly
         if(mod == "Gossipish") {
             k = 0;
             llGetNotecardLine("gossip", k);
             return;
         }
    }
    dataserver(key id, string data) {
        if(data == EOF) return;
        if(data == "") jump continue;
        gossip += data;
@continue;
        llGetNotecardLine("gossip", ++k);
    }
    collision_start(integer num) {
        --num;
        do {
            if(llDetectedType(num) == (ACTIVE|AGENT))jump continue;
            float velocity = llVecMag(llDetectedVel(num));
            if(velocity == 0) jump continue;
            time -= (integer)velocity;
            llSetTimerEvent(0);
            key wo = llGetOwnerKey(llDetectedKey(num));
            if(time <= 0) {
                kill = wasKeyValueEncode([
                    "killer", wo, 
                    "name", llKey2Name(wo),
                    "weapon", llDetectedName(num),
                    "value", (string)value, 
                    "region", llGetRegionName(), 
                    "owner", llGetOwner(),
                    "fish", llGetObjectName(),
                    "creator", llGetCreator()
                ]);
                state score;
            }
            llSetTimerEvent(1);
            if(speed-.3 > 0)speed -= .3;
            if(llListFindList(MODS, (list)"Bloody Me") != -1) {
                llParticleSystem([
                    PSYS_PART_START_SCALE, <.01, .014, 0>, PSYS_PART_END_SCALE, <.1, .14, 0>, 
                    PSYS_PART_START_COLOR, <1,0,0>,    PSYS_PART_END_COLOR, <.1,0,0>,
                    PSYS_SRC_BURST_PART_COUNT, 0x7FFFFFFF,
                    PSYS_PART_MAX_AGE, 1.25,
                    PSYS_SRC_MAX_AGE, 1.25,
                    PSYS_SRC_PATTERN, 8,
                    PSYS_SRC_BURST_SPEED_MIN, (float)1,   PSYS_SRC_BURST_SPEED_MAX, (float)1.2, 
                    PSYS_SRC_ANGLE_BEGIN, 0,    PSYS_SRC_ANGLE_END, TWO_PI,       
                    PSYS_PART_FLAGS, ( PSYS_PART_INTERP_SCALE_MASK
                                     | PSYS_PART_INTERP_COLOR_MASK    
                                     | PSYS_PART_EMISSIVE_MASK )                  
                ]);
            }
@continue;
        } while(--num>-1);
        moveTo(wasCirclePoint(WANDER_DISTANCE));
 
    }
    on_rez(integer num) {
        if(num == 0 && llGetOwner() != llGetCreator()) llDie();
        channel = num;
        llListen(channel, "", "", "");
        value = (integer)(1+llFrand(5));
        speed = 1 + (float)5/value;
        name = llGetObjectName();
        _iPos = llGetPos();
 
        llSetLinkPrimitiveParamsFast(LINK_SET, [PRIM_TEMP_ON_REZ, TRUE]);
        llSetStatus(STATUS_PHYSICS|STATUS_BLOCK_GRAB, TRUE);
        moveTo(wasCirclePoint(WANDER_DISTANCE));
        llSetTimerEvent(1);
    }
    at_target(integer tnum, vector targetpos, vector ourpos) {
        if(tnum != _targetID) return;
        moveTo(wasCirclePoint(WANDER_DISTANCE));
    }
    timer() {
        if(time <= 0) llDie();
        integer life = (integer)(100*(60-(60-(float)time))/60);
        llSetText(
            llGetObjectName() + "\n" +
            "Life: " + wasProgress(life, 5, ["[", "█", "░", "]"]) + "\n" +
            "Value: " + wasRepeatString("✪", " ", value) + "\n" +
            llList2String(gossip, (integer)llFrand(llGetListLength(gossip)))
        , wasPercentToGradient(life, "rg"), 1);
        --time;
    }
}
 
state score {
    state_entry() {
        llTargetRemove(_targetID);
        llStopMoveToTarget();
        llVolumeDetect(FALSE);
        llSetStatus(STATUS_PHYSICS|STATUS_BLOCK_GRAB, TRUE);
        llSetPhysicsMaterial(GRAVITY_MULTIPLIER, 1, 0, 0, 0);
        llSetBuoyancy(0.85);
        llSetText("✖_✖ " + llGetObjectName() + " ✖_✖" + "\n" + 
            "Loot: " + llKey2Name((key)wasKeyValueGet("killer", kill))
        , <.8,.8,.8>, 1);
    }
    touch_start(integer num) {
        if(channel != 0) llRegionSay(channel, kill);
        llGetNotecardLine("password", 0);
    }
    dataserver(key id, string password) {
        llHTTPRequest("http://fish.grimore.org/kill.php", [HTTP_METHOD, "POST", HTTP_MIMETYPE, "application/x-www-form-urlencoded"], wasKeyValueURIEscape(wasKeyValueSet("password", password, kill)));
    }
    http_response(key id, integer status, list metadata, string body) {
        llHTTPResponse(id, 200, "Ok");
        llDie();
    }
}