/* HS_DollyCam - CamCore (tour split, memory-safe) - Permissions, base camera params, MoveTo, Follow/Lock, Config, GetState - Tour playback is offloaded to HS_CamTourEngine.lsl via internal link messages: CE_INT_TOUR_BEGIN / CE_INT_SET_CAM / CE_INT_TOUR_END / CE_INT_TOUR_STOP */ string CFG_CARD = "HS_CamEngine.properties"; // Link message protocol (Controller -> Core) integer CE_CMD_INIT = 1000; integer CE_CMD_RELEASE = 1001; integer CE_CMD_MOVE = 1010; // payload: moveId|durMs|| integer CE_CMD_TOUR = 1011; // handled by HS_CamTourEngine (Core ignores) integer CE_CMD_STOP = 1012; integer CE_CMD_LOCK = 1020; // payload: 0| or 1| or 1| integer CE_CMD_FOLLOW = 1030; // payload: 0||| or 1|... integer CE_CMD_CFG_RELOAD = 1050; integer CE_CMD_CFG_DUMP = 1051; integer CE_CMD_GET_STATE = 1060; // payload: reqId // Link message protocol (Core -> Controller) integer CE_EVT_READY = 2000; // payload: CAM_OK integer CE_EVT_DENIED = 2001; // payload: CAM_DENIED integer CE_EVT_MOVE_DONE = 2010; // payload: moveId integer CE_EVT_CFG_DUMP = 2051; // payload: key=val|... integer CE_EVT_STATE = 2060; // payload: reqId||||fovRad // Internal protocol (TourEngine -> Core) integer CE_INT_SET_CAM = 3000; // payload: | integer CE_INT_TOUR_BEGIN = 3001; // payload: moveId integer CE_INT_TOUR_END = 3002; // payload: moveId integer CE_INT_TOUR_STOP = 3003; // payload: (unused) integer FOLLOW_YAW = 0; integer FOLLOW_LOCAL = 1; integer FOLLOW_WORLD = 2; // --- Defaults (Notecard can override) --- float gMOVE_STEP = 0.025; float gFOLLOW_STEP = 0.05; integer gDEFAULT_MOVE_MS = 3000; integer gTOUR_MAX_POINTS = 20; float gDEFAULT_FOCUS_DIST = 10.0; float gTOUR_CAM_MIN_INTERVAL = 0.033; float gTOUR_POS_EPS = 0.002; float gTOUR_FOCUS_EPS = 0.002; float gMOVE_POS_LAG = 2.0; float gMOVE_FOCUS_LAG = 2.0; float gFOLLOW_POS_LAG = 1.6; float gFOLLOW_FOCUS_LAG= 1.6; float gPOS_THRESHOLD = 0.02; float gFOCUS_THRESHOLD = 0.02; float gFOLLOW_PREDICT = 0.10; // Runtime state key gOwner; integer gHasPerm = FALSE; integer gCamActive = FALSE; integer gMoving = FALSE; integer gMoveId = 0; float gMoveStart = 0.0; float gMoveDur = 1.0; integer gMovePending = FALSE; integer gMovePendId; integer gMovePendDurMs; vector gMovePendPos; vector gMovePendFoc; vector gStartPos; vector gStartFocus; vector gTargetPos; vector gTargetFocus; vector gCurPos; vector gCurFocus; rotation gCurRot; // Follow/Lock integer gFollowMode = FOLLOW_WORLD; vector gCamOffW; vector gFocusOffW; vector gCamOffL; vector gFocusOffL; vector gCamOffYaw; vector gFocusOffYaw; integer gFollowBlendOn = FALSE; float gFollowBlendDur = 0.0; float gFollowBlendStart = 0.0; vector gFollowBlendStartCam; vector gFollowBlendStartFocus; integer gLockOn = FALSE; vector gLockFocus; key gLockTarget = NULL_KEY; vector gLockTargetOffset = <0,0,1.0>; integer gFollowOn = FALSE; key gFollowTarget; // Config key gCfgQuery; integer gCfgLine = 0; integer gPendingBaseApply = FALSE; // Tour external drive flag integer gExternDrive = FALSE; // Cached camera param lists list gBaseMoveParams; list gBaseFollowParams; integer gBaseParamsDirty = TRUE; // ---------- helpers ---------- float clampf(float v, float lo, float hi) { if (v < lo) return lo; if (v > hi) return hi; return v; } float smootherstep(float x) { if (x <= 0.0) return 0.0; if (x >= 1.0) return 1.0; return x*x*x*(x*(x*6.0 - 15.0) + 10.0); } rotation rotFromPosFocus(vector pos, vector focus) { vector fwd = llVecNorm(focus - pos); vector up = <0,0,1>; vector left = up % fwd; if (llVecMag(left) < 0.0001) left = <1,0,0>; else left = llVecNorm(left); vector up2 = llVecNorm(fwd % left); return llAxes2Rot(left, up2, fwd); } rotation yawOnlyRot(rotation r, vector vel) { vector fwd = llRot2Fwd(r); if (r == ZERO_ROTATION && vel != ZERO_VECTOR) fwd = llVecNorm(vel); fwd.z = 0.0; if (fwd == ZERO_VECTOR) return ZERO_ROTATION; fwd = llVecNorm(fwd); return llRotBetween(<1,0,0>, fwd); } integer camControlOk() { return ((llGetPermissions() & PERMISSION_CONTROL_CAMERA) && (llGetPermissionsKey() == gOwner)); } integer camTrackOk() { return ((llGetPermissions() & PERMISSION_TRACK_CAMERA) && (llGetPermissionsKey() == gOwner)); } buildBaseParams() { gBaseMoveParams = [ CAMERA_ACTIVE, TRUE, CAMERA_POSITION_LOCKED, TRUE, CAMERA_FOCUS_LOCKED, TRUE, CAMERA_POSITION_LAG, gMOVE_POS_LAG, CAMERA_FOCUS_LAG, gMOVE_FOCUS_LAG, CAMERA_POSITION_THRESHOLD, gPOS_THRESHOLD, CAMERA_FOCUS_THRESHOLD, gFOCUS_THRESHOLD ]; gBaseFollowParams = [ CAMERA_ACTIVE, TRUE, CAMERA_POSITION_LOCKED, TRUE, CAMERA_FOCUS_LOCKED, TRUE, CAMERA_POSITION_LAG, gFOLLOW_POS_LAG, CAMERA_FOCUS_LAG, gFOLLOW_FOCUS_LAG, CAMERA_POSITION_THRESHOLD, gPOS_THRESHOLD, CAMERA_FOCUS_THRESHOLD, gFOCUS_THRESHOLD ]; gBaseParamsDirty = FALSE; } applyBaseMove() { if (!gHasPerm) return; if (gBaseParamsDirty || llGetListLength(gBaseMoveParams) == 0) buildBaseParams(); llSetCameraParams(gBaseMoveParams); gCamActive = TRUE; } applyBaseFollow() { if (!gHasPerm) return; if (gBaseParamsDirty || llGetListLength(gBaseFollowParams) == 0) buildBaseParams(); llSetCameraParams(gBaseFollowParams); gCamActive = TRUE; } applyBaseForMode() { if (!gHasPerm) return; if (gMoving) applyBaseMove(); else applyBaseFollow(); } setTimerForMode() { if (gExternDrive) { llSetTimerEvent(0.0); return; } if (gMoving) { llSetTimerEvent(gMOVE_STEP); return; } if (gFollowOn || gLockOn) { llSetTimerEvent(gFOLLOW_STEP); return; } llSetTimerEvent(0.0); } requestCamPerm() { if (gOwner == NULL_KEY) gOwner = llGetOwner(); if ((llGetPermissions() & PERMISSION_CONTROL_CAMERA) && (llGetPermissionsKey() == gOwner)) { gHasPerm = TRUE; applyBaseForMode(); llMessageLinked(LINK_SET, CE_EVT_READY, "CAM_OK", gOwner); return; } llRequestPermissions(gOwner, PERMISSION_CONTROL_CAMERA | PERMISSION_TRACK_CAMERA); } releaseCam() { // Idempotenz: verhindert doppelte Release-Sequenzen (attach(NULL_KEY) + CE_CMD_RELEASE) if (!gCamActive && !gHasPerm && !gExternDrive && !gMoving && !gFollowOn && !gLockOn) return; llMessageLinked(LINK_SET, CE_INT_TOUR_STOP, "", gOwner); gExternDrive = FALSE; gMoving = FALSE; gFollowOn = FALSE; gLockOn = FALSE; gCamActive = FALSE; // reset permission flags (avoid stale state) gHasPerm = FALSE; if (camControlOk()) llClearCameraParams(); llSetTimerEvent(0.0); } integer followCapture(key target, integer mode, integer transitionMs) { list d = llGetObjectDetails(target, [OBJECT_POS, OBJECT_ROT, OBJECT_VELOCITY]); if (llGetListLength(d) < 1) return FALSE; vector p = llList2Vector(d, 0); rotation r = ZERO_ROTATION; vector v = ZERO_VECTOR; if (llGetListLength(d) >= 2) r = llList2Rot(d, 1); if (llGetListLength(d) >= 3) v = llList2Vector(d, 2); p += v * gFOLLOW_PREDICT; vector camPos; rotation camRot; if (camTrackOk()) { camPos = llGetCameraPos(); camRot = llGetCameraRot(); } else { camPos = gCurPos; camRot = rotFromPosFocus(gCurPos, gCurFocus); } if (camPos == ZERO_VECTOR) { rotation rr = r; if (mode == FOLLOW_YAW) rr = yawOnlyRot(r, v); camPos = p + (<-4.0, 0.0, 2.0> * rr); camRot = rr; } vector focusNow = camPos + (llRot2Fwd(camRot) * gDEFAULT_FOCUS_DIST); gFollowTarget = target; gFollowMode = mode; if (mode == FOLLOW_WORLD) { gCamOffW = camPos - p; gFocusOffW = focusNow - p; } else if (mode == FOLLOW_LOCAL) { gCamOffL = (camPos - p) / r; gFocusOffL = (focusNow - p) / r; } else { rotation yawR = yawOnlyRot(r, v); gCamOffYaw = (camPos - p) / yawR; gFocusOffYaw = (focusNow - p) / yawR; gFollowMode = FOLLOW_YAW; } gFollowBlendOn = (transitionMs > 0); if (gFollowBlendOn) { gFollowBlendDur = (float)transitionMs / 1000.0; if (gFollowBlendDur < 0.01) gFollowBlendDur = 0.01; gFollowBlendStart = llGetTime(); gFollowBlendStartCam = camPos; gFollowBlendStartFocus = focusNow; } return TRUE; } applyFollowOnce() { if (!gHasPerm) return; if (!gFollowOn || gFollowTarget == NULL_KEY) return; list d = llGetObjectDetails(gFollowTarget, [OBJECT_POS, OBJECT_ROT, OBJECT_VELOCITY]); integer len = llGetListLength(d); if (len < 1) return; vector p = llList2Vector(d, 0); rotation r = ZERO_ROTATION; vector v = ZERO_VECTOR; if (len >= 2) r = llList2Rot(d, 1); if (len >= 3) v = llList2Vector(d, 2); p += v * gFOLLOW_PREDICT; vector pos; vector foc; if (gFollowMode == FOLLOW_WORLD) { pos = p + gCamOffW; foc = p + gFocusOffW; } else if (gFollowMode == FOLLOW_LOCAL) { pos = p + (gCamOffL * r); foc = p + (gFocusOffL * r); } else { rotation yawR = yawOnlyRot(r, v); pos = p + (gCamOffYaw * yawR); foc = p + (gFocusOffYaw * yawR); } if (gFollowBlendOn) { float b = (llGetTime() - gFollowBlendStart) / gFollowBlendDur; if (b >= 1.0) { b = 1.0; gFollowBlendOn = FALSE; } pos = gFollowBlendStartCam + (pos - gFollowBlendStartCam) * b; foc = gFollowBlendStartFocus + (foc - gFollowBlendStartFocus) * b; } if (gLockOn) { if (gLockTarget != NULL_KEY) { list det2 = llGetObjectDetails(gLockTarget, [OBJECT_POS]); if (llGetListLength(det2) >= 1) { vector tpos = llList2Vector(det2, 0); foc = tpos + gLockTargetOffset; } } else { foc = gLockFocus; } } gCurPos = pos; gCurFocus = foc; llSetCameraParams([CAMERA_POSITION, pos, CAMERA_FOCUS, foc]); } applyLockOnce() { if (!gHasPerm) return; if (!gLockOn) return; if (gMoving || gExternDrive) return; if (gCurPos == ZERO_VECTOR && camTrackOk()) { vector camPos = llGetCameraPos(); rotation camRot = llGetCameraRot(); gCurPos = camPos; gCurFocus = camPos + (llRot2Fwd(camRot) * gDEFAULT_FOCUS_DIST); } if (gCurPos == ZERO_VECTOR) return; vector foc = gCurFocus; if (gLockTarget != NULL_KEY) { list det = llGetObjectDetails(gLockTarget, [OBJECT_POS]); if (llGetListLength(det) >= 1) { vector tpos = llList2Vector(det, 0); foc = tpos + gLockTargetOffset; } } else { foc = gLockFocus; } gCurFocus = foc; llSetCameraParams([CAMERA_POSITION, gCurPos, CAMERA_FOCUS, foc]); } startMove(integer moveId, integer durMs, vector pos, vector focus) { // stop external tour playback (a move overrides) gExternDrive = FALSE; llMessageLinked(LINK_SET, CE_INT_TOUR_STOP, "", gOwner); if (!gHasPerm) return; applyBaseMove(); if (gCurPos == ZERO_VECTOR && gCurFocus == ZERO_VECTOR) { vector camPos = llGetCameraPos(); rotation camRot = llGetCameraRot(); vector camFocus = camPos + (llRot2Fwd(camRot) * gDEFAULT_FOCUS_DIST); gCurPos = camPos; gCurFocus = camFocus; } gMoveId = moveId; gStartPos = gCurPos; gStartFocus = gCurFocus; gTargetPos = pos; gTargetFocus = focus; float d = (float)durMs / 1000.0; if (durMs <= 0 || d <= gMOVE_STEP) { gMoving = FALSE; gCurPos = gTargetPos; gCurFocus = gTargetFocus; gCurRot = rotFromPosFocus(gCurPos, gCurFocus); llSetCameraParams([CAMERA_POSITION, gCurPos, CAMERA_FOCUS, gCurFocus]); llMessageLinked(LINK_SET, CE_EVT_MOVE_DONE, (string)gMoveId, gOwner); if (gPendingBaseApply) { gPendingBaseApply = FALSE; applyBaseForMode(); } setTimerForMode(); return; } gMoveStart = llGetTime(); gMoveDur = d; gMoving = TRUE; llSetCameraParams([CAMERA_POSITION, gStartPos, CAMERA_FOCUS, gStartFocus]); setTimerForMode(); } stopMove() { // stop external tour too llMessageLinked(LINK_SET, CE_INT_TOUR_STOP, "", gOwner); gExternDrive = FALSE; gMoving = FALSE; setTimerForMode(); } // ---------- config loading ---------- cfgResetDefaults() { gMOVE_STEP = 0.025; gFOLLOW_STEP = 0.05; gDEFAULT_MOVE_MS = 3000; gTOUR_MAX_POINTS = 20; gDEFAULT_FOCUS_DIST = 10.0; gTOUR_CAM_MIN_INTERVAL = 0.033; gTOUR_POS_EPS = 0.002; gTOUR_FOCUS_EPS = 0.002; gMOVE_POS_LAG = 2.0; gMOVE_FOCUS_LAG = 2.0; gFOLLOW_POS_LAG = 1.6; gFOLLOW_FOCUS_LAG = 1.6; gPOS_THRESHOLD = 0.02; gFOCUS_THRESHOLD = 0.02; gFOLLOW_PREDICT = 0.10; gBaseParamsDirty = TRUE; } cfgStart() { gCfgLine = 0; gCfgQuery = NULL_KEY; cfgResetDefaults(); if (llGetInventoryType(CFG_CARD) != INVENTORY_NOTECARD) { llMessageLinked(LINK_SET, CE_EVT_CFG_DUMP, cfgDump(), gOwner); if (gCamActive) { if (gMoving || gExternDrive) gPendingBaseApply = TRUE; else applyBaseForMode(); } return; } gCfgQuery = llGetNotecardLine(CFG_CARD, gCfgLine); } cfgParseLine(string line) { line = llStringTrim(line, STRING_TRIM); if (line == "") return; if (llGetSubString(line, 0, 0) == "#") return; if (llGetSubString(line, 0, 1) == "//") return; integer eq = llSubStringIndex(line, "="); if (eq < 1) return; string k = llToLower(llStringTrim(llGetSubString(line, 0, eq - 1), STRING_TRIM)); string v = llStringTrim(llGetSubString(line, eq + 1, -1), STRING_TRIM); if (k == "move_step") gMOVE_STEP = (float)v; else if (k == "follow_step") gFOLLOW_STEP = (float)v; else if (k == "default_move_ms") gDEFAULT_MOVE_MS = (integer)v; else if (k == "default_focus_dist") gDEFAULT_FOCUS_DIST = (float)v; else if (k == "move_pos_lag") gMOVE_POS_LAG = (float)v; else if (k == "move_focus_lag") gMOVE_FOCUS_LAG = (float)v; else if (k == "follow_pos_lag") gFOLLOW_POS_LAG = (float)v; else if (k == "follow_focus_lag") gFOLLOW_FOCUS_LAG = (float)v; else if (k == "pos_threshold") gPOS_THRESHOLD = (float)v; else if (k == "focus_threshold") gFOCUS_THRESHOLD = (float)v; else if (k == "follow_predict") gFOLLOW_PREDICT = (float)v; else if (k == "tour_cam_min_interval") { gTOUR_CAM_MIN_INTERVAL = clampf((float)v, 0.0, 0.25); } else if (k == "tour_pos_epsilon") { gTOUR_POS_EPS = clampf((float)v, 0.0, 1.0); } else if (k == "tour_focus_epsilon") { gTOUR_FOCUS_EPS = clampf((float)v, 0.0, 1.0); } else if (k == "tour_max_points") { integer n = (integer)v; if (n < 2) n = 2; if (n > 40) n = 40; // safety cap gTOUR_MAX_POINTS = n; } // Backward-compatible aliases else if (k == "camera_position_lag" || k == "poslag") { float f = (float)v; gMOVE_POS_LAG = f; gFOLLOW_POS_LAG = f; } else if (k == "camera_focus_lag" || k == "focuslag") { float f2 = (float)v; gMOVE_FOCUS_LAG = f2; gFOLLOW_FOCUS_LAG = f2; } gBaseParamsDirty = TRUE; } string cfgDump() { return "move_step=" + (string)gMOVE_STEP + "|" + "follow_step=" + (string)gFOLLOW_STEP + "|" + "default_move_ms=" + (string)gDEFAULT_MOVE_MS + "|" + "default_focus_dist=" + (string)gDEFAULT_FOCUS_DIST + "|" + "move_pos_lag=" + (string)gMOVE_POS_LAG + "|" + "move_focus_lag=" + (string)gMOVE_FOCUS_LAG + "|" + "follow_pos_lag=" + (string)gFOLLOW_POS_LAG + "|" + "follow_focus_lag=" + (string)gFOLLOW_FOCUS_LAG + "|" + "pos_threshold=" + (string)gPOS_THRESHOLD + "|" + "focus_threshold=" + (string)gFOCUS_THRESHOLD + "|" + "follow_predict=" + (string)gFOLLOW_PREDICT + "|" + "tour_cam_min_interval=" + (string)gTOUR_CAM_MIN_INTERVAL + "|" + "tour_pos_epsilon=" + (string)gTOUR_POS_EPS + "|" + "tour_focus_epsilon=" + (string)gTOUR_FOCUS_EPS + "|" + "tour_max_points=" + (string)gTOUR_MAX_POINTS; } // ---------- LSL events ---------- default { state_entry() { gOwner = llGetOwner(); cfgStart(); } on_rez(integer sp) { gOwner = llGetOwner(); } attach(key id) { gOwner = llGetOwner(); if (id == NULL_KEY) { releaseCam(); } } run_time_permissions(integer perm) { gHasPerm = ((perm & PERMISSION_CONTROL_CAMERA) != 0); if (gHasPerm) { applyBaseForMode(); llMessageLinked(LINK_SET, CE_EVT_READY, "CAM_OK", gOwner); if (gMovePending) { gMovePending = FALSE; startMove(gMovePendId, gMovePendDurMs, gMovePendPos, gMovePendFoc); } } else { gHasPerm = FALSE; llMessageLinked(LINK_SET, CE_EVT_DENIED, "CAM_DENIED", gOwner); } } dataserver(key qid, string data) { if (qid != gCfgQuery) return; if (data == EOF) { if (gCamActive) { if (gMoving || gExternDrive) gPendingBaseApply = TRUE; else applyBaseForMode(); } llMessageLinked(LINK_SET, CE_EVT_CFG_DUMP, cfgDump(), gOwner); return; } cfgParseLine(data); gCfgLine++; gCfgQuery = llGetNotecardLine(CFG_CARD, gCfgLine); } timer() { if (!camControlOk()) { // verhindert Spam-Warnungen und "tote" Moves gHasPerm = FALSE; gMoving = FALSE; gExternDrive = FALSE; llSetTimerEvent(0.0); return; } if (gExternDrive) return; // TourEngine drives the camera if (gMoving) { float now = llGetTime(); float t = (now - gMoveStart) / gMoveDur; if (t >= 1.0) { gMoving = FALSE; gCurPos = gTargetPos; gCurFocus = gTargetFocus; gCurRot = rotFromPosFocus(gCurPos, gCurFocus); llSetCameraParams([CAMERA_POSITION, gCurPos, CAMERA_FOCUS, gCurFocus]); llMessageLinked(LINK_SET, CE_EVT_MOVE_DONE, (string)gMoveId, gOwner); if (gPendingBaseApply) { gPendingBaseApply = FALSE; applyBaseForMode(); } else { applyBaseForMode(); } setTimerForMode(); return; } float e = smootherstep(t); vector pos = gStartPos + (gTargetPos - gStartPos) * e; vector foc = gStartFocus + (gTargetFocus - gStartFocus) * e; gCurPos = pos; gCurFocus = foc; llSetCameraParams([CAMERA_POSITION, pos, CAMERA_FOCUS, foc]); return; } // Secondary modes (only when not moving) vector pos = gCurPos; vector foc = gCurFocus; integer did = FALSE; if (gFollowOn && gFollowTarget != NULL_KEY) { list d = llGetObjectDetails(gFollowTarget, [OBJECT_POS, OBJECT_ROT, OBJECT_VELOCITY]); integer len = llGetListLength(d); if (len < 1) { gFollowOn = FALSE; } else { vector p = llList2Vector(d, 0); rotation r = ZERO_ROTATION; vector v = ZERO_VECTOR; if (len >= 2) r = llList2Rot(d, 1); if (len >= 3) v = llList2Vector(d, 2); p += v * gFOLLOW_PREDICT; if (gFollowMode == FOLLOW_WORLD) { pos = p + gCamOffW; foc = p + gFocusOffW; } else if (gFollowMode == FOLLOW_LOCAL) { pos = p + (gCamOffL * r); foc = p + (gFocusOffL * r); } else { rotation yawR = yawOnlyRot(r, v); pos = p + (gCamOffYaw * yawR); foc = p + (gFocusOffYaw * yawR); } if (gFollowBlendOn) { float b = (llGetTime() - gFollowBlendStart) / gFollowBlendDur; if (b >= 1.0) { b = 1.0; gFollowBlendOn = FALSE; } pos = gFollowBlendStartCam + (pos - gFollowBlendStartCam) * b; foc = gFollowBlendStartFocus + (foc - gFollowBlendStartFocus) * b; } did = TRUE; } } if (gLockOn) { if (gLockTarget != NULL_KEY) { list det2 = llGetObjectDetails(gLockTarget, [OBJECT_POS]); if (llGetListLength(det2) >= 1) { vector tpos = llList2Vector(det2, 0); foc = tpos + gLockTargetOffset; did = TRUE; } } else { foc = gLockFocus; did = TRUE; } } if (did && gHasPerm) { gCurPos = pos; gCurFocus = foc; llSetCameraParams([CAMERA_POSITION, pos, CAMERA_FOCUS, foc]); } } link_message(integer sender, integer num, string str, key id) { // ---- internal tour drive ---- if (num == CE_INT_TOUR_BEGIN) { gExternDrive = TRUE; gMoving = TRUE; gMoveId = (integer)str; if (gHasPerm) { applyBaseMove(); gCamActive = TRUE; } llSetTimerEvent(0.0); return; } if (num == CE_INT_SET_CAM) { if (!gHasPerm || !gExternDrive) return; integer sep = llSubStringIndex(str, "|"); if (sep < 1) return; vector pos = (vector)llGetSubString(str, 0, sep - 1); vector foc = (vector)llGetSubString(str, sep + 1, -1); gCurPos = pos; gCurFocus = foc; llSetCameraParams([CAMERA_POSITION, pos, CAMERA_FOCUS, foc]); return; } if (num == CE_INT_TOUR_END) { gExternDrive = FALSE; gMoving = FALSE; if (gPendingBaseApply) { gPendingBaseApply = FALSE; } applyBaseForMode(); setTimerForMode(); return; } if (num == CE_INT_TOUR_STOP) { // IMPORTANT: // Core broadcastet CE_INT_TOUR_STOP um TourEngine zu stoppen. // Ohne Guard würde Core damit JEDE normale MOVE sofort wieder beenden. if (!gExternDrive) return; // ignorieren, wenn wir nicht extern gefahren werden gExternDrive = FALSE; gMoving = FALSE; if (gPendingBaseApply) { gPendingBaseApply = FALSE; } applyBaseForMode(); setTimerForMode(); return; } // ---- controller protocol ---- if (num == CE_CMD_INIT) { requestCamPerm(); return; } if (num == CE_CMD_RELEASE) { releaseCam(); return; } if (num == CE_CMD_CFG_RELOAD) { cfgStart(); return; } if (num == CE_CMD_CFG_DUMP) { llMessageLinked(LINK_SET, CE_EVT_CFG_DUMP, cfgDump(), gOwner); return; } if (num == CE_CMD_STOP) { stopMove(); return; } if (num == CE_CMD_TOUR) { // handled by HS_CamTourEngine.lsl return; } if (num == CE_CMD_MOVE) { list p = llParseString2List(str, ["|"], []); if (llGetListLength(p) < 4) return; integer mid = (integer)llList2String(p, 0); integer dms = (integer)llList2String(p, 1); vector pos = (vector)llList2String(p, 2); vector foc = (vector)llList2String(p, 3); // Permission wirklich prüfen (nicht nur gHasPerm vertrauen) if (!camControlOk()) { gHasPerm = FALSE; gMovePending = TRUE; gMovePendId = mid; gMovePendDurMs = dms; gMovePendPos = pos; gMovePendFoc = foc; requestCamPerm(); // hol Permission nach return; } gHasPerm = TRUE; startMove(mid, dms, pos, foc); return; } if (num == CE_CMD_LOCK) { list p = llParseString2List(str, ["|"], []); if (llGetListLength(p) < 2) return; gLockOn = (integer)llList2String(p, 0); if (!gLockOn) { gLockTarget = NULL_KEY; if (!gMoving && !gExternDrive) setTimerForMode(); return; } // Lock wins gFollowOn = FALSE; gFollowTarget = NULL_KEY; gFollowBlendOn = FALSE; string arg = llList2String(p, 1); if (llGetSubString(arg,0,0) == "<") { gLockTarget = NULL_KEY; gLockFocus = (vector)arg; } else { key k = (key)arg; if (k != NULL_KEY) gLockTarget = k; else { gLockTarget = NULL_KEY; gLockFocus = (vector)arg; } } if (gHasPerm && !gCamActive) applyBaseForMode(); applyLockOnce(); if (!gMoving && !gExternDrive) setTimerForMode(); return; } if (num == CE_CMD_FOLLOW) { list p = llParseString2List(str, ["|"], []); integer len = llGetListLength(p); if (len < 2) return; gFollowOn = (integer)llList2String(p, 0); gFollowTarget = (key)llList2String(p, 1); if (gFollowOn) { gLockOn = FALSE; gLockTarget = NULL_KEY; } vector posOff = ZERO_VECTOR; vector focOff = ZERO_VECTOR; if (len >= 4) { posOff = (vector)llList2String(p, 2); focOff = (vector)llList2String(p, 3); } integer mode = FOLLOW_WORLD; integer trans = 0; if (len >= 5) mode = (integer)llList2String(p, 4); if (len >= 6) trans = (integer)llList2String(p, 5); if (!gFollowOn) { gFollowTarget = NULL_KEY; gFollowBlendOn = FALSE; if (!gExternDrive) setTimerForMode(); return; } if (gFollowTarget == NULL_KEY) gFollowTarget = gOwner; if (gHasPerm && !gCamActive) applyBaseForMode(); integer ok = FALSE; if (posOff != ZERO_VECTOR || focOff != ZERO_VECTOR) { gFollowMode = FOLLOW_WORLD; gCamOffW = posOff; gFocusOffW = focOff; gFollowBlendOn = (trans > 0); if (gFollowBlendOn) { gFollowBlendDur = (float)trans / 1000.0; if (gFollowBlendDur < 0.01) gFollowBlendDur = 0.01; gFollowBlendStart = llGetTime(); if (camTrackOk()) { vector camPos = llGetCameraPos(); rotation camRot = llGetCameraRot(); gFollowBlendStartCam = camPos; gFollowBlendStartFocus = camPos + (llRot2Fwd(camRot) * gDEFAULT_FOCUS_DIST); } else { gFollowBlendStartCam = gCurPos; gFollowBlendStartFocus = gCurFocus; } } ok = TRUE; } else { ok = followCapture(gFollowTarget, mode, trans); } if (!ok) { gFollowOn = FALSE; gFollowTarget = NULL_KEY; gFollowBlendOn = FALSE; if (!gExternDrive) setTimerForMode(); return; } applyFollowOnce(); if (!gMoving && !gExternDrive) setTimerForMode(); return; } float fov = 1.04719755; // default 60 Grad if (num == CE_CMD_GET_STATE) { string reqId = str; if (camTrackOk()) { vector camPos = llGetCameraPos(); rotation camRot = llGetCameraRot(); gCurPos = camPos; gCurRot = camRot; gCurFocus = camPos + (llRot2Fwd(camRot) * gDEFAULT_FOCUS_DIST); fov = llGetCameraFOV(); // radians, viewer-reported } else { if (gCurRot == ZERO_ROTATION) gCurRot = rotFromPosFocus(gCurPos, gCurFocus); } llMessageLinked(LINK_SET, CE_EVT_STATE, reqId + "|" + (string)gCurPos + "|" + (string)gCurFocus + "|" + (string)gCurRot + "|" + (string)fov, gOwner ); return; } } }