394 lines
11 KiB
Plaintext
394 lines
11 KiB
Plaintext
/*
|
|
HS_DollyCam - Tour command helper
|
|
- Handles secondary chat/menu one-shot tour builders
|
|
- Keeps HS_CamPlaylist.lsl focused on memory-sensitive notecard playback
|
|
*/
|
|
|
|
// Engine protocol
|
|
integer CE_CMD_TOUR = 1011;
|
|
integer CE_CMD_CFG_DUMP = 1051;
|
|
integer CE_EVT_CFG_DUMP = 2051;
|
|
|
|
// Controller -> helper protocol
|
|
integer PH_CMD_CHAT_TOUR = 6102; // payload: full chat line starting with "tour ..."
|
|
integer PH_CMD_TOURRUN = 6103; // payload: raw menu string "TOURRUN|..."
|
|
integer PH_CMD_CHAT_DZ = 6104; // payload: full chat line starting with "dollyzoom ..."
|
|
|
|
// Presets
|
|
string PRE_KEY(integer idx) { return "P" + (string)idx; }
|
|
|
|
key gOwner;
|
|
integer gMoveId = 8000;
|
|
integer gDefaultMoveMs = 2200;
|
|
integer gTourMaxPoints = 20;
|
|
|
|
vector gTmpPos;
|
|
vector gTmpFoc;
|
|
integer gTmpHasFov = FALSE;
|
|
float gTmpFovRad = 0.0;
|
|
|
|
integer nextMoveId()
|
|
{
|
|
++gMoveId;
|
|
return gMoveId;
|
|
}
|
|
|
|
integer isValidIdx(integer idx) { return (idx > 0); }
|
|
say(string s) { llOwnerSay(s); }
|
|
hudHide() { llSetAlpha(0.0, ALL_SIDES); }
|
|
|
|
float deg2rad(float deg) { return deg * PI / 180.0; }
|
|
|
|
float clampf(float v, float lo, float hi)
|
|
{
|
|
if (v < lo) return lo;
|
|
if (v > hi) return hi;
|
|
return v;
|
|
}
|
|
|
|
float clampFovRad(float rad)
|
|
{
|
|
float deg = rad * 180.0 / PI;
|
|
deg = clampf(deg, 10.0, 179.0);
|
|
return deg2rad(deg);
|
|
}
|
|
|
|
string modeSanitize(string raw)
|
|
{
|
|
raw = llToLower(llStringTrim(raw, STRING_TRIM));
|
|
if (raw == "") return "linear";
|
|
return raw;
|
|
}
|
|
|
|
integer findCharFrom(string s, string ch, integer start)
|
|
{
|
|
integer L = llStringLength(s);
|
|
integer i;
|
|
for (i = start; i < L; ++i) {
|
|
if (llGetSubString(s, i, i) == ch) return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
integer cfgGetInt(string dump, string keyName, integer def)
|
|
{
|
|
integer at = llSubStringIndex(dump, keyName);
|
|
if (at < 0) return def;
|
|
|
|
at += llStringLength(keyName);
|
|
|
|
integer end = findCharFrom(dump, "|", at);
|
|
string v = "";
|
|
if (end < 0) v = llGetSubString(dump, at, -1);
|
|
else v = llGetSubString(dump, at, end - 1);
|
|
|
|
v = llStringTrim(v, STRING_TRIM);
|
|
if (v == "") return def;
|
|
return (integer)v;
|
|
}
|
|
|
|
integer loadPreset(integer idx)
|
|
{
|
|
string data = llLinksetDataRead(PRE_KEY(idx));
|
|
if (data == "") return FALSE;
|
|
|
|
// packed: px|py|pz|fx|fy|fz|rx|ry|rz|rs|fovRad
|
|
integer start = 0;
|
|
integer end = 0;
|
|
integer field = 0;
|
|
float px = 0.0; float py = 0.0; float pz = 0.0;
|
|
float fx = 0.0; float fy = 0.0; float fz = 0.0;
|
|
float fr = 0.0;
|
|
|
|
while (field <= 10) {
|
|
end = findCharFrom(data, "|", start);
|
|
|
|
string v = "";
|
|
if (end < 0) v = llGetSubString(data, start, -1);
|
|
else v = llGetSubString(data, start, end - 1);
|
|
|
|
if (field <= 5 && v == "") return FALSE;
|
|
|
|
if (field == 0) px = (float)v;
|
|
else if (field == 1) py = (float)v;
|
|
else if (field == 2) pz = (float)v;
|
|
else if (field == 3) fx = (float)v;
|
|
else if (field == 4) fy = (float)v;
|
|
else if (field == 5) fz = (float)v;
|
|
else if (field == 10) fr = (float)v;
|
|
|
|
++field;
|
|
if (end < 0) jump preset_done;
|
|
start = end + 1;
|
|
}
|
|
@preset_done;
|
|
|
|
if (field < 6) return FALSE;
|
|
|
|
gTmpPos = <px, py, pz>;
|
|
gTmpFoc = <fx, fy, fz>;
|
|
|
|
gTmpHasFov = FALSE;
|
|
gTmpFovRad = 0.0;
|
|
if (fr > 0.0001) {
|
|
gTmpHasFov = TRUE;
|
|
gTmpFovRad = clampFovRad(fr);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
list tourParseFov(list t, integer startIdx)
|
|
{
|
|
integer n = llGetListLength(t);
|
|
integer i;
|
|
for (i = startIdx; i < n; ++i) {
|
|
string tok = llToLower(llList2String(t, i));
|
|
|
|
if ((tok == "fovdeg" || tok == "fov") && (i + 2 < n)) {
|
|
float a = (float)llList2String(t, i + 1);
|
|
float b = (float)llList2String(t, i + 2);
|
|
|
|
if (tok == "fovdeg") {
|
|
a = clampf(a, 10.0, 179.0);
|
|
b = clampf(b, 10.0, 179.0);
|
|
return [1, deg2rad(a), deg2rad(b)];
|
|
}
|
|
|
|
float adeg = clampf(a * 180.0 / PI, 10.0, 179.0);
|
|
float bdeg = clampf(b * 180.0 / PI, 10.0, 179.0);
|
|
return [1, deg2rad(adeg), deg2rad(bdeg)];
|
|
}
|
|
}
|
|
return [0, 0.0, 0.0];
|
|
}
|
|
|
|
runChatTour(string msg)
|
|
{
|
|
msg = llStringTrim(msg, STRING_TRIM);
|
|
if (llToLower(llGetSubString(msg, 0, 3)) != "tour") return;
|
|
|
|
if (llStringLength(msg) > 600) {
|
|
say("Tour one-liner too long. Use notecard tour blocks.");
|
|
return;
|
|
}
|
|
|
|
list t = llParseString2List(msg, [" "], []);
|
|
integer n = llGetListLength(t);
|
|
if (n < 4) { say("Tour: usage: tour <ms> [mode] <idx1> <idx2> ..."); return; }
|
|
|
|
integer totalMs = (integer)llList2String(t, 1);
|
|
if (totalMs < 0) totalMs = 0;
|
|
|
|
integer i = 2;
|
|
string mode = "linear";
|
|
|
|
string tok2 = llList2String(t, 2);
|
|
integer maybeIdx = (integer)tok2;
|
|
if (maybeIdx <= 0 || (string)maybeIdx != tok2) {
|
|
mode = tok2;
|
|
i = 3;
|
|
}
|
|
mode = modeSanitize(mode);
|
|
|
|
list posList = [];
|
|
list focList = [];
|
|
integer k = i;
|
|
integer fovAt = -1;
|
|
|
|
for (; k < n; ++k) {
|
|
string tok = llToLower(llList2String(t, k));
|
|
if (tok == "fovdeg" || tok == "fov") { fovAt = k; jump chat_idx_done; }
|
|
|
|
integer idx = (integer)llList2String(t, k);
|
|
if (!isValidIdx(idx) || !loadPreset(idx)) { say("Tour: bad/missing preset: " + llList2String(t, k)); return; }
|
|
posList += [gTmpPos];
|
|
focList += [gTmpFoc];
|
|
}
|
|
@chat_idx_done;
|
|
|
|
integer want = llGetListLength(posList);
|
|
if (want < 2) { say("Tour: needs at least 2 presets."); return; }
|
|
if (want > gTourMaxPoints) {
|
|
say("Tour: too many points, capping to " + (string)gTourMaxPoints);
|
|
want = gTourMaxPoints;
|
|
posList = llList2List(posList, 0, want - 1);
|
|
focList = llList2List(focList, 0, want - 1);
|
|
}
|
|
|
|
integer fovOn = FALSE;
|
|
float fovA = 0.0;
|
|
float fovB = 0.0;
|
|
if (fovAt != -1) {
|
|
list ff2 = tourParseFov(t, fovAt);
|
|
fovOn = (integer)llList2String(ff2, 0);
|
|
fovA = llList2Float(ff2, 1);
|
|
fovB = llList2Float(ff2, 2);
|
|
}
|
|
|
|
integer mid = nextMoveId();
|
|
string payload = (string)mid + "|" + (string)totalMs + "|" + mode + "|" + (string)want;
|
|
|
|
if (fovOn) payload += "|FOV|" + (string)fovA + "|" + (string)fovB;
|
|
|
|
integer j;
|
|
for (j = 0; j < want; ++j) {
|
|
payload += "|" + (string)llList2Vector(posList, j) + "|" + (string)llList2Vector(focList, j) + "|0";
|
|
}
|
|
|
|
hudHide();
|
|
llMessageLinked(LINK_SET, CE_CMD_TOUR, payload, gOwner);
|
|
}
|
|
|
|
runChatDollyZoom(string msg)
|
|
{
|
|
msg = llStringTrim(msg, STRING_TRIM);
|
|
if (llToLower(llGetSubString(msg, 0, 8)) != "dollyzoom") return;
|
|
|
|
if (llStringLength(msg) > 600) {
|
|
say("DollyZoom one-liner too long. Use notecard/playlist.");
|
|
return;
|
|
}
|
|
|
|
list t = llParseString2List(msg, [" "], []);
|
|
integer n = llGetListLength(t);
|
|
if (n < 4) { say("DollyZoom: usage: dollyzoom <ms> [mode] <idxA> <idxB>"); return; }
|
|
|
|
integer totalMs = (integer)llList2String(t, 1);
|
|
if (totalMs < 0) totalMs = 0;
|
|
|
|
integer i = 2;
|
|
string mode = "linear";
|
|
|
|
string tok2 = llList2String(t, 2);
|
|
integer maybeIdx = (integer)tok2;
|
|
if (maybeIdx <= 0 || (string)maybeIdx != tok2) {
|
|
mode = tok2;
|
|
i = 3;
|
|
}
|
|
mode = modeSanitize(mode);
|
|
|
|
if (i + 1 >= n) { say("DollyZoom: usage: dollyzoom <ms> [mode] <idxA> <idxB>"); return; }
|
|
|
|
integer idxA = (integer)llList2String(t, i);
|
|
integer idxB = (integer)llList2String(t, i + 1);
|
|
|
|
integer keepOn = FALSE;
|
|
integer k;
|
|
for (k = i + 2; k < n; ++k) {
|
|
string tokK = llToLower(llList2String(t, k));
|
|
if (tokK == "keep" || tokK == "keepframe" || tokK == "kf") { keepOn = TRUE; }
|
|
}
|
|
if (keepOn) mode = mode + "+keep";
|
|
|
|
if (!isValidIdx(idxA) || !isValidIdx(idxB)) { say("DollyZoom: idx must be > 0"); return; }
|
|
|
|
if (!loadPreset(idxA)) { say("DollyZoom: missing preset " + (string)idxA); return; }
|
|
vector posA = gTmpPos; vector focA = gTmpFoc;
|
|
integer hasA = gTmpHasFov; float fovA = gTmpFovRad;
|
|
|
|
if (!loadPreset(idxB)) { say("DollyZoom: missing preset " + (string)idxB); return; }
|
|
vector posB = gTmpPos; vector focB = gTmpFoc;
|
|
integer hasB = gTmpHasFov; float fovB = gTmpFovRad;
|
|
|
|
if (keepOn) focB = focA;
|
|
|
|
if (!hasA || (!keepOn && !hasB)) {
|
|
say("DollyZoom needs FOV stored in BOTH presets (unless using 'keep'). Set FOV (fovdeg) and save presets again.");
|
|
return;
|
|
}
|
|
if (keepOn && !hasB) fovB = fovA;
|
|
|
|
integer mid = nextMoveId();
|
|
string payload = (string)mid + "|" + (string)totalMs + "|" + mode + "|2"
|
|
+ "|FOV|" + (string)fovA + "|" + (string)fovB
|
|
+ "|" + (string)posA + "|" + (string)focA + "|0"
|
|
+ "|" + (string)posB + "|" + (string)focB + "|0";
|
|
|
|
hudHide();
|
|
llMessageLinked(LINK_SET, CE_CMD_TOUR, payload, gOwner);
|
|
}
|
|
|
|
runMenuTourRun(string raw)
|
|
{
|
|
// raw: "TOURRUN|totalMs|mode|count|idx1|idx2|..."
|
|
if (llStringLength(raw) > 1800) { say("Tour: menu payload too large."); return; }
|
|
|
|
list p = llParseString2List(raw, ["|"], []);
|
|
integer len = llGetListLength(p);
|
|
if (len < 5) return;
|
|
|
|
if (llToUpper(llList2String(p, 0)) != "TOURRUN") return;
|
|
|
|
integer totalMs = (integer)llList2String(p, 1);
|
|
string mode = modeSanitize(llList2String(p, 2));
|
|
integer count = (integer)llList2String(p, 3);
|
|
|
|
if (totalMs < 1) totalMs = gDefaultMoveMs;
|
|
if (count < 2) { say("Tour: needs at least 2 points."); return; }
|
|
if (count > gTourMaxPoints) { say("Tour: too many points, capping to " + (string)gTourMaxPoints); count = gTourMaxPoints; }
|
|
if (len < (4 + count)) { say("Tour: bad menu payload."); return; }
|
|
|
|
integer mid = nextMoveId();
|
|
string payload = (string)mid + "|" + (string)totalMs + "|" + mode + "|" + (string)count;
|
|
|
|
integer i;
|
|
for (i = 0; i < count; ++i) {
|
|
integer idx = (integer)llList2String(p, 4 + i);
|
|
if (!isValidIdx(idx) || !loadPreset(idx)) { say("Tour: bad/missing preset: " + (string)idx); return; }
|
|
payload += "|" + (string)gTmpPos + "|" + (string)gTmpFoc + "|0";
|
|
}
|
|
|
|
hudHide();
|
|
llMessageLinked(LINK_SET, CE_CMD_TOUR, payload, gOwner);
|
|
}
|
|
|
|
default
|
|
{
|
|
state_entry()
|
|
{
|
|
gOwner = llGetOwner();
|
|
llMessageLinked(LINK_SET, CE_CMD_CFG_DUMP, "", gOwner);
|
|
}
|
|
|
|
on_rez(integer sp)
|
|
{
|
|
gOwner = llGetOwner();
|
|
}
|
|
|
|
attach(key id)
|
|
{
|
|
gOwner = llGetOwner();
|
|
}
|
|
|
|
link_message(integer sender, integer num, string str, key id)
|
|
{
|
|
if (num == CE_EVT_CFG_DUMP) {
|
|
gDefaultMoveMs = cfgGetInt(str, "default_move_ms=", gDefaultMoveMs);
|
|
|
|
integer n = cfgGetInt(str, "tour_max_points=", gTourMaxPoints);
|
|
if (n < 2) n = 2;
|
|
if (n > 40) n = 40;
|
|
gTourMaxPoints = n;
|
|
return;
|
|
}
|
|
|
|
if (id != gOwner) return;
|
|
|
|
if (num == PH_CMD_CHAT_TOUR) {
|
|
runChatTour(str);
|
|
return;
|
|
}
|
|
|
|
if (num == PH_CMD_CHAT_DZ) {
|
|
runChatDollyZoom(str);
|
|
return;
|
|
}
|
|
|
|
if (num == PH_CMD_TOURRUN) {
|
|
runMenuTourRun(str);
|
|
return;
|
|
}
|
|
}
|
|
}
|