395 lines
11 KiB
XML
395 lines
11 KiB
XML
|
|
<?xml version="1.0" encoding="iso-8859-1"?>
|
||
|
|
<!DOCTYPE muclient>
|
||
|
|
|
||
|
|
<muclient>
|
||
|
|
<plugin
|
||
|
|
name="GagManager"
|
||
|
|
author="Antigravity"
|
||
|
|
id="d2211ef7d54c4a7bd0cc9ab8"
|
||
|
|
save_state="y"
|
||
|
|
language="Lua"
|
||
|
|
purpose="VIPMud-like accessible gagging plugin."
|
||
|
|
requires="4.46"
|
||
|
|
version="1.1"
|
||
|
|
>
|
||
|
|
</plugin>
|
||
|
|
|
||
|
|
<!-- Aliases -->
|
||
|
|
<aliases>
|
||
|
|
<alias
|
||
|
|
match="gagconfig"
|
||
|
|
enabled="y"
|
||
|
|
script="gagconfig"
|
||
|
|
omit_from_output="y"
|
||
|
|
omit_from_log="y"
|
||
|
|
omit_from_command_history="y"
|
||
|
|
send_to="12"
|
||
|
|
sequence="100"
|
||
|
|
>
|
||
|
|
</alias>
|
||
|
|
</aliases>
|
||
|
|
|
||
|
|
<!-- Script -->
|
||
|
|
<script>
|
||
|
|
<![CDATA[
|
||
|
|
require "serialize"
|
||
|
|
|
||
|
|
dir = world.GetInfo(67)
|
||
|
|
|
||
|
|
gags = {}
|
||
|
|
subst_map = {}
|
||
|
|
|
||
|
|
function OnPluginInstall()
|
||
|
|
-- Register alt+f2 to open gag configuration menu (was alt+n, which
|
||
|
|
-- conflicted with direction_plugin.xml's south-west movement key)
|
||
|
|
Accelerator("alt+f2", "gagconfig")
|
||
|
|
|
||
|
|
load_gags()
|
||
|
|
register_all_gags()
|
||
|
|
end
|
||
|
|
|
||
|
|
function OnPluginClose()
|
||
|
|
end
|
||
|
|
|
||
|
|
function gags_file_path()
|
||
|
|
-- Store gags as a human-readable file next to the world file so users can copy/share them
|
||
|
|
return dir .. "gags.lua"
|
||
|
|
end
|
||
|
|
|
||
|
|
function save_gags()
|
||
|
|
-- Save to MUSHclient variable (for fast load)
|
||
|
|
local serialized = serialize.save_simple(gags)
|
||
|
|
SetVariable("gags_data", serialized)
|
||
|
|
-- Also write a plain file next to the world that users can copy/share
|
||
|
|
local f = io.open(gags_file_path(), "w")
|
||
|
|
if f then
|
||
|
|
f:write("-- Cosmic Rage Mush Soundpack - Gag Manager data\n")
|
||
|
|
f:write("-- Copy this file to share your gags with others.\n")
|
||
|
|
f:write("-- Place it next to your cosmic rage.MCL file to load it.\n\n")
|
||
|
|
f:write("return " .. serialized .. "\n")
|
||
|
|
f:close()
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
function load_gags()
|
||
|
|
-- Try loading from file first (portable, shareable)
|
||
|
|
local f = io.open(gags_file_path(), "r")
|
||
|
|
if f then
|
||
|
|
local content = f:read("*all")
|
||
|
|
f:close()
|
||
|
|
local chunk, err = loadstring(content)
|
||
|
|
if chunk then
|
||
|
|
local ok, result = pcall(chunk)
|
||
|
|
if ok and type(result) == "table" then
|
||
|
|
gags = result
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
if not gags or gags == {} then
|
||
|
|
-- Fall back to MUSHclient variable
|
||
|
|
local serialized = GetVariable("gags_data")
|
||
|
|
if serialized and serialized ~= "" then
|
||
|
|
local chunk, err = loadstring("return " .. serialized)
|
||
|
|
if chunk then
|
||
|
|
local ok, result = pcall(chunk)
|
||
|
|
if ok then gags = result end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
gags = gags or {}
|
||
|
|
|
||
|
|
-- Migrate old entries (from before each gag had a stable trigger name) and
|
||
|
|
-- make sure every gag has a unique trigger name we can add/delete directly.
|
||
|
|
local next_id = 1
|
||
|
|
for _, gag in ipairs(gags) do
|
||
|
|
if gag.name then
|
||
|
|
local n = tonumber(gag.name:match("gag_(%d+)"))
|
||
|
|
if n and n >= next_id then next_id = n + 1 end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
for _, gag in ipairs(gags) do
|
||
|
|
if not gag.name then
|
||
|
|
gag.name = "gag_" .. tostring(next_id)
|
||
|
|
next_id = next_id + 1
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
-- Adds (or replaces) the MUSHclient trigger for a single gag entry.
|
||
|
|
function add_trigger_for_gag(gag)
|
||
|
|
-- Enabled=1, KeepEvaluating=8, Replace=1024, Temporary=16384
|
||
|
|
local flags = 1 + 8 + 1024 + 16384
|
||
|
|
local script_name = ""
|
||
|
|
|
||
|
|
if gag.method == "all" then
|
||
|
|
flags = flags + 4 -- OmitFromOutput
|
||
|
|
elseif gag.method == "voice" then
|
||
|
|
flags = flags + 4 -- OmitFromOutput: hidden from screen, but MushReader still speaks it
|
||
|
|
elseif gag.method == "substitute" then
|
||
|
|
flags = flags + 4 -- OmitFromOutput: hide the original line
|
||
|
|
script_name = "substitute_trigger"
|
||
|
|
end
|
||
|
|
|
||
|
|
-- No regex needed - MUSHclient's normal trigger matching already treats
|
||
|
|
-- "*" as a wildcard, so the gag pattern can be used as-is.
|
||
|
|
local res = AddTrigger(gag.name, gag.line, "", flags, -1, 0, "", script_name)
|
||
|
|
if res == 0 then
|
||
|
|
SetTriggerOption(gag.name, "group", gag.file)
|
||
|
|
if gag.method == "substitute" then
|
||
|
|
SetTriggerOption(gag.name, "send_to", "14")
|
||
|
|
subst_map[gag.name] = gag.subst
|
||
|
|
end
|
||
|
|
end
|
||
|
|
return res
|
||
|
|
end
|
||
|
|
|
||
|
|
function register_all_gags()
|
||
|
|
subst_map = {}
|
||
|
|
|
||
|
|
for _, gag in ipairs(gags) do
|
||
|
|
add_trigger_for_gag(gag)
|
||
|
|
end
|
||
|
|
|
||
|
|
-- Apply enabled/disabled states to trigger groups
|
||
|
|
local groups = {"ActivityGags", "ShipGags", "VehicleGags", "MiscGags"}
|
||
|
|
for _, g in ipairs(groups) do
|
||
|
|
local state = GetVariable(g .. "_state") or "enabled"
|
||
|
|
EnableTriggerGroup(g, state == "enabled")
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
-- --- TRIGGER CALLBACKS ---
|
||
|
|
|
||
|
|
function substitute_trigger(name, line, wildcards)
|
||
|
|
local subst = subst_map[name]
|
||
|
|
if subst then
|
||
|
|
local res = subst
|
||
|
|
for i = 1, #wildcards do
|
||
|
|
res = res:gsub("%%" .. tostring(i), wildcards[i])
|
||
|
|
end
|
||
|
|
res = res:gsub("%%0", line)
|
||
|
|
Note(res) -- original line is already omitted; print the replacement
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
-- --- GAG CONFIGURATION DIALOGS ---
|
||
|
|
|
||
|
|
local function sleep(s)
|
||
|
|
local t = os.clock()
|
||
|
|
while os.clock() - t < s do end
|
||
|
|
end
|
||
|
|
|
||
|
|
function toggle_group(group_name)
|
||
|
|
sleep(0.2)
|
||
|
|
local current = GetVariable(group_name .. "_state") or "enabled"
|
||
|
|
local new_state = "disabled"
|
||
|
|
if current == "disabled" then
|
||
|
|
new_state = "enabled"
|
||
|
|
end
|
||
|
|
SetVariable(group_name .. "_state", new_state)
|
||
|
|
EnableTriggerGroup(group_name, new_state == "enabled")
|
||
|
|
Execute("tts_interrupt " .. group_name .. " set to " .. new_state)
|
||
|
|
end
|
||
|
|
|
||
|
|
function delete_gag()
|
||
|
|
sleep(0.2)
|
||
|
|
if #gags == 0 then
|
||
|
|
Execute("tts_interrupt No gags defined yet.")
|
||
|
|
return
|
||
|
|
end
|
||
|
|
|
||
|
|
local choices = {}
|
||
|
|
for i, gag in ipairs(gags) do
|
||
|
|
table.insert(choices, string.format("[%s] %s (%s)", gag.file, gag.line, gag.method))
|
||
|
|
end
|
||
|
|
|
||
|
|
local result = utils.choose("Select a gag to delete:", "Delete Gag", choices)
|
||
|
|
if result then
|
||
|
|
local idx
|
||
|
|
if type(result) == "number" then
|
||
|
|
idx = result
|
||
|
|
else
|
||
|
|
for i, v in ipairs(choices) do
|
||
|
|
if v == result then idx = i; break end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
if idx then
|
||
|
|
local gag = gags[idx]
|
||
|
|
DeleteTrigger(gag.name)
|
||
|
|
subst_map[gag.name] = nil
|
||
|
|
table.remove(gags, idx)
|
||
|
|
save_gags()
|
||
|
|
Execute("tts_interrupt Gag deleted: " .. gag.line)
|
||
|
|
end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
function next_gag_name()
|
||
|
|
local max_id = 0
|
||
|
|
for _, gag in ipairs(gags) do
|
||
|
|
local n = tonumber(gag.name:match("gag_(%d+)"))
|
||
|
|
if n and n > max_id then max_id = n end
|
||
|
|
end
|
||
|
|
return "gag_" .. tostring(max_id + 1)
|
||
|
|
end
|
||
|
|
|
||
|
|
function addnewgag()
|
||
|
|
sleep(0.2)
|
||
|
|
-- Prompt 1: Line to gag
|
||
|
|
local gag_line = utils.inputbox(
|
||
|
|
"Enter the exact text you want to gag, as it appears in the game.\n" ..
|
||
|
|
"\n" ..
|
||
|
|
"You can use * (asterisk) as a wildcard to match any text.\n" ..
|
||
|
|
"Examples:\n" ..
|
||
|
|
" warrior hugs you -- gags that exact line\n" ..
|
||
|
|
" * hugs you -- gags anyone hugging you\n" ..
|
||
|
|
" You receive * -- gags any 'You receive ...' line",
|
||
|
|
"Step 1 of 3: Enter gag pattern", "")
|
||
|
|
if not gag_line or gag_line == "" then
|
||
|
|
return
|
||
|
|
end
|
||
|
|
|
||
|
|
sleep(0.2)
|
||
|
|
-- Prompt 2: Gag method
|
||
|
|
local m_choices = {
|
||
|
|
"Voice only - Hide from screen, still spoken by screen reader",
|
||
|
|
"Full gag - Hide completely from screen and speech",
|
||
|
|
"Substitute - Replace the matched line with different text"
|
||
|
|
}
|
||
|
|
local m_res = utils.choose(
|
||
|
|
"How should this line be gagged?\n" ..
|
||
|
|
"\n" ..
|
||
|
|
"Voice only: The line is hidden from the screen and log, but your screen reader will still read it aloud. Good for information you want to hear but don't need cluttering the screen.\n" ..
|
||
|
|
"Full gag: The line is completely removed from both the screen and speech. Good for things you never want to see.\n" ..
|
||
|
|
"Substitute: Replaces the matched line with your own custom text.",
|
||
|
|
"Step 2 of 3: Choose gag method", m_choices)
|
||
|
|
if not m_res then
|
||
|
|
return
|
||
|
|
end
|
||
|
|
if type(m_res) == "number" then
|
||
|
|
m_res = m_choices[m_res]
|
||
|
|
end
|
||
|
|
|
||
|
|
local gag_method = ""
|
||
|
|
local gag_sub = ""
|
||
|
|
if m_res == "Voice only - Hide from screen, still spoken by screen reader" then
|
||
|
|
gag_method = "voice"
|
||
|
|
elseif m_res == "Full gag - Hide completely from screen and speech" then
|
||
|
|
gag_method = "all"
|
||
|
|
elseif m_res == "Substitute - Replace the matched line with different text" then
|
||
|
|
gag_method = "substitute"
|
||
|
|
sleep(0.2)
|
||
|
|
gag_sub = utils.inputbox(
|
||
|
|
"Enter the replacement text that will be shown instead of the matched line.\n" ..
|
||
|
|
"\n" ..
|
||
|
|
"You can use placeholders:\n" ..
|
||
|
|
" %0 - The entire original matched line\n" ..
|
||
|
|
" %1 - The first wildcard * match\n" ..
|
||
|
|
" %2 - The second wildcard * match, and so on\n" ..
|
||
|
|
"\n" ..
|
||
|
|
"Example: If your gag pattern is '* hugs you'\n" ..
|
||
|
|
"and you type '%1 pokes you', it shows 'warrior pokes you' instead.",
|
||
|
|
"Substitution text", "")
|
||
|
|
if not gag_sub or gag_sub == "" then
|
||
|
|
return
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
sleep(0.2)
|
||
|
|
-- Prompt 3: Category
|
||
|
|
local f_choices = {
|
||
|
|
"Activity Gags - Mining, crafting, harvesting, general activities",
|
||
|
|
"Ship Gags - Starship combat, travel, ship systems",
|
||
|
|
"Miscellaneous Gags - Everything else that does not fit above",
|
||
|
|
"Vehicle Gags - Ground vehicles, speeders, walkers"
|
||
|
|
}
|
||
|
|
local f_res = utils.choose(
|
||
|
|
"Which category should this gag be stored in?\n" ..
|
||
|
|
"\n" ..
|
||
|
|
"Categories let you toggle entire groups of gags on or off at once from the main menu.\n" ..
|
||
|
|
"For example, you can disable all Ship Gags when not flying, and re-enable them when you board a ship.",
|
||
|
|
"Step 3 of 3: Choose category", f_choices)
|
||
|
|
if not f_res then
|
||
|
|
return
|
||
|
|
end
|
||
|
|
if type(f_res) == "number" then
|
||
|
|
f_res = f_choices[f_res]
|
||
|
|
end
|
||
|
|
|
||
|
|
local gag_file = ""
|
||
|
|
if f_res == "Activity Gags - Mining, crafting, harvesting, general activities" then
|
||
|
|
gag_file = "ActivityGags"
|
||
|
|
elseif f_res == "Ship Gags - Starship combat, travel, ship systems" then
|
||
|
|
gag_file = "ShipGags"
|
||
|
|
elseif f_res == "Miscellaneous Gags - Everything else that does not fit above" then
|
||
|
|
gag_file = "MiscGags"
|
||
|
|
elseif f_res == "Vehicle Gags - Ground vehicles, speeders, walkers" then
|
||
|
|
gag_file = "VehicleGags"
|
||
|
|
end
|
||
|
|
|
||
|
|
local gag = {
|
||
|
|
name = next_gag_name(),
|
||
|
|
line = gag_line,
|
||
|
|
method = gag_method,
|
||
|
|
subst = gag_sub,
|
||
|
|
file = gag_file
|
||
|
|
}
|
||
|
|
|
||
|
|
local res = add_trigger_for_gag(gag)
|
||
|
|
if res ~= 0 then
|
||
|
|
Execute("tts_interrupt Could not create that gag, error " .. tostring(res))
|
||
|
|
return
|
||
|
|
end
|
||
|
|
|
||
|
|
table.insert(gags, gag)
|
||
|
|
save_gags()
|
||
|
|
Execute("tts_interrupt Gag added successfully!")
|
||
|
|
end
|
||
|
|
|
||
|
|
function gagconfig()
|
||
|
|
repeat
|
||
|
|
sleep(0.2)
|
||
|
|
local choices = {
|
||
|
|
"Create a new gag...",
|
||
|
|
"Delete an existing gag...",
|
||
|
|
"Toggle Activity-related gags (" .. (GetVariable("ActivityGags_state") or "enabled") .. ")",
|
||
|
|
"Toggle Ship-related gags (" .. (GetVariable("ShipGags_state") or "enabled") .. ")",
|
||
|
|
"Toggle Vehicle-related gags (" .. (GetVariable("VehicleGags_state") or "enabled") .. ")",
|
||
|
|
"Toggle Misc Gags (" .. (GetVariable("MiscGags_state") or "enabled") .. ")"
|
||
|
|
}
|
||
|
|
local result = utils.choose("Choose a gag option to edit or toggle:", "Gag Configuration (Alt+F2)", choices)
|
||
|
|
if not result then break end
|
||
|
|
|
||
|
|
-- utils.choose may return either an index (number) or the string itself
|
||
|
|
local idx
|
||
|
|
if type(result) == "number" then
|
||
|
|
idx = result
|
||
|
|
else
|
||
|
|
for i, v in ipairs(choices) do
|
||
|
|
if v == result then idx = i; break end
|
||
|
|
end
|
||
|
|
end
|
||
|
|
|
||
|
|
if idx == 1 then
|
||
|
|
addnewgag()
|
||
|
|
elseif idx == 2 then
|
||
|
|
delete_gag()
|
||
|
|
elseif idx == 3 then
|
||
|
|
toggle_group("ActivityGags")
|
||
|
|
elseif idx == 4 then
|
||
|
|
toggle_group("ShipGags")
|
||
|
|
elseif idx == 5 then
|
||
|
|
toggle_group("VehicleGags")
|
||
|
|
elseif idx == 6 then
|
||
|
|
toggle_group("MiscGags")
|
||
|
|
end
|
||
|
|
until false
|
||
|
|
end
|
||
|
|
|
||
|
|
]]>
|
||
|
|
</script>
|
||
|
|
</muclient>
|