Support for player entity message commands

This commit is contained in:
Kae 2023-07-19 01:16:22 +10:00
parent 770314fd7e
commit e1645f37fc
12 changed files with 133 additions and 77 deletions

View File

@ -1,5 +1,5 @@
{ {
"universeScriptContexts" : { "opensb" : ["/scripts/universeClient/opensb.lua"] }, "universeScriptContexts" : { "OpenStarbound" : ["/scripts/opensb/universeclient/universeclient.lua"] },
// Disables scissoring and letterboxing on vanilla and modded warp cinematics // Disables scissoring and letterboxing on vanilla and modded warp cinematics
"warpCinematicBase" : { "warpCinematicBase" : {

View File

@ -1,4 +1,5 @@
{ {
"genericScriptContexts" : { "OpenStarbound" : "/scripts/opensb/player/player.lua" },
"wireConfig" : { "wireConfig" : {
"innerBrightnessScale" : 20, "innerBrightnessScale" : 20,
"firstStripeThickness" : 0.6, "firstStripeThickness" : 0.6,

View File

@ -0,0 +1,30 @@
local module = {}
modules.commands = module
local commands = {}
local function command(name, func)
commands[name] = func
end
function module.init()
for name, func in pairs(commands) do
message.setHandler("/" .. name, function(isLocal, _, ...)
if not isLocal then
return
else
return func(...)
end
end)
end
end
command("run", function(src)
local success, result = pcall(loadstring, src, "/run")
if not success then
return "^#f00;compile error: " .. result
else
local success, result = pcall(result)
return not success and "^#f00;error: " .. result or sb.printJson(result)
end
end)

View File

@ -0,0 +1,2 @@
require "/scripts/opensb/util/modules.lua"
modules("/scripts/opensb/player/", {"commands"})

View File

@ -0,0 +1,2 @@
require "/scripts/opensb/util/modules.lua"
modules("/scripts/opensb/universeclient/", {"voicemanager"})

View File

@ -4,7 +4,7 @@ local fmt = string.format
local sqrt = math.sqrt local sqrt = math.sqrt
local module = {} local module = {}
submodules.voice_manager = module modules.voice_manager = module
--constants --constants
local INDICATOR_PATH = "/interface/voicechat/indicator/" local INDICATOR_PATH = "/interface/voicechat/indicator/"
@ -19,52 +19,35 @@ local LINE_COLOR = {50, 210, 255, 255}
local FONT_DIRECTIVES = "?border=1;333;3337?border=1;333;3330" local FONT_DIRECTIVES = "?border=1;333;3337?border=1;333;3330"
local NAME_PREFIX = "^noshadow,white,set;" local NAME_PREFIX = "^noshadow,white,set;"
local canvas local linePaddingDrawable
do
local linePaddingDrawable = { local drawable = { image = BACK_INDICATOR_IMAGE, position = {0, 0}, color = LINE_COLOR, centered = false }
image = BACK_INDICATOR_IMAGE, function linePaddingDrawable(a, b)
position = {0, 0}, drawable.image = BACK_INDICATOR_IMAGE .. fmt("?crop=%i;%i;%i;%i?fade=fff;1", a, 0, b, INDICATOR_SIZE[2])
color = LINE_COLOR, drawable.position[1] = a
centered = false return drawable;
} end
local function getLinePadding(a, b)
linePaddingDrawable.image = BACK_INDICATOR_IMAGE .. fmt("?crop=%i;%i;%i;%i?fade=fff;1", a, 0, b, INDICATOR_SIZE[2])
linePaddingDrawable.position[1] = a
return linePaddingDrawable;
end end
local lineDrawable = { local function line(pos, value)
line = {{LINE_PADDING, 24}, {10, 24}},
width = 48,
color = LINE_COLOR
}
local function drawTheLine(pos, value)
local width = math.floor((LINE_WIDTH * value) + 0.5) local width = math.floor((LINE_WIDTH * value) + 0.5)
LINE_COLOR[4] = 255 * math.min(1, sqrt(width / 350)) LINE_COLOR[4] = 255 * math.min(1, sqrt(width / 350))
if width > 0 then if width > 0 then
canvas:drawDrawable(getLinePadding(0, math.min(12, width)), pos) canvas:drawDrawable(linePaddingDrawable(0, math.min(12, width)), pos)
if width > 12 then if width > 12 then
lineDrawable.line[2][1] = math.min(width, LINE_WIDTH_PADDED) lineDrawable.line[2][1] = math.min(width, LINE_WIDTH_PADDED)
canvas:drawDrawable(lineDrawable, pos) canvas:drawDrawable(lineDrawable, pos)
if width > LINE_WIDTH_PADDED then if width > LINE_WIDTH_PADDED then
canvas:drawDrawable(getLinePadding(LINE_WIDTH_PADDED, width), pos) canvas:drawDrawable(linePaddingDrawable(LINE_WIDTH_PADDED, width), pos)
end end
end end
end end
end end
local drawable = { local canvas
image = BACK_INDICATOR_IMAGE,
centered = false
}
local textPositioning = { local drawable = { image = BACK_INDICATOR_IMAGE, centered = false }
position = {0, 0}, local textPositioning = { position = {0, 0}, horizontalAnchor = "left", verticalAnchor = "mid" }
horizontalAnchor = "left",
verticalAnchor = "mid"
}
local hoveredSpeaker = nil local hoveredSpeaker = nil
local hoveredSpeakerIndex = 1 local hoveredSpeakerIndex = 1
@ -78,7 +61,7 @@ end
local function drawSpeakerBar(mouse, pos, speaker, i) local function drawSpeakerBar(mouse, pos, speaker, i)
drawable.image = BACK_INDICATOR_IMAGE drawable.image = BACK_INDICATOR_IMAGE
canvas:drawDrawable(drawable, pos) canvas:drawDrawable(drawable, pos)
drawTheLine(pos, 1 - math.sqrt(math.min(1, math.max(0, speaker.loudness / -50)))) line(pos, 1 - sqrt(math.min(1, math.max(0, speaker.loudness / -50))))
local hovering = not speaker.isLocal and mouseOverSpeaker(mouse, pos) local hovering = not speaker.isLocal and mouseOverSpeaker(mouse, pos)
textPositioning.position = {pos[1] + 49, pos[2] + 24} textPositioning.position = {pos[1] + 49, pos[2] + 24}
@ -128,7 +111,6 @@ local function drawIndicators()
local mousePosition = canvas:mousePosition() local mousePosition = canvas:mousePosition()
local basePos = {screenSize[1] - 350, 50} local basePos = {screenSize[1] - 350, 50}
-- sort it ourselves for now
local speakersRemaining, speakers = {}, {} local speakersRemaining, speakers = {}, {}
local hoveredSpeakerId = nil local hoveredSpeakerId = nil
if hoveredSpeaker then if hoveredSpeaker then
@ -140,7 +122,6 @@ local function drawIndicators()
end end
local speakerCount = 0 local speakerCount = 0
local now = os.clock()
for i, speaker in pairs(voice.speakers()) do for i, speaker in pairs(voice.speakers()) do
local speakerId = speaker.speakerId local speakerId = speaker.speakerId
speakersRemaining[speakerId] = true speakersRemaining[speakerId] = true

View File

@ -1,8 +1,10 @@
submodules = {} modules = setmetatable({}, {__call = function(this, path, names)
for i, name in pairs(names) do
require(path .. name .. ".lua")
end
end})
require "/scripts/universeClient/opensb/voice_manager.lua" local modules, type = modules, type
local submodules, type = submodules, type
local function call(func, ...) local function call(func, ...)
if type(func) == "function" then if type(func) == "function" then
return func(...) return func(...)
@ -11,19 +13,19 @@ end
function init(...) function init(...)
script.setUpdateDelta(1) script.setUpdateDelta(1)
for i, module in pairs(submodules) do for i, module in pairs(modules) do
call(module.init, ...) call(module.init, ...)
end end
end end
function update(...) function update(...)
for i, module in pairs(submodules) do for i, module in pairs(modules) do
call(module.update, ...) call(module.update, ...)
end end
end end
function uninit(...) function uninit(...)
for i, module in pairs(submodules) do for i, module in pairs(modules) do
call(module.uninit, ...) call(module.uninit, ...)
end end
end end

View File

@ -1095,6 +1095,17 @@ LuaFunction LuaEngine::createRawFunction(lua_CFunction function) {
return LuaFunction(LuaDetail::LuaHandle(RefPtr<LuaEngine>(this), popHandle(m_state))); return LuaFunction(LuaDetail::LuaHandle(RefPtr<LuaEngine>(this), popHandle(m_state)));
} }
LuaFunction LuaEngine::createFunctionFromSource(int handleIndex, char const* contents, size_t size, char const* name) {
lua_checkstack(m_state, 2);
handleError(m_state, luaL_loadbuffer(m_state, contents, size, name));
pushHandle(m_state, handleIndex);
lua_setupvalue(m_state, -2, 1);
return LuaFunction(LuaDetail::LuaHandle(RefPtr<LuaEngine>(this), popHandle(m_state)));
}
void LuaEngine::pushLuaValue(lua_State* state, LuaValue const& luaValue) { void LuaEngine::pushLuaValue(lua_State* state, LuaValue const& luaValue) {
lua_checkstack(state, 1); lua_checkstack(state, 1);

View File

@ -310,6 +310,7 @@ public:
using LuaTable::contains; using LuaTable::contains;
using LuaTable::remove; using LuaTable::remove;
using LuaTable::engine; using LuaTable::engine;
using LuaTable::handleIndex;
// Splits the path by '.' character, so can get / set values in tables inside // Splits the path by '.' character, so can get / set values in tables inside
// other tables. If any table in the path is not a table but is accessed as // other tables. If any table in the path is not a table but is accessed as
@ -530,6 +531,8 @@ public:
LuaFunction createRawFunction(lua_CFunction func); LuaFunction createRawFunction(lua_CFunction func);
LuaFunction createFunctionFromSource(int handleIndex, char const* contents, size_t size, char const* name);
LuaThread createThread(); LuaThread createThread();
template <typename T> template <typename T>

View File

@ -10,6 +10,7 @@
#include "StarAiInterface.hpp" #include "StarAiInterface.hpp"
#include "StarQuestInterface.hpp" #include "StarQuestInterface.hpp"
#include "StarStatistics.hpp" #include "StarStatistics.hpp"
#include "StarInterfaceLuaBindings.hpp"
namespace Star { namespace Star {
@ -76,11 +77,10 @@ StringList ClientCommandProcessor::handleCommand(String const& commandLine) {
String allArguments = commandLine.substr(1); String allArguments = commandLine.substr(1);
String command = allArguments.extract(); String command = allArguments.extract();
auto arguments = m_parser.tokenizeToStringList(allArguments);
StringList result; StringList result;
if (auto builtinCommand = m_builtinCommands.maybe(command)) { if (auto builtinCommand = m_builtinCommands.maybe(command)) {
result.append((*builtinCommand)(arguments)); result.append((*builtinCommand)(allArguments));
} else if (auto macroCommand = m_macroCommands.maybe(command)) { } else if (auto macroCommand = m_macroCommands.maybe(command)) {
for (auto const& c : *macroCommand) { for (auto const& c : *macroCommand) {
if (c.beginsWith("/")) if (c.beginsWith("/"))
@ -89,7 +89,11 @@ StringList ClientCommandProcessor::handleCommand(String const& commandLine) {
result.append(c); result.append(c);
} }
} else { } else {
m_universeClient->sendChat(commandLine, ChatSendMode::Broadcast); auto player = m_universeClient->mainPlayer();
if (auto messageResult = player->receiveMessage(connectionForEntity(player->entityId()), strf("/{}", command), { allArguments }))
result.append(messageResult->isType(Json::Type::String) ? *messageResult->stringPtr() : messageResult->repr(1, true));
else
m_universeClient->sendChat(commandLine, ChatSendMode::Broadcast);
} }
return result; return result;
} catch (ShellParsingException const& e) { } catch (ShellParsingException const& e) {
@ -130,7 +134,8 @@ String ClientCommandProcessor::gravity() {
return toString(m_universeClient->worldClient()->gravity(m_universeClient->mainPlayer()->position())); return toString(m_universeClient->worldClient()->gravity(m_universeClient->mainPlayer()->position()));
} }
String ClientCommandProcessor::debug(StringList const& arguments) { String ClientCommandProcessor::debug(String const& argumentsString) {
auto arguments = m_parser.tokenizeToStringList(argumentsString);
if (!adminCommandAllowed()) if (!adminCommandAllowed())
return "You must be an admin to use this command."; return "You must be an admin to use this command.";
@ -168,7 +173,8 @@ String ClientCommandProcessor::asyncLighting() {
? "enabled" : "disabled"); ? "enabled" : "disabled");
} }
String ClientCommandProcessor::setGravity(StringList const& arguments) { String ClientCommandProcessor::setGravity(String const& argumentsString) {
auto arguments = m_parser.tokenizeToStringList(argumentsString);
if (!adminCommandAllowed()) if (!adminCommandAllowed())
return "You must be an admin to use this command."; return "You must be an admin to use this command.";
@ -198,7 +204,8 @@ String ClientCommandProcessor::monochromeLighting() {
return strf("Monochrome lighting {}", monochrome ? "enabled" : "disabled"); return strf("Monochrome lighting {}", monochrome ? "enabled" : "disabled");
} }
String ClientCommandProcessor::radioMessage(StringList const& arguments) { String ClientCommandProcessor::radioMessage(String const& argumentsString) {
auto arguments = m_parser.tokenizeToStringList(argumentsString);
if (!adminCommandAllowed()) if (!adminCommandAllowed())
return "You must be an admin to use this command."; return "You must be an admin to use this command.";
@ -225,7 +232,8 @@ String ClientCommandProcessor::clearCinematics() {
return "Player cinematic records cleared!"; return "Player cinematic records cleared!";
} }
String ClientCommandProcessor::startQuest(StringList const& arguments) { String ClientCommandProcessor::startQuest(String const& argumentsString) {
auto arguments = m_parser.tokenizeToStringList(argumentsString);
if (!adminCommandAllowed()) if (!adminCommandAllowed())
return "You must be an admin to use this command."; return "You must be an admin to use this command.";
@ -234,7 +242,8 @@ String ClientCommandProcessor::startQuest(StringList const& arguments) {
return "Quest started"; return "Quest started";
} }
String ClientCommandProcessor::completeQuest(StringList const& arguments) { String ClientCommandProcessor::completeQuest(String const& argumentsString) {
auto arguments = m_parser.tokenizeToStringList(argumentsString);
if (!adminCommandAllowed()) if (!adminCommandAllowed())
return "You must be an admin to use this command."; return "You must be an admin to use this command.";
@ -242,7 +251,8 @@ String ClientCommandProcessor::completeQuest(StringList const& arguments) {
return strf("Quest {} complete", arguments.at(0)); return strf("Quest {} complete", arguments.at(0));
} }
String ClientCommandProcessor::failQuest(StringList const& arguments) { String ClientCommandProcessor::failQuest(String const& argumentsString) {
auto arguments = m_parser.tokenizeToStringList(argumentsString);
if (!adminCommandAllowed()) if (!adminCommandAllowed())
return "You must be an admin to use this command."; return "You must be an admin to use this command.";
@ -250,7 +260,8 @@ String ClientCommandProcessor::failQuest(StringList const& arguments) {
return strf("Quest {} failed", arguments.at(0)); return strf("Quest {} failed", arguments.at(0));
} }
String ClientCommandProcessor::previewNewQuest(StringList const& arguments) { String ClientCommandProcessor::previewNewQuest(String const& argumentsString) {
auto arguments = m_parser.tokenizeToStringList(argumentsString);
if (!adminCommandAllowed()) if (!adminCommandAllowed())
return "You must be an admin to use this command."; return "You must be an admin to use this command.";
@ -259,7 +270,8 @@ String ClientCommandProcessor::previewNewQuest(StringList const& arguments) {
}); });
} }
String ClientCommandProcessor::previewQuestComplete(StringList const& arguments) { String ClientCommandProcessor::previewQuestComplete(String const& argumentsString) {
auto arguments = m_parser.tokenizeToStringList(argumentsString);
if (!adminCommandAllowed()) if (!adminCommandAllowed())
return "You must be an admin to use this command."; return "You must be an admin to use this command.";
@ -268,7 +280,8 @@ String ClientCommandProcessor::previewQuestComplete(StringList const& arguments)
}); });
} }
String ClientCommandProcessor::previewQuestFailed(StringList const& arguments) { String ClientCommandProcessor::previewQuestFailed(String const& argumentsString) {
auto arguments = m_parser.tokenizeToStringList(argumentsString);
if (!adminCommandAllowed()) if (!adminCommandAllowed())
return "You must be an admin to use this command."; return "You must be an admin to use this command.";
@ -294,7 +307,8 @@ String ClientCommandProcessor::deathCount() {
return strf("Total deaths: {}{}", deaths, deaths == 0 ? ". Well done!" : ""); return strf("Total deaths: {}{}", deaths, deaths == 0 ? ". Well done!" : "");
} }
String ClientCommandProcessor::cinema(StringList const& arguments) { String ClientCommandProcessor::cinema(String const& argumentsString) {
auto arguments = m_parser.tokenizeToStringList(argumentsString);
if (!adminCommandAllowed()) if (!adminCommandAllowed())
return "You must be an admin to use this command."; return "You must be an admin to use this command.";
@ -326,7 +340,8 @@ String ClientCommandProcessor::resetAchievements() {
return "Unable to reset achievements"; return "Unable to reset achievements";
} }
String ClientCommandProcessor::statistic(StringList const& arguments) { String ClientCommandProcessor::statistic(String const& argumentsString) {
auto arguments = m_parser.tokenizeToStringList(argumentsString);
if (!adminCommandAllowed()) if (!adminCommandAllowed())
return "You must be an admin to use this command."; return "You must be an admin to use this command.";
@ -337,7 +352,8 @@ String ClientCommandProcessor::statistic(StringList const& arguments) {
return values.join("\n"); return values.join("\n");
} }
String ClientCommandProcessor::giveEssentialItem(StringList const& arguments) { String ClientCommandProcessor::giveEssentialItem(String const& argumentsString) {
auto arguments = m_parser.tokenizeToStringList(argumentsString);
if (!adminCommandAllowed()) if (!adminCommandAllowed())
return "You must be an admin to use this command."; return "You must be an admin to use this command.";
@ -354,7 +370,8 @@ String ClientCommandProcessor::giveEssentialItem(StringList const& arguments) {
} }
} }
String ClientCommandProcessor::makeTechAvailable(StringList const& arguments) { String ClientCommandProcessor::makeTechAvailable(String const& argumentsString) {
auto arguments = m_parser.tokenizeToStringList(argumentsString);
if (!adminCommandAllowed()) if (!adminCommandAllowed())
return "You must be an admin to use this command."; return "You must be an admin to use this command.";
@ -365,7 +382,8 @@ String ClientCommandProcessor::makeTechAvailable(StringList const& arguments) {
return strf("Added {} to player's visible techs", arguments.at(0)); return strf("Added {} to player's visible techs", arguments.at(0));
} }
String ClientCommandProcessor::enableTech(StringList const& arguments) { String ClientCommandProcessor::enableTech(String const& argumentsString) {
auto arguments = m_parser.tokenizeToStringList(argumentsString);
if (!adminCommandAllowed()) if (!adminCommandAllowed())
return "You must be an admin to use this command."; return "You must be an admin to use this command.";
@ -377,7 +395,8 @@ String ClientCommandProcessor::enableTech(StringList const& arguments) {
return strf("Player tech {} enabled", arguments.at(0)); return strf("Player tech {} enabled", arguments.at(0));
} }
String ClientCommandProcessor::upgradeShip(StringList const& arguments) { String ClientCommandProcessor::upgradeShip(String const& argumentsString) {
auto arguments = m_parser.tokenizeToStringList(argumentsString);
if (!adminCommandAllowed()) if (!adminCommandAllowed())
return "You must be an admin to use this command."; return "You must be an admin to use this command.";

View File

@ -29,40 +29,40 @@ private:
String reload(); String reload();
String whoami(); String whoami();
String gravity(); String gravity();
String debug(StringList const& arguments); String debug(String const& argumentsString);
String boxes(); String boxes();
String fullbright(); String fullbright();
String asyncLighting(); String asyncLighting();
String setGravity(StringList const& arguments); String setGravity(String const& argumentsString);
String resetGravity(); String resetGravity();
String fixedCamera(); String fixedCamera();
String monochromeLighting(); String monochromeLighting();
String radioMessage(StringList const& arguments); String radioMessage(String const& argumentsString);
String clearRadioMessages(); String clearRadioMessages();
String clearCinematics(); String clearCinematics();
String startQuest(StringList const& arguments); String startQuest(String const& argumentsString);
String completeQuest(StringList const& arguments); String completeQuest(String const& argumentsString);
String failQuest(StringList const& arguments); String failQuest(String const& argumentsString);
String previewNewQuest(StringList const& arguments); String previewNewQuest(String const& argumentsString);
String previewQuestComplete(StringList const& arguments); String previewQuestComplete(String const& argumentsString);
String previewQuestFailed(StringList const& arguments); String previewQuestFailed(String const& argumentsString);
String clearScannedObjects(); String clearScannedObjects();
String playTime(); String playTime();
String deathCount(); String deathCount();
String cinema(StringList const& arguments); String cinema(String const& argumentsString);
String suicide(); String suicide();
String naked(); String naked();
String resetAchievements(); String resetAchievements();
String statistic(StringList const& arguments); String statistic(String const& argumentsString);
String giveEssentialItem(StringList const& arguments); String giveEssentialItem(String const& argumentsString);
String makeTechAvailable(StringList const& arguments); String makeTechAvailable(String const& argumentsString);
String enableTech(StringList const& arguments); String enableTech(String const& argumentsString);
String upgradeShip(StringList const& arguments); String upgradeShip(String const& argumentsString);
UniverseClientPtr m_universeClient; UniverseClientPtr m_universeClient;
CinematicPtr m_cinematicOverlay; CinematicPtr m_cinematicOverlay;
MainInterfacePaneManager* m_paneManager; MainInterfacePaneManager* m_paneManager;
CaseInsensitiveStringMap<function<String(StringList const&)>> m_builtinCommands; CaseInsensitiveStringMap<function<String(String const&)>> m_builtinCommands;
StringMap<StringList> m_macroCommands; StringMap<StringList> m_macroCommands;
ShellParser m_parser; ShellParser m_parser;
LuaBaseComponent m_scriptComponent; LuaBaseComponent m_scriptComponent;

View File

@ -106,6 +106,11 @@ LuaContext LuaRoot::createContext(StringList const& scriptPaths) {
} }
}); });
newContext.set("loadstring", m_luaEngine->createFunction([newContext](String const& source, Maybe<String> const& name, Maybe<LuaValue> const& env) -> LuaFunction {
String functionName = name ? strf("loadstring: {}", name) : "loadstring";
return newContext.engine().createFunctionFromSource(newContext.handleIndex(), source.utf8Ptr(), source.utf8Size(), functionName.utf8Ptr());
}));
for (auto const& scriptPath : scriptPaths) for (auto const& scriptPath : scriptPaths)
cache->loadContextScript(newContext, scriptPath); cache->loadContextScript(newContext, scriptPath);