// Davan Camus, 2006 June // Control Unit for Cavorite Flotation Devices string device = "Cavorite Flotation Device"; integer COMMAND_CHANNEL = 17; integer gListenerNumber = 0; integer gTicks = 0; float TIMER_RATE = .3; // easy on the servers integer gHeldTicks = 0; // for control accel integer gHeldKeys = 0; vector gHere; // updated on every timer tick vector gVel; // velocity in global coords rotation gRot; // our current direction key gOwner; // updated on timer tick integer gOwnerInfo; // bitfields from llGetAgentInfo // User Mode Settings integer gUWalkingGravity = 100; // gravity we apply when walking integer gUJumpingGravity = 14; // gravity we apply when jumping float gUInertia = 0; // applies drag opposing your vel integer gUSafetyAnchor = 1; // activate safety anchor if falling! (if we're in controlled-flight mode) float gJumpTime = 0; // set a time when you start a jump, to return to walking gravity. integer S_OFF = 0x0001; // turned off -- not receiving keys integer S_GROUND = 0x0002; // on, ready to jump or fly integer S_JUMP = 0x0004; // in process of jumping integer S_FLOAT = 0x0008; // currently floating integer S_ANCHOR = 0x0010; // floating, but continuously anchored integer gHighJump = 0; // if true, allow low-grav jumping integer gGlide = 0; // if true, you keep moving without automatically braking integer gLanding = 1; // if true, down + on-ground causes landing integer MOTION_KEYS = 0x0033; // any combination of up down fwd back left right. float gVelThreshold = .8; // how fast you must be going for the counterbrake to activate float gFallThreshold = 25.; // falling rate threshold float gTeleThreshold = 50.; // if we move this or more, assume it was a teleporter: no brake! string gOffCaution = "Caution: Device is now completely off. Your emergency etheric anchor is disabled. You can fall."; string gGlideCaution = "Caution: Flotation Glide is active. Use forward+backward to brake."; string gLandCaution = "Caution: Ground-landing is disabled. Use up+down to land."; // +--------------------------------------------- // | d([Things,to, print]); // | prints a message if gDebug isnt zero // | // | (I find it easier to type the [list,notation] than all // | that tedious casting-to-string and adding stuff // | // | For example, d(["The time is now: ",llGetTime()]); // | integer gDebug = 0; // set to 1 for debug output d(list debugList) { if(!gDebug) return; else { string s = list2String(debugList); llOwnerSay(s); } } // +----------------------------------- // | Format a list into a string by converting // | each element in the list to a string // | string list2String(list x) { string s = ""; integer len = llGetListLength(x); integer i; for(i = 0; i < len; i++) { integer t = llGetListEntryType(x,i); if(t == TYPE_STRING) s += llList2String(x,i); else if(t == TYPE_INTEGER) s += (string)llList2Integer(x,i); else if(t == TYPE_FLOAT) s += (string)llList2Float(x,i); else if(t == TYPE_FLOAT) s += (string)llList2Float(x,i); else if(t == TYPE_VECTOR) s += (string)llList2Vector(x,i); else s += "(item " + (string)i + ")"; } return s; } integer gAnimationPermission = 0; integer gControlPermission = 0; // corresponds to enable and disable commands. integer gOldAttachPoint = 0; perms(integer onoff) { if(onoff == 0) { llReleaseControls(); gControlPermission = 0; return; } integer attachPoint = llGetAttached(); // are we worn? if((attachPoint != gOldAttachPoint) || !gControlPermission) { // attach point changed, release all... gOldAttachPoint = attachPoint; gAnimationPermission = 0; gControlPermission = 0; llReleaseControls(); // and try now. if(gOldAttachPoint) // !0 means worn { llSleep(1); llRequestPermissions(gOwner,PERMISSION_TAKE_CONTROLS | PERMISSION_TRIGGER_ANIMATION); // control. } } } init() { MOTION_KEYS = CONTROL_FWD | CONTROL_BACK | CONTROL_UP | CONTROL_DOWN | CONTROL_LEFT | CONTROL_RIGHT; gOwner = llGetOwner(); // reset the chat listener llListenRemove(gListenerNumber); gListenerNumber = llListen(COMMAND_CHANNEL,"",gOwner,""); // only you d(["init: listening on ",COMMAND_CHANNEL]); gTicks = 0; llSetTimerEvent(TIMER_RATE); // easy on the servers link("debug 0"); //link("brake"); // go through shield-fadeout perms(0); perms(1); gState = 0; // when permissions come back, we'll set ground mode. // when you put it on, go into some default easiest-to-use state. gGlide = 0; gHighJump = 0; gLanding = 1; } doHelp(key avKey) { // give the first notecard... string helpCard = llGetInventoryName(INVENTORY_NOTECARD,0); llGiveInventory(avKey,helpCard); } doCommand(key avKey,string message) { if(message == "") { doDialog(); return; } // If the command starts with a dot, then bring up the dialog after. integer dialogAfter = 0; if(llGetSubString(message,0,0) == ".") { dialogAfter = 1; message = llGetSubString(message,1,-1); } list messageL = llParseString2List(message,[" "],[]); string cmd = llToLower(llList2String(messageL,0)); integer arg1 = llList2Integer(messageL,1); string arg1S = llToLower(llList2String(messageL,1)); if(arg1S == "" || arg1S == "on") arg1 = 1; integer handledMessage = 1; // innocent til guilty if(cmd == "debug") { gDebug = arg1; d(["memory: ",llGetFreeMemory()]); //link(message); // pass debug message down to linked fellas } else if(cmd == "on") { setState(S_GROUND); sayReady(); } else if(cmd == "off") { setState(S_OFF); llOwnerSay(gOffCaution); } else if(cmd == "jump") gHighJump = arg1; else if(cmd == "glide") { gGlide = arg1; setState(S_FLOAT); if(gGlide) llOwnerSay(gGlideCaution); } else if(cmd == "landing") { gLanding = arg1; if(!gLanding) llOwnerSay(gLandCaution); } else if(cmd == "init") init(); else if(cmd == "help...") doHelp(avKey); else if(cmd == "float") { setState(S_FLOAT); doControls(0); } else if(cmd == "fall") { setState(S_GROUND); doControls(0); } else if(cmd == "link") { // send link message direct (for debug) or parse as commands string linkMessage = llGetSubString(message,5,-1); link(linkMessage); return; } else if(cmd == "gravity") setJumpingGravity(arg1); else if(cmd == "safety") gUSafetyAnchor = arg1; else handledMessage = 0; // show yellow for good or orange for bad command if(handledMessage) link("pingShort 1 1 0 .3 .1"); else link("pingShort 1 .4 0 .2 .1"); if(dialogAfter) doDialog(); } string bs(integer b) { if(b) return "On"; return "Off"; } doDialog() { key av = llGetOwner(); string s = "Personal Cavorite Device 0.81" + "\nMechanism by Davan Camus" + "\nDesign by Horg Neurocam" + "\n"; if(gState == S_OFF) { s += "\nStatus: Off"; s += "\n\n" + gOffCaution; } else { s += "\nHigh Jump: " + bs(gHighJump); s += "\nFlotation Glide: " + bs(gGlide); s += "\nLanding: " + bs(gLanding); } list options; if(!gControlPermission) options = ["Help...","On"]; else { string s = "Float"; if(gState != S_GROUND) s = "Fall"; options = [ "Help...","Off",s,"Jump " + bs(!gHighJump),"Glide " + bs(!gGlide),"Landing " + bs(!gLanding)]; } llDialog(av,s,options,COMMAND_CHANNEL); } link(string linkMessage) { if(linkMessage != "") { d(["link message sending: ",linkMessage]); llMessageLinked(LINK_SET,COMMAND_CHANNEL,linkMessage,""); } } brake(integer parachute) { d(["brake ",parachute]); // use llGetVel, not gVel, so we can brake several times iteratively llApplyImpulse(-1 * llGetVel() * llGetMass(),0); // brake! // and always with braking brakeFX(parachute); } // this is the Safety Ether Anchor visual effect brakeFX(integer parachute) { float velM = llVecMag(gVel); if(velM < .5) return; // no expensive particles on such a mild brake. // if not moving fast, but braking, do a safety circle if(parachute) { llParticleSystem( [ PSYS_SRC_TEXTURE,"radio_circle", PSYS_SRC_BURST_RADIUS,0.0, PSYS_PART_MAX_AGE,.4, // fast blink! PSYS_SRC_BURST_RATE,5.0, PSYS_SRC_BURST_PART_COUNT,2, PSYS_PART_START_SCALE,<3,3,3>, PSYS_PART_END_SCALE,<3.8,3.8,3.8>, PSYS_PART_FLAGS, PSYS_PART_INTERP_COLOR_MASK | PSYS_PART_EMISSIVE_MASK, PSYS_SRC_PATTERN, PSYS_SRC_PATTERN_EXPLODE, PSYS_PART_START_COLOR, <.6,1,.6>, PSYS_PART_START_ALPHA,0.9, PSYS_PART_END_ALPHA,0.0, PSYS_SRC_PATTERN,PSYS_SRC_PATTERN_ANGLE_CONE, PSYS_SRC_ANGLE_BEGIN,0.0, PSYS_SRC_ANGLE_END,PI, PSYS_SRC_BURST_SPEED_MIN,0., PSYS_SRC_BURST_SPEED_MAX,0., PSYS_SRC_MAX_AGE,1.0 // 1-second total burst ]); llSleep(.4); return; } float startAlpha = velM / 50.0; // top speed 50-ish makes dim alpha llParticleSystem( [ PSYS_SRC_BURST_RADIUS,2.0, PSYS_PART_MAX_AGE,.6, PSYS_SRC_BURST_RATE,.1, PSYS_SRC_BURST_PART_COUNT,400, PSYS_PART_FLAGS, PSYS_PART_FOLLOW_VELOCITY_MASK | PSYS_PART_INTERP_COLOR_MASK | PSYS_PART_EMISSIVE_MASK, PSYS_SRC_PATTERN, PSYS_SRC_PATTERN_EXPLODE, PSYS_PART_START_SCALE, <1,.1,0>, PSYS_PART_START_COLOR, <0.4,0.9,0.4>, PSYS_PART_END_COLOR, <0.0,1.0,0.0>, PSYS_PART_START_ALPHA,startAlpha, PSYS_PART_END_ALPHA,0.0, PSYS_SRC_PATTERN,PSYS_SRC_PATTERN_ANGLE_CONE, PSYS_SRC_ANGLE_BEGIN,0.0, PSYS_SRC_ANGLE_END,PI, PSYS_SRC_BURST_SPEED_MIN,0.01, PSYS_SRC_BURST_SPEED_MAX,0.01, PSYS_SRC_MAX_AGE,1.4 // 1-second total burst ]); llSleep(.1); } // +------------------- // | Misc stuff to start flying // | call setState(S_FLOAT), not this // | doFloatPrep() { setGOA("hover"); setGravity(0); // hover // if falling, do a courtesy brake if(gVel.z < -1) brake(1); } // ground mode -- you can walk around and stuff! // differences (really) are: buoyancy, and pass-through of fwd-bwd arrows. // +------------- // | Misc stuff to return to walking // | call setState(S_GROUND), not this doGroundPrep() { setGOA(""); setGravity(gUWalkingGravity); // fall! } integer gGravity = 100; // 0 to 100 // +--------- // | set the objects actual gravity (buoyancy) // | usually call setState(whatever), not this // | range: 100=normal, 0=float, -100=fall up // | setGravity(integer gravity) { if(gravity != gGravity) { gGravity = gravity; float buoyancy = 1.0 - ((float)gGravity) / 100.0; if(buoyancy < 0.0) buoyancy = 0.0; else if(buoyancy > 2.0) buoyancy = 2.0; llSetBuoyancy(buoyancy); } } // set the user-setting for gravity in Ground mode setWalkingGravity(integer g) { gUWalkingGravity = g; if(gState & (S_GROUND)) setGravity(g); } // set the user-setting for gravity in Ground mode setJumpingGravity(integer g) { gUJumpingGravity = g; if(gState & (S_JUMP)) setGravity(g); } // state variable for managing buttony transitions integer gState = 0; float gStateTime; // time (seconds) spent in current state setState(integer newState) { if(newState != gState) { gStateTime = llGetTime(); // to know how long we've been in a state d(["state from ",gState," to ",newState]); integer oldState = gState; gState = newState; vector pingColor = <0,0,0>; // leaving off? claim control if(oldState == S_OFF) { perms(1); } if(newState == S_OFF) { doGroundPrep(); perms(0); pingColor = <0,0,0>; } else if(newState == S_GROUND) { doGroundPrep(); pingColor = <0,0,.2>; } else if(newState == S_FLOAT) { doFloatPrep(); pingColor = <0,1,0>; } else if(newState == S_ANCHOR) { brake(0); pingColor = <0,1,.4>; } else if(newState == S_JUMP) { setGravity(gUJumpingGravity); pingColor = <1,.5,0>; gJumpTime = 15; } link("pingSteady " + (string)pingColor.x + " " + (string)pingColor.y + " " + (string)pingColor.z); link("state " + (string)gState); } } float gBasicMovingTime = 0; // +------------------------ // | handle currently held control buttons // | held = buttons now // | doControls(integer held) { integer cu = held & CONTROL_UP; integer cd = held & CONTROL_DOWN; integer cf = held & CONTROL_FWD; integer cb = held & CONTROL_BACK; float sameStateTime = llGetTime() - gStateTime; if(gState == S_GROUND) { // minimum time in Ground mode so you can "drop" // without accidentally lifting up again too easily if(cu && someStateTime > .6) { if(gHighJump && (gVel.z > -.5) && !(gOwnerInfo & AGENT_IN_AIR)) // jump if not falling... setState(S_JUMP); else setState(S_FLOAT); } } else if(gState == S_JUMP) { // while jumping, switch to float mode can happen after first second if(cu && sameStateTime > 1) setState(S_FLOAT); else if(cu && cd) setState(S_GROUND); else if(cf && cb) setState(S_ANCHOR); } else { integer cl = held & CONTROL_LEFT; integer cr = held & CONTROL_RIGHT; if(gState == S_ANCHOR) { brake(0); if((cu + cd + cf + cb + cr + cl) && ((!gGlide) || (sameStateTime > 1))) setState(S_FLOAT); } if(gState == S_FLOAT) { if (!gGlide && !(held & MOTION_KEYS)) setState(S_ANCHOR); else if(cu && cd) setState(S_GROUND); else if(gGlide && (cf && cb)) setState(S_ANCHOR); else if(cd && gLanding && !(gOwnerInfo & AGENT_IN_AIR)) setState(S_GROUND); else { vector impulse = <0,0,0>; if(cu) impulse += <0,0,1>; if(cd) impulse += <0,0,-1>; if(cf) impulse += <1,0,0>; if(cb) impulse += <-1,0,0>; if(cl) impulse += <0,1,0>; if(cr) impulse += <0,-1,0>; // scale accelerating impulse float t = llGetTime() - gBasicMovingTime; if(t > 1) t = 1; impulse *= t; gRot = llGetRot(); impulse *= gRot; impulse *= llGetMass(); d(["basic impulse: ",impulse]); llApplyImpulse(impulse,0); } } } } // poll for typing, and activate antenna if so typingStuff() { string message = ""; integer info = llGetAgentInfo(gOwner); integer isTyping = info & AGENT_TYPING; if(isTyping) message = "pingShort 1 0 0 " + (string)(TIMER_RATE - 0.2)+ " 0.2"; link(message); } // poll for traditional-flying mode integer gAgentFlying = 0; integer gAgentSitting = 0; avFlyStuff() { integer isFlying = gOwnerInfo & AGENT_FLYING; integer isSitting = gOwnerInfo & AGENT_SITTING; // release controls automatically if you sit. take em when done. if(isSitting && !gAgentSitting) { setState(S_OFF); } else if(gAgentSitting && !isSitting) { setState(S_GROUND); } gAgentSitting = isSitting; if(isFlying && !gAgentFlying) { setState(S_FLOAT); doControls(0); } else if(gAgentFlying && !isFlying) { setState(S_GROUND); doControls(0); } gAgentFlying = isFlying; // adjust gravity based on flying -- if(gState == S_FLOAT) { // if you are buoyant and AGENT_FLYING you tend to drift to 150, gr if(gAgentFlying && gHere.z < 150) setGravity(100); else setGravity(0); } } vector gRecentPosition = <0,0,0>; // set a certain animation, but only if we have perms, and its not the current string gOA = ""; setGOA(string oa) { if(gAnimationPermission)// && gOA != oa) { if(gOA != oa) { if(gOA != "") llStopAnimation(gOA); } gOA = oa; if(gOA != "") llStartAnimation(gOA); } } // Do some misc logic and maybe override animation. animationStuff() { if(!gAnimationPermission) return; // dont try if not allowed. if(gState & (S_FLOAT | S_JUMP | S_ANCHOR)) { string oa = "hover"; if(gVel.z > 2) oa = "hover_up"; else if(gVel.z < -2) oa = "hover_down"; llStopAnimation("falldown"); setGOA(oa); } } // falling stuff -- if not in control mode, quick! stop the fall, and // take over. applySafetyAnchor() { if(gState == S_GROUND && !gAgentSitting) // we can fall if we want to { if(gVel.z < -gFallThreshold && gVel.z > -gTeleThreshold) // tune me { d(["safety anchor activated, z = ",gVel.z]); brake(1); setState(S_FLOAT); doControls(0); llSleep(.3); brake(1); } } } sayReady() { llOwnerSay((string)["Ready: Listening on ",COMMAND_CHANNEL,". Click for controls."]); } default { on_rez(integer n) { sayReady(); } state_entry() { init(); } touch_start(integer total_number) { key avKey = llDetectedKey(0); // touch to redo perms if(avKey == gOwner) doDialog(); else doHelp(avKey); } listen(integer channel,string name,key id,string message) { d(["Heard \"",message,"\" on channel ",channel," from ",name]); doCommand(id,message); } attach(key avKey) { if(avKey) { init(); perms(1); sayReady(); } else setState(0); // silent } // just got control! run_time_permissions(integer perm) { if(perm & PERMISSION_TAKE_CONTROLS) { d(["run_time_permissions: ",perm]); gControlPermission = 1; // take control exactly once, for all occasions. llTakeControls(MOTION_KEYS, TRUE,TRUE // if we pass through, then Flying still works... ); setState(S_GROUND); } if(perm & PERMISSION_TRIGGER_ANIMATION) gAnimationPermission = 1; } control(key id, integer held, integer change) { gOwnerInfo = llGetAgentInfo(gOwner); doControls(held); } timer() { // rock-steady in basic mode if(gState == S_ANCHOR) brake(0); if(gJumpTime > 0 && gState == S_JUMP) { gJumpTime -= TIMER_RATE; if(gJumpTime <= 0) { setState(S_GROUND); } } gVel = llGetVel(); gHere = llGetPos(); gOwnerInfo = llGetAgentInfo(gOwner); gTicks++; // d(["gTicks: ",gTicks]); typingStuff(); avFlyStuff(); animationStuff(); if(gUSafetyAnchor && gControlPermission) // dont anchor when "disabled" applySafetyAnchor(); } } // end of file