Skip to content

New feature: TPE plugin #1157

@anne-skydancer

Description

@anne-skydancer

/* This is a plugin for opencollar that is meant to enable or disable TPE in a non-hackish way (meaning, no fiddling with settings, but by an act of the primary owner).

I've done what i can, but now it's beyond my ability to continue, so i would like feedback on it. */

/*

  • Total Power Exchange (TPE) Plugin for OpenCollar 8.x — FINAL, OWNER PROTECTED
    • Prevents wearer from enabling TPE if no primary owner is set
    • Menu dialog appears only once per click
    • All TPE logic, debug, and owner check via oc_settings
      */

integer ALIVE = -55;
integer READY = -56;
integer STARTUP = -57;
integer MENUNAME_REQUEST = 3000;
integer MENUNAME_RESPONSE = 3001;
integer CMD_ZERO = 0;
integer CMD_SAFEWORD = 510;
integer CMD_RELAY_SAFEWORD = 511;
integer CMD_RLV_RELAY = 507;

integer ISOWNED_REQUEST = 7600;
integer ISOWNED_RESPONSE = 7601;

integer gTPE_Enabled = FALSE;
key gOwner;
integer gDialogChan = -999987;
key gLastDialogUser;

float gLastMenuTime = 0.0;

integer gAwaitingOwnerCheck = FALSE;
key gPendingUser;

sendAPICmd(string cmd) {
llMessageLinked(LINK_SET, CMD_ZERO, cmd, gOwner);
llOwnerSay("[TPE] Command sent: " + cmd);
}

applyTPE() {
llOwnerSay(
"[TPE] ACTIVATING TPE:\n" +
"• Public ON\n" +
"• Safeword DISABLED\n" +
"• Wearer controls BLOCKED\n" +
"• RLV relay AUTOMATIC"
);
sendAPICmd("auth=public~1");
llMessageLinked(LINK_SET, CMD_RLV_RELAY, "automatic", gOwner);
llOwnerSay("[TPE] RLV relay set to automatic");
}

revertTPE() {
llOwnerSay("[TPE] DEACTIVATING TPE: Restoring defaults.");
sendAPICmd("auth=public~0");
}

showTPEMenu(key who) {
float now = llGetUnixTime();
if (now - gLastMenuTime < 1.0) return; // Prevent double-pop
gLastMenuTime = now;

gLastDialogUser = who;
list btns = [];
string msg = "[TPE]\nDANGEROUS PLUGIN!\nEnabling TPE disables ALL wearer controls and disables the safeword.\nProceed with caution!";
if (!gTPE_Enabled)
    btns = ["TPE On"];
else
    btns = ["TPE Off"];
llListenRemove(gDialogChan);
gDialogChan = llListen(gDialogChan, "", who, "");
llDialog(who, msg, btns, gDialogChan);
llOwnerSay("[TPE DEBUG] Showing TPE menu dialog to: " + (string)who);

}

requestOwnerStatus(key who) {
gAwaitingOwnerCheck = TRUE;
gPendingUser = who;
llMessageLinked(LINK_SET, ISOWNED_REQUEST, "", who);
}

default {
state_entry() {
gOwner = llGetOwner();
llOwnerSay("[TPE] Owner-protected plugin loaded. Awaiting menu interactions...");
}

link_message(integer sender, integer msg, string str, key id) {
    llOwnerSay(
        "[TPE DEBUG] link_message: msg=" + (string)msg +
        " str=\"" + str + "\" sender=" + (string)sender +
        " id=" + (string)id
    );

    if (msg == MENUNAME_REQUEST) {
        if (str == "Apps") {
            llOwnerSay("[TPE DEBUG] Registering 'Apps|TPE' under context 'Apps'");
            llMessageLinked(LINK_SET, MENUNAME_RESPONSE, "Apps|TPE", "Apps");
        }
        else if (str == "Apps|TPE" || str == "TPE") {
            llOwnerSay("[TPE DEBUG] Registering submenus 'Apps|TPE|On', 'Apps|TPE|Off' under context '" + str + "'");
            llMessageLinked(LINK_SET, MENUNAME_RESPONSE, "Apps|TPE|On", str);
            llMessageLinked(LINK_SET, MENUNAME_RESPONSE, "Apps|TPE|Off", str);
        }
        return;
    }

    if ((msg == 0 || msg == 500) && llToLower(str) == "menu tpe") {
        llOwnerSay("[TPE DEBUG] Handling 'menu TPE' request with custom menu dialog!");
        showTPEMenu(id);
        return;
    }

    if (msg == CMD_ZERO) {
        list parts = llParseString2List(str, ["|"], []);
        if (llGetListLength(parts) == 3 &&
            llList2String(parts,0) == "Apps" &&
            llList2String(parts,1) == "TPE") {
            string action = llList2String(parts,2);
            if (action == "On" && !gTPE_Enabled) {
                requestOwnerStatus(id);
                return;
            }
            if (action == "Off" && gTPE_Enabled) {
                llDialog(id,
                    "[TPE]\nDisable Total Power Exchange?",
                    ["Yes", "No"], gDialogChan);
                llListenRemove(gDialogChan);
                gDialogChan = llListen(gDialogChan, "", id, "");
                return;
            }
        }
        string lstr = llToLower(str);
        if (lstr == "tpe") {
            showTPEMenu(id);
            return;
        }
        if (lstr == "tpe on" && !gTPE_Enabled) {
            requestOwnerStatus(id);
            return;
        }
        if (lstr == "tpe off" && gTPE_Enabled) {
            llDialog(id, "[TPE] Are you sure you want to DISABLE TPE?", ["Yes", "No"], gDialogChan);
            llListenRemove(gDialogChan);
            gDialogChan = llListen(gDialogChan, "", id, "");
            return;
        }
    }

    // Handle ISOWNED_RESPONSE from oc_settings
    if (msg == ISOWNED_RESPONSE && gAwaitingOwnerCheck && id == gPendingUser) {
        gAwaitingOwnerCheck = FALSE;
        if (str == "0") {
            llOwnerSay("You can't lock yourself out of your collar. TPE requires a primary owner set.");
            return;
        } else {
            llDialog(id,
                "[TPE] Are you SURE you want to ENABLE TPE?\nYou will lose all wearer controls and the safeword!\nThis is irreversible until the owner disables it.\nProceed?",
                ["Yes", "No"], gDialogChan);
            llListenRemove(gDialogChan);
            gDialogChan = llListen(gDialogChan, "", id, "");
        }
        return;
    }

    if (gTPE_Enabled) {
        if (msg == CMD_SAFEWORD || msg == CMD_RELAY_SAFEWORD) {
            llOwnerSay("[TPE] Safeword blocked.");
            return;
        }
        if (msg == CMD_RLV_RELAY) {
            if (llSubStringIndex(str, "detach") != -1 || llSubStringIndex(str, "unbind") != -1) {
                llOwnerSay("[TPE] Unbind attempt blocked: " + str);
                return;
            }
        }
    }
}

listen(integer channel, string name, key id, string msg) {
    if (channel != gDialogChan) return;
    if (id != gOwner) return;
    if (msg == "TPE On" && !gTPE_Enabled) {
        requestOwnerStatus(id);
        return;
    }
    if (msg == "TPE Off" && gTPE_Enabled) {
        llDialog(id, "[TPE] Are you sure you want to DISABLE TPE?", ["Yes", "No"], gDialogChan);
        return;
    }
    if (msg == "Yes" && !gTPE_Enabled) {
        gTPE_Enabled = TRUE;
        applyTPE();
        llOwnerSay("[TPE] Enabled.");
        llListenRemove(gDialogChan);
        return;
    }
    if (msg == "Yes" && gTPE_Enabled) {
        gTPE_Enabled = FALSE;
        revertTPE();
        llOwnerSay("[TPE] Disabled.");
        llListenRemove(gDialogChan);
        return;
    }
    if (msg == "No") {
        llOwnerSay("[TPE] Action canceled.");
        llListenRemove(gDialogChan);
        return;
    }
}

changed(integer change) {
    if (change & (CHANGED_REGION | CHANGED_OWNER)) {
        llResetScript();
    }
}

on_rez(integer start_param) {
    llResetScript();
}

}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions