/* HS_DollyCam - Preset Transfer Utility Drop this script into a camera object only when importing/exporting presets. It reads and writes the existing Linkset Data preset keys: P1, P2, ... Commands on channel 88: /88 xfer help /88 xfer status /88 xfer export [from] [count] /88 xfer import /88 xfer importreplace /88 xfer poof Export format: HS_DOLLYCAM_PRESETS_V1 P1=px|py|pz|fx|fy|fz|rx|ry|rz|rs|fovRad HS_DOLLYCAM_PRESETS_END */ integer CH = 88; integer MAX_PRESET_SCAN = 999; string HEADER = "HS_DOLLYCAM_PRESETS_V1"; string FOOTER = "HS_DOLLYCAM_PRESETS_END"; key gOwner; integer gListen; string gImportCard; key gImportQuery; integer gImportLine; integer gImporting; integer gReplaceBeforeImport; integer gImported; integer gSkipped; string preKey(integer idx) { return "P" + (string)idx; } say(string msg) { llOwnerSay(msg); } string firstToken(string msg) { msg = llStringTrim(msg, STRING_TRIM); integer sp = llSubStringIndex(msg, " "); if (sp < 0) return msg; return llGetSubString(msg, 0, sp - 1); } string restAfterFirstToken(string msg) { msg = llStringTrim(msg, STRING_TRIM); integer sp = llSubStringIndex(msg, " "); if (sp < 0) return ""; return llStringTrim(llGetSubString(msg, sp + 1, -1), STRING_TRIM); } integer isCommentOrMarker(string line) { if (line == "") return TRUE; if (line == HEADER) return TRUE; if (line == FOOTER) return TRUE; if (llGetSubString(line, 0, 0) == "#") return TRUE; if (llGetSubString(line, 0, 1) == "//") return TRUE; return FALSE; } integer hasPresetMinimumFields(string data) { integer pipes = 0; integer pos = -1; integer len = llStringLength(data); while (pipes < 5) { integer next = llSubStringIndex(llGetSubString(data, pos + 1, -1), "|"); if (next < 0) return FALSE; pos += next + 1; ++pipes; if (pos >= len) return FALSE; } return TRUE; } integer validPresetIndexString(string s) { integer len = llStringLength(s); if (len < 2) return FALSE; if (llToUpper(llGetSubString(s, 0, 0)) != "P") return FALSE; integer idx = (integer)llGetSubString(s, 1, -1); if (idx < 1 || idx > MAX_PRESET_SCAN) return FALSE; if ("P" + (string)idx != llToUpper(s)) return FALSE; return TRUE; } integer importLine(string line) { line = llStringTrim(line, STRING_TRIM); if (isCommentOrMarker(line)) return FALSE; integer eq = llSubStringIndex(line, "="); if (eq < 2) return FALSE; string keyName = llStringTrim(llGetSubString(line, 0, eq - 1), STRING_TRIM); string data = llStringTrim(llGetSubString(line, eq + 1, -1), STRING_TRIM); if (!validPresetIndexString(keyName)) return FALSE; if (!hasPresetMinimumFields(data)) return FALSE; integer idx = (integer)llGetSubString(keyName, 1, -1); llLinksetDataWrite(preKey(idx), data); return TRUE; } clearPresets() { integer i; for (i = 1; i <= MAX_PRESET_SCAN; ++i) { llLinksetDataDelete(preKey(i)); } } printHelp() { say( "HS DollyCam Preset Transfer\n" + "/88 xfer status\n" + "/88 xfer export [from] [count]\n" + "/88 xfer import \n" + "/88 xfer importreplace \n" + "/88 xfer poof\n" + "Import notecard lines use: P1=px|py|pz|fx|fy|fz|rx|ry|rz|rs|fovRad" ); } printStatus() { integer count = 0; integer first = 0; integer last = 0; integer i; for (i = 1; i <= MAX_PRESET_SCAN; ++i) { if (llLinksetDataRead(preKey(i)) != "") { ++count; if (first == 0) first = i; last = i; } } if (count == 0) { say("Preset Transfer: no saved presets found."); return; } say("Preset Transfer: " + (string)count + " presets found, first P" + (string)first + ", last P" + (string)last + "."); } exportPresets(string args) { integer from = 1; integer count = 50; string tok = firstToken(args); if (tok != "") { from = (integer)tok; string rest = restAfterFirstToken(args); tok = firstToken(rest); if (tok != "") count = (integer)tok; } if (from < 1) from = 1; if (from > MAX_PRESET_SCAN) from = MAX_PRESET_SCAN; if (count < 1) count = 50; if (count > 100) count = 100; integer stop = from + count - 1; if (stop > MAX_PRESET_SCAN) stop = MAX_PRESET_SCAN; integer shown = 0; integer i; say(HEADER); for (i = from; i <= stop; ++i) { string data = llLinksetDataRead(preKey(i)); if (data != "") { say(preKey(i) + "=" + data); ++shown; } } say(FOOTER); if (shown == 0) { say("Preset Transfer: no presets found in requested range."); return; } say("Preset Transfer: exported " + (string)shown + " presets from P" + (string)from + " to P" + (string)stop + "."); } startImport(string card, integer replace) { card = llStringTrim(card, STRING_TRIM); if (card == "") { say("Preset Transfer: missing notecard name."); return; } if (llGetInventoryType(card) != INVENTORY_NOTECARD) { say("Preset Transfer: notecard not found: " + card); return; } if (gImporting) { say("Preset Transfer: import already running."); return; } gImportCard = card; gImportLine = 0; gImported = 0; gSkipped = 0; gReplaceBeforeImport = replace; gImporting = TRUE; if (gReplaceBeforeImport) { clearPresets(); say("Preset Transfer: cleared existing P1-P" + (string)MAX_PRESET_SCAN + " presets."); } say("Preset Transfer: importing from notecard '" + gImportCard + "' ..."); gImportQuery = llGetNotecardLine(gImportCard, gImportLine); } handleCommand(string msg) { string cmd = llToLower(firstToken(msg)); if (cmd != "xfer" && cmd != "transfer") return; string rest = restAfterFirstToken(msg); string sub = llToLower(firstToken(rest)); string args = restAfterFirstToken(rest); if (sub == "" || sub == "help") { printHelp(); return; } if (sub == "status") { printStatus(); return; } if (sub == "export") { exportPresets(args); return; } if (sub == "import") { startImport(args, FALSE); return; } if (sub == "importreplace") { startImport(args, TRUE); return; } if (sub == "poof" || sub == "done" || sub == "remove") { say("Preset Transfer: removing script."); llRemoveInventory(llGetScriptName()); return; } say("Preset Transfer: unknown command. Use /88 xfer help"); } default { state_entry() { gOwner = llGetOwner(); gListen = llListen(CH, "", "", ""); say("HS DollyCam Preset Transfer ready. Use /88 xfer help"); } on_rez(integer start_param) { gOwner = llGetOwner(); } changed(integer change) { if (change & CHANGED_OWNER) { llResetScript(); } } listen(integer channel, string name, key id, string msg) { if (llGetOwnerKey(id) != gOwner) return; handleCommand(llStringTrim(msg, STRING_TRIM)); } dataserver(key query_id, string data) { if (!gImporting || query_id != gImportQuery) return; if (data == EOF) { say("Preset Transfer: import complete. imported=" + (string)gImported + " skipped=" + (string)gSkipped + "."); gImporting = FALSE; return; } if (importLine(data)) ++gImported; else { string trimmed = llStringTrim(data, STRING_TRIM); if (!isCommentOrMarker(trimmed)) ++gSkipped; } ++gImportLine; gImportQuery = llGetNotecardLine(gImportCard, gImportLine); } }