From 560768bc61e884aa1cc2293be6415c19c2c56639 Mon Sep 17 00:00:00 2001 From: Matt Wagner Date: Sat, 24 Oct 2020 23:38:05 -0700 Subject: [PATCH] Initial commit --- .gitignore | 1 + mfile | 4 + src/aliases/galaxy-map/aliases.json | 6 + src/aliases/galaxy-map/gmap-refresh.lua | 5 + src/aliases/mapper/aliases.json | 7 + src/aliases/system-map/aliases.json | 6 + src/aliases/system-map/radar.lua | 4 + src/resources/space.jpg | Bin 0 -> 92053 bytes src/scripts/chat/chat.lua | 34 ++ src/scripts/chat/scripts.json | 5 + src/scripts/galaxy-map/galaxy-map.lua | 319 +++++++++++ src/scripts/galaxy-map/scripts.json | 5 + src/scripts/info-panel/info-panel.lua | 369 ++++++++++++ src/scripts/info-panel/scripts.json | 5 + src/scripts/layout/layout.lua | 140 +++++ src/scripts/layout/scripts.json | 5 + src/scripts/mapper/mapper.lua | 527 ++++++++++++++++++ src/scripts/mapper/scripts.json | 5 + src/scripts/msdp/msdp.lua | 45 ++ src/scripts/msdp/scripts.json | 5 + src/scripts/system-map/scripts.json | 5 + src/scripts/system-map/system-map.lua | 326 +++++++++++ src/triggers/chat/commnet-translated.lua | 6 + src/triggers/chat/commnet.lua | 6 + src/triggers/chat/triggers.json | 76 +++ src/triggers/galaxy-map/any-planet-line.lua | 5 + src/triggers/galaxy-map/gather-planet.lua | 7 + src/triggers/galaxy-map/gather-planets.lua | 7 + src/triggers/galaxy-map/no-datapad.lua | 3 + src/triggers/galaxy-map/no-resources.lua | 2 + src/triggers/galaxy-map/planet-coords.lua | 5 + src/triggers/galaxy-map/planet-empty-line.lua | 14 + src/triggers/galaxy-map/planet-end.lua | 11 + src/triggers/galaxy-map/planets-line.lua | 29 + src/triggers/galaxy-map/resource-price.lua | 7 + src/triggers/galaxy-map/showplanet-fail | 4 + src/triggers/galaxy-map/system-line.lua | 8 + src/triggers/galaxy-map/triggers.json | 211 +++++++ src/triggers/info-panel/triggers.json | 32 ++ src/triggers/layout/leave-ship.lua | 1 + src/triggers/layout/ship-launched.lua | 1 + src/triggers/layout/triggers.json | 20 + src/triggers/mapper/triggers.json | 12 + src/triggers/system-map/auto-radar.lua | 2 + src/triggers/system-map/radar-blank-line.lua | 5 + src/triggers/system-map/radar-item.lua | 32 ++ src/triggers/system-map/system-map-radar.lua | 22 + src/triggers/system-map/triggers.json | 45 ++ 48 files changed, 2401 insertions(+) create mode 100644 .gitignore create mode 100644 mfile create mode 100644 src/aliases/galaxy-map/aliases.json create mode 100644 src/aliases/galaxy-map/gmap-refresh.lua create mode 100644 src/aliases/mapper/aliases.json create mode 100644 src/aliases/system-map/aliases.json create mode 100644 src/aliases/system-map/radar.lua create mode 100644 src/resources/space.jpg create mode 100644 src/scripts/chat/chat.lua create mode 100644 src/scripts/chat/scripts.json create mode 100644 src/scripts/galaxy-map/galaxy-map.lua create mode 100644 src/scripts/galaxy-map/scripts.json create mode 100644 src/scripts/info-panel/info-panel.lua create mode 100644 src/scripts/info-panel/scripts.json create mode 100644 src/scripts/layout/layout.lua create mode 100644 src/scripts/layout/scripts.json create mode 100644 src/scripts/mapper/mapper.lua create mode 100644 src/scripts/mapper/scripts.json create mode 100644 src/scripts/msdp/msdp.lua create mode 100644 src/scripts/msdp/scripts.json create mode 100644 src/scripts/system-map/scripts.json create mode 100644 src/scripts/system-map/system-map.lua create mode 100644 src/triggers/chat/commnet-translated.lua create mode 100644 src/triggers/chat/commnet.lua create mode 100644 src/triggers/chat/triggers.json create mode 100644 src/triggers/galaxy-map/any-planet-line.lua create mode 100644 src/triggers/galaxy-map/gather-planet.lua create mode 100644 src/triggers/galaxy-map/gather-planets.lua create mode 100644 src/triggers/galaxy-map/no-datapad.lua create mode 100644 src/triggers/galaxy-map/no-resources.lua create mode 100644 src/triggers/galaxy-map/planet-coords.lua create mode 100644 src/triggers/galaxy-map/planet-empty-line.lua create mode 100644 src/triggers/galaxy-map/planet-end.lua create mode 100644 src/triggers/galaxy-map/planets-line.lua create mode 100644 src/triggers/galaxy-map/resource-price.lua create mode 100644 src/triggers/galaxy-map/showplanet-fail create mode 100644 src/triggers/galaxy-map/system-line.lua create mode 100644 src/triggers/galaxy-map/triggers.json create mode 100644 src/triggers/info-panel/triggers.json create mode 100644 src/triggers/layout/leave-ship.lua create mode 100644 src/triggers/layout/ship-launched.lua create mode 100644 src/triggers/layout/triggers.json create mode 100644 src/triggers/mapper/triggers.json create mode 100644 src/triggers/system-map/auto-radar.lua create mode 100644 src/triggers/system-map/radar-blank-line.lua create mode 100644 src/triggers/system-map/radar-item.lua create mode 100644 src/triggers/system-map/system-map-radar.lua create mode 100644 src/triggers/system-map/triggers.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/mfile b/mfile new file mode 100644 index 0000000..c56d3c3 --- /dev/null +++ b/mfile @@ -0,0 +1,4 @@ +{ + "package": "lotj-ui", + "version": "1.0" +} \ No newline at end of file diff --git a/src/aliases/galaxy-map/aliases.json b/src/aliases/galaxy-map/aliases.json new file mode 100644 index 0000000..1876b64 --- /dev/null +++ b/src/aliases/galaxy-map/aliases.json @@ -0,0 +1,6 @@ +[ + { + "name": "gmap-refresh", + "regex": "^gmap refresh$" + } +] \ No newline at end of file diff --git a/src/aliases/galaxy-map/gmap-refresh.lua b/src/aliases/galaxy-map/gmap-refresh.lua new file mode 100644 index 0000000..4f9f085 --- /dev/null +++ b/src/aliases/galaxy-map/gmap-refresh.lua @@ -0,0 +1,5 @@ +lotj.galaxyMap.resetData() + +enableTrigger("galaxy-map-refresh") +send("starsystems", false) +send("planets", false) diff --git a/src/aliases/mapper/aliases.json b/src/aliases/mapper/aliases.json new file mode 100644 index 0000000..31cf83b --- /dev/null +++ b/src/aliases/mapper/aliases.json @@ -0,0 +1,7 @@ +[ + { + "name": "map", + "regex": "^map( ?.*)$", + "script": "lotj.mapper.mapCommand(matches[2])" + } +] \ No newline at end of file diff --git a/src/aliases/system-map/aliases.json b/src/aliases/system-map/aliases.json new file mode 100644 index 0000000..18afe80 --- /dev/null +++ b/src/aliases/system-map/aliases.json @@ -0,0 +1,6 @@ +[ + { + "name": "radar", + "regex": "^r(a(d(a(r)?)?)?)?$" + } +] \ No newline at end of file diff --git a/src/aliases/system-map/radar.lua b/src/aliases/system-map/radar.lua new file mode 100644 index 0000000..c04d782 --- /dev/null +++ b/src/aliases/system-map/radar.lua @@ -0,0 +1,4 @@ +-- Intercept the radar command to enable our radar scraping triggers. +-- We don't want them to potentially fire on unrelated lines. +enableTrigger("system-map-radar") +send("radar", false) diff --git a/src/resources/space.jpg b/src/resources/space.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b5dd7fa0e9840deaa9ff3022a1c02cb1a6f6d5b3 GIT binary patch literal 92053 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

*N8w^Lcvys-q)>AW0P_TTrt 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 literal 0 HcmV?d00001 diff --git a/src/scripts/chat/chat.lua b/src/scripts/chat/chat.lua new file mode 100644 index 0000000..89f9709 --- /dev/null +++ b/src/scripts/chat/chat.lua @@ -0,0 +1,34 @@ +lotj = lotj or {} +lotj.chat = lotj.chat or {} + +registerAnonymousEventHandler("lotjUICreated", function() + for keyword, contentsContainer in pairs(lotj.layout.lowerRightTabData.contents) do + lotj.chat[keyword] = Geyser.MiniConsole:new({ + x = "1%", y = "1%", + width = "98%", + height = "98%", + autoWrap = false, + color = "black", + scrollBar = true, + fontSize = 14, + }, contentsContainer) + + -- Set the wrap at a few characters short of the full width to avoid the scroll bar showing over text + local charsPerLine = lotj.chat[keyword]:getColumnCount()-3 + lotj.chat[keyword]:setWrap(charsPerLine) + registerAnonymousEventHandler("sysWindowResizeEvent", function() + local charsPerLine = lotj.chat[keyword]:getColumnCount()-3 + lotj.chat[keyword]:setWrap(charsPerLine) + end) + end +end) + +function lotj.chat.routeMessage(type) + selectCurrentLine() + copy() + lotj.chat[type]:cecho(""..getTime(true, "hh:mm:ss").." ") + lotj.chat[type]:appendBuffer() + + lotj.chat.all:cecho(""..getTime(true, "hh:mm:ss").." ") + lotj.chat.all:appendBuffer() +end diff --git a/src/scripts/chat/scripts.json b/src/scripts/chat/scripts.json new file mode 100644 index 0000000..5056dad --- /dev/null +++ b/src/scripts/chat/scripts.json @@ -0,0 +1,5 @@ +[ + { + "name": "chat" + } +] diff --git a/src/scripts/galaxy-map/galaxy-map.lua b/src/scripts/galaxy-map/galaxy-map.lua new file mode 100644 index 0000000..e321968 --- /dev/null +++ b/src/scripts/galaxy-map/galaxy-map.lua @@ -0,0 +1,319 @@ +lotj = lotj or {} +lotj.galaxyMap = lotj.galaxyMap or { + data = { + labelAsPlanets = true, + systems = {}, + planets = {}, + govToColor = { + ["Neutral Government"] = "#AAAAAA", + }, + } +} + +local dataFileName = getMudletHomeDir().."/galaxyMap" +registerAnonymousEventHandler("lotjUICreated", function() + lotj.galaxyMap.container = Geyser.Label:new({ + name = "galaxy", + x = 0, y = 0, + width = "100%", + height = "100%", + }, lotj.layout.upperRightTabData.contents["galaxy"]) + lotj.galaxyMap.container:setBackgroundImage(getMudletHomeDir().."/@PKGNAME@/space.jpg") + + lotj.galaxyMap.refreshButton = Geyser.Label:new({ + name = "galaxyMapRefresh", + x = "20%", y = "35%", + width = "60%", height = 40, + }, lotj.galaxyMap.container) + lotj.galaxyMap.refreshButton:setStyleSheet([[ + background-color: grey; + border: 1px solid white; + ]]) + lotj.galaxyMap.refreshButton:echo("Click here to populate this map.", "white", "c14") + lotj.galaxyMap.refreshButton:setClickCallback(function() + expandAlias("gmap refresh", false) + end) + + disableTrigger("galaxy-map-refresh") + if io.exists(dataFileName) then + table.load(dataFileName, lotj.galaxyMap.data) + lotj.galaxyMap.log("Loaded map data.") + lotj.galaxyMap.drawSystems() + end + + registerAnonymousEventHandler("msdp.SHIPGALY", lotj.galaxyMap.setShipGalCoords) +end) + + +function lotj.galaxyMap.log(text) + cecho("[LOTJ Galaxy Map] "..text.."\n") +end + +function lotj.galaxyMap.setShipGalCoords() + -- Assume "0" in both values means we don't have valid coordinates and leave them what they were. + -- TODO: Find a way to support a system actually located at 0, 0 + if msdp.SHIPGALX ~= "0" or msdp.SHIPGALY ~= "0" then + lotj.galaxyMap.currentX = tonumber(msdp.SHIPGALX) + lotj.galaxyMap.currentY = tonumber(msdp.SHIPGALY) + lotj.galaxyMap.drawSystems() + end +end + +local function container() + return lotj.galaxyMap.container +end + +-- Fire off any showplanet commands we still need to run to load data for all known planets +function lotj.galaxyMap.enqueuePendingRefreshCommands() + for planet in pairs(gatherPlanetsState.pendingBasic) do + send("showplanet \""..planet.."\"", false) + gatherPlanetsState.pendingCommands = gatherPlanetsState.pendingCommands + 1 + end + for planet in pairs(gatherPlanetsState.pendingResources) do + send("showplanet \""..planet.."\" resources", false) + gatherPlanetsState.pendingCommands = gatherPlanetsState.pendingCommands + 1 + end + + -- We didn't have to retry any, so we're done getting info. + if gatherPlanetsState.pendingCommands == 0 then + disableTrigger("galaxy-map-refresh") + return + end + + echo("\n") + lotj.galaxyMap.log("Enqueueing "..gatherPlanetsState.pendingCommands.." showplanet commands.") +end + +function lotj.galaxyMap.resetData() + lotj.galaxyMap.data.planets = {} + lotj.galaxyMap.data.systems = {} +end + +local govColorIdx = 1 +local govColorList = {} +table.insert(govColorList, "#E69F00") +table.insert(govColorList, "#56B4E9") +table.insert(govColorList, "#009E73") +table.insert(govColorList, "#F0E442") +table.insert(govColorList, "#D55E00") +table.insert(govColorList, "#CC79A7") + +function lotj.galaxyMap.recordSystem(name, x, y) + lotj.galaxyMap.data.systems = lotj.galaxyMap.data.systems or {} + lotj.galaxyMap.data.systems[name] = { + name = name, + planets = {}, + gov = "Neutral Government", + x = x, + y = y, + } + table.save(dataFileName, lotj.galaxyMap.data) + + lotj.galaxyMap.drawSystems() +end + +function lotj.galaxyMap.recordPlanet(planetData) + if not lotj.galaxyMap.data.planets[planetData.name] then + lotj.galaxyMap.data.planets[planetData.name] = { + name = planetData.name, + gov = planetData.gov, + system = planetData.system, + } + + local system = lotj.galaxyMap.data.systems[planetData.system] + if system ~= nil then + system.gov = planetData.gov + table.insert(system.planets, planetData.name) + else + lotj.galaxyMap.log("Unable to find system "..planetData.system.." for planet "..planetData.name) + end + + if lotj.galaxyMap.data.govToColor[planetData.gov] == nil then + govColorIdx = govColorIdx+1 + lotj.galaxyMap.data.govToColor[planetData.gov] = govColorList[govColorIdx] + end + end + + if planetData.coords ~= nil then + lotj.galaxyMap.data.planets[planetData.name].coords = planetData.coords + end + if planetData.freeport ~= nil then + lotj.galaxyMap.data.planets[planetData.name].freeport = planetData.freeport + end + if planetData.taxRate ~= nil then + lotj.galaxyMap.data.planets[planetData.name].taxRate = planetData.taxRate + end + if planetData.resources ~= nil then + lotj.galaxyMap.data.planets[planetData.name].resources = planetData.resources + end + + table.save(dataFileName, lotj.galaxyMap.data) + + lotj.galaxyMap.drawSystems() +end + +local systemPointSize = 14 +local function stylePoint(point, gov, currentSystem) + local backgroundColor = lotj.galaxyMap.data.govToColor[gov] or "#AAAAAA" + local borderStyle = "" + if currentSystem then + borderStyle = "border: 2px solid red;" + end + point:setStyleSheet([[ + border-radius: ]]..math.floor(systemPointSize/2)..[[px; + background-color: ]]..backgroundColor..[[; + ]]..borderStyle..[[ + ]]) +end + +local function systemDisplayName(system) + if lotj.galaxyMap.data.labelAsPlanets and #(system.planets or {}) > 0 then + local labelString = "" + for _, planet in ipairs(system.planets) do + if #labelString > 0 then + labelString = labelString..", " + end + labelString = labelString..planet + end + return labelString + else + -- Cut off common extra words from the system name to keep labels short + local labelString = system.name + labelString = string.gsub(labelString, " System$", "") + labelString = string.gsub(labelString, " Sector$", "") + return labelString + end +end + +function lotj.galaxyMap.drawSystems() + local minX, _, _, maxY = lotj.galaxyMap.coordRange() + local xOffset, yOffset, pxPerCoord = lotj.galaxyMap.calculateSizing() + + lotj.galaxyMap.systemPoints = lotj.galaxyMap.systemPoints or {} + for _, point in pairs(lotj.galaxyMap.systemPoints) do + point:hide() + end + + lotj.galaxyMap.systemLabels = lotj.galaxyMap.systemLabels or {} + for _, label in pairs(lotj.galaxyMap.systemLabels) do + label:hide() + end + + + local foundCurrentLocation = false + local systemsToDraw = {} + for _, system in pairs(lotj.galaxyMap.data.systems) do + table.insert(systemsToDraw, system) + if system.x == lotj.galaxyMap.currentX and system.y == lotj.galaxyMap.currentY then + foundCurrentLocation = true + end + end + if not foundCurrentLocation and lotj.galaxyMap.currentX and lotj.galaxyMap.currentY then + table.insert(systemsToDraw, { + name = "Current", + x = lotj.galaxyMap.currentX, + y = lotj.galaxyMap.currentY + }) + end + + -- Hide or show the refresh button accordingly, based on whether we have any data. + if #systemsToDraw > 0 then + lotj.galaxyMap.refreshButton:hide() + else + lotj.galaxyMap.refreshButton:show() + end + + for _, system in ipairs(systemsToDraw) do + local point = lotj.galaxyMap.systemPoints[system.name] + if point == nil then + point = Geyser.Label:new({width=systemPointSize, height=systemPointSize}, container()) + stylePoint(point, system.gov, false) + lotj.galaxyMap.systemPoints[system.name] = point + else + point:show() + end + + local label = lotj.galaxyMap.systemLabels[system.name] + if label == nil then + label = Geyser.Label:new({ + height = 16, width = 100, + fillBg = 0, + }, container()) + + lotj.galaxyMap.systemLabels[system.name] = label + else + label:show() + end + label:echo(systemDisplayName(system), "white", "12c") + + local sysX = math.floor(xOffset + (system.x-minX)*pxPerCoord - systemPointSize/2 + 0.5) + local sysY = math.floor(yOffset + (maxY-system.y)*pxPerCoord - systemPointSize/2 + 0.5) + point:move(sysX, sysY) + if system.x == lotj.galaxyMap.currentX and system.y == lotj.galaxyMap.currentY then + stylePoint(point, system.gov, true) + else + stylePoint(point, system.gov, false) + end + + label:move(math.max(sysX-45, 0), sysY+systemPointSize) + end +end + +-- Returns X starting point, Y starting point, and pixels per coordinate +function lotj.galaxyMap.calculateSizing() + local minX, maxX, minY, maxY = lotj.galaxyMap.coordRange() + local xRange = maxX-minX + local yRange = maxY-minY + local contWidth = container():get_width() + local contHeight = container():get_height() + + -- Determine whether the map would be limited by height or width first. + local mapWidth = nil + local mapHeight = nil + local pxPerCoord = nil + local pxHeightIfLimitedByWidth = (contWidth/xRange)*yRange + local pxWidthIfLimitedByHeight = (contHeight/yRange)*xRange + if pxHeightIfLimitedByWidth <= contHeight then + -- Width was the limiting factor, so use it to determine sizing + mapWidth = contWidth + mapHeight = pxHeightIfLimitedByWidth + pxPerCoord = contWidth/xRange + elseif pxWidthIfLimitedByHeight <= contWidth then + -- Width was the limiting factor, so use it to determine sizing + mapWidth = pxWidthIfLimitedByHeight + mapHeight = contHeight + pxPerCoord = contHeight/yRange + else + echo("Unable to determine appropriate galaxy map dimensions. This is a script bug.\n") + end + + local mapAnchorX = (contWidth-mapWidth)/2 + local mapAnchorY = (contHeight-mapHeight)/2 + + return mapAnchorX, mapAnchorY, pxPerCoord +end + +function lotj.galaxyMap.coordRange() + local minX = 0 + local maxX = 0 + local minY = 0 + local maxY = 0 + + for _, system in pairs(lotj.galaxyMap.data.systems) do + if minX > system.x then + minX = system.x + end + if maxX < system.x then + maxX = system.x + end + if minY > system.y then + minY = system.y + end + if maxY < system.y then + maxY = system.y + end + end + + -- Pad all values by 10 to ensure points are displayed reasonably. + return minX-10, maxX+10, minY-10, maxY+10 +end diff --git a/src/scripts/galaxy-map/scripts.json b/src/scripts/galaxy-map/scripts.json new file mode 100644 index 0000000..833f062 --- /dev/null +++ b/src/scripts/galaxy-map/scripts.json @@ -0,0 +1,5 @@ +[ + { + "name": "galaxy-map" + } +] diff --git a/src/scripts/info-panel/info-panel.lua b/src/scripts/info-panel/info-panel.lua new file mode 100644 index 0000000..977cbe8 --- /dev/null +++ b/src/scripts/info-panel/info-panel.lua @@ -0,0 +1,369 @@ +lotj = lotj or {} +lotj.infoPanel = lotj.infoPanel or {} + + +registerAnonymousEventHandler("lotjUICreated", function() + local basicStatsContainer = Geyser.Label:new({ + h_stretch_factor = 0.9 + }, lotj.layout.lowerInfoPanel) + local combatContainer = Geyser.Label:new({ + h_stretch_factor = 0.9 + }, lotj.layout.lowerInfoPanel) + local chatContainer = Geyser.Label:new({ + h_stretch_factor = 1 + }, lotj.layout.lowerInfoPanel) + local spaceContainer = Geyser.Label:new({ + h_stretch_factor = 2.2 + }, lotj.layout.lowerInfoPanel) + + lotj.infoPanel.createBasicStats(basicStatsContainer) + lotj.infoPanel.createOpponentStats(combatContainer) + lotj.infoPanel.createChatInfo(chatContainer) + lotj.infoPanel.createSpaceStats(spaceContainer) +end) + + +-- Utility functions +local function gaugeFrontStyle(step1, step2, step3, step4, step5) + return [[ + background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 ]]..step1..[[, stop: 0.1 ]]..step2..[[, stop: 0.49 ]]..step3..[[, stop: 0.5 ]]..step4..[[, stop: 1 ]]..step5..[[); + border-top: 1px black solid; + border-left: 1px black solid; + border-bottom: 1px black solid; + padding: 3px; + ]] +end + +local function gaugeBackStyle(step1, step2, step3, step4, step5) + return [[ + background-color: QLinearGradient( x1: 0, y1: 0, x2: 0, y2: 1, stop: 0 ]]..step1..[[, stop: 0.1 ]]..step2..[[, stop: 0.49 ]]..step3..[[, stop: 0.5 ]]..step4..[[, stop: 1 ]]..step5..[[); + border-width: 1px; + border-color: black; + border-style: solid; + padding: 3px; + ]] +end + +local function styleGaugeText(gauge, fontSize) + gauge.text:setStyleSheet([[ + padding-right: 10px; + ]]) + gauge:setAlignment("r") + gauge:setFontSize(fontSize) +end + +-- Wires up MSDP subscriptions for a gauge. +-- statName is the short version of the stat name to show after the value (mv, hp, etc) +local function wireGaugeUpdate(gauge, valueVarName, maxVarName, statName) + local function doUpdate() + local current = tonumber(msdp[valueVarName] or "0") + local max = tonumber(msdp[maxVarName] or "0") + if max > 0 then + gauge:setValue(current, max, current.."/"..max.." "..statName) + else + gauge:setValue(0, 1, "") + end + end + registerAnonymousEventHandler("msdp."..valueVarName, doUpdate) + registerAnonymousEventHandler("msdp."..maxVarName, doUpdate) +end + + +function lotj.infoPanel.createBasicStats(container) + -- Health gauge + local healthGauge = Geyser.Gauge:new({ + x="5%", y=4, + width="90%", height=16, + }, container) + healthGauge.front:setStyleSheet(gaugeFrontStyle("#f04141", "#ef2929", "#cc0000", "#a40000", "#cc0000")) + healthGauge.back:setStyleSheet(gaugeBackStyle("#3f1111", "#3f0707", "#330000", "#220000", "#330000")) + styleGaugeText(healthGauge, 12) + wireGaugeUpdate(healthGauge, "HEALTH", "HEALTHMAX", "hp") + + local wimpyBar = Geyser.Label:new({ + x=0, y=0, + width=2, height="100%", + }, healthGauge.front) + wimpyBar:setStyleSheet([[ + background-color: yellow; + ]]) + + registerAnonymousEventHandler("msdp.WIMPY", function() + local health = tonumber(msdp.HEALTH or 0) + local healthMax = tonumber(msdp.HEALTHMAX or 0) + local wimpy = tonumber(msdp.WIMPY or 0) + if healthMax > 0 then + if wimpy > 0 and health > 0 and wimpy < health then + wimpyBar:show() + wimpyBar:move(math.floor(wimpy*100/health).."%", nil) + else + wimpyBar:hide() + end + end + end) + + -- Movement gauge + local movementGauge = Geyser.Gauge:new({ + x="5%", y=23, + width="90%", height=16, + }, container) + movementGauge.front:setStyleSheet(gaugeFrontStyle("#41f041", "#29ef29", "#00cc00", "#00a400", "#00cc00")) + movementGauge.back:setStyleSheet(gaugeBackStyle("#113f11", "#073f07", "#003300", "#002200", "#003300")) + styleGaugeText(movementGauge, 12) + wireGaugeUpdate(movementGauge, "MOVEMENT", "MOVEMENTMAX", "mv") + + -- Mana gauge (will be hidden later if we do not have mana) + local manaGauge = Geyser.Gauge:new({ + x="5%", y=42, + width="90%", height=16, + }, container) + manaGauge.front:setStyleSheet(gaugeFrontStyle("#4141f0", "#2929ef", "#0000cc", "#0000a4", "#0000cc")) + manaGauge.back:setStyleSheet(gaugeBackStyle("#11113f", "#07073f", "#000033", "#000022", "#000011")) + styleGaugeText(manaGauge, 12) + wireGaugeUpdate(manaGauge, "MANA", "MANAMAX", "mn") + + registerAnonymousEventHandler("msdp.MANAMAX", function() + local manaMax = tonumber(msdp.MANAMAX or 0) + if manaMax > 0 then + healthGauge:move(nil, 4) + healthGauge:resize(nil, 16) + healthGauge:setFontSize("12") + + movementGauge:move(nil, 23) + movementGauge:resize(nil, 16) + movementGauge:setFontSize("12") + + manaGauge:show() + manaGauge:move(nil, 42) + manaGauge:resize(nil, 16) + manaGauge:setFontSize("12") + else + healthGauge:move(nil, 6) + healthGauge:resize(nil, 22) + healthGauge:setFontSize("13") + + movementGauge:move(nil, 32) + movementGauge:resize(nil, 22) + movementGauge:setFontSize("13") + + manaGauge:hide() + end + end) +end + + +function lotj.infoPanel.createOpponentStats(container) + -- Opponent health gauge + local opponentGauge = Geyser.Gauge:new({ + x="5%", y=6, + width="90%", height=48, + }, container) + opponentGauge.front:setStyleSheet(gaugeFrontStyle("#bd7833", "#bd6e20", "#994c00", "#703800", "#994c00")) + opponentGauge.back:setStyleSheet(gaugeBackStyle("#442511", "#441d08", "#331100", "#200900", "#331100")) + opponentGauge.text:setStyleSheet("padding: 10px;") + opponentGauge:setAlignment("c") + opponentGauge:setFontSize("12") + + local function update() + local opponentName = string.gsub(msdp.OPPONENTNAME or "", "&.", "") + -- Opponent name seems to always be empty string even when fighting, so fall back to something reasonable + if opponentName == "" then + opponentName = "Current target" + end + local opponentHealth = tonumber(msdp.OPPONENTHEALTH or "0") + local opponentHealthMax = tonumber(msdp.OPPONENTHEALTHMAX or "0") + if opponentHealth > 0 then + opponentGauge:setValue(opponentHealth, opponentHealthMax, opponentName.."
"..opponentHealth.."%") + else + opponentGauge:setValue(0, 1, "Not fighting") + end + end + registerAnonymousEventHandler("msdp.OPPONENTNAME", update) + registerAnonymousEventHandler("msdp.OPPONENTHEALTH", update) + registerAnonymousEventHandler("msdp.OPPONENTHEALTHMAX", update) +end + + +function lotj.infoPanel.createChatInfo(container) + -- Commnet channel/code + local commnetLabel = Geyser.Label:new({ + x="3%", y=6, + width="20%", height=24, + }, container) + commnetLabel:echo("Comm:", nil, "rb13") + local commnetInfo = Geyser.Label:new({ + x = "25%", y = 6, + width = "75%", height = 24 + }, container) + + local function updateCommnet() + local commChannel = msdp.COMMCHANNEL or "0" + local commEncrypt = msdp.COMMENCRYPT or "0" + if commEncrypt == "0" then + commnetInfo:echo(commChannel, nil, "l13") + else + commnetInfo:echo(commChannel.." (E "..commEncrypt..")", nil, "l13") + end + end + registerAnonymousEventHandler("msdp.COMMCHANNEL", updateCommnet) + registerAnonymousEventHandler("msdp.COMMENCRYPT", updateCommnet) + + -- OOC meter + local oocLabel = Geyser.Label:new({ + x="3%", y=32, + width="20%", height=24, + }, container) + oocLabel:echo("OOC:", nil, "rb13") + local oocGauge = Geyser.Gauge:new({ + x="25%", y=32, + width="40%", height=20, + }, container) + oocGauge.front:setStyleSheet(gaugeFrontStyle("#31d0d0", "#22cfcf", "#00b2b2", "#009494", "#00b2b2")) + oocGauge.back:setStyleSheet(gaugeBackStyle("#113f3f", "#073f3f", "#003333", "#002222", "#001111")) + + registerAnonymousEventHandler("msdp.OOCLIMIT", function() + local oocLeft = tonumber(msdp.OOCLIMIT or 0) + local oocMax = 6 + oocGauge:setValue(oocLeft, oocMax) + end) +end + + +function lotj.infoPanel.createSpaceStats(container) + local energyGauge = Geyser.Gauge:new({ + x="3%", y=4, + width="30%", height=16, + }, container) + energyGauge.front:setStyleSheet(gaugeFrontStyle("#7a7a7a", "#777777", "#656565", "#505050", "#656565")) + energyGauge.back:setStyleSheet(gaugeBackStyle("#383838", "#303030", "#222222", "#151515", "#222222")) + styleGaugeText(energyGauge, 12) + wireGaugeUpdate(energyGauge, "SHIPENERGY", "SHIPMAXENERGY", "en") + + local hullGauge = Geyser.Gauge:new({ + x="3%", y=23, + width="30%", height=16, + }, container) + hullGauge.front:setStyleSheet(gaugeFrontStyle("#bd7833", "#bd6e20", "#994c00", "#703800", "#994c00")) + hullGauge.back:setStyleSheet(gaugeBackStyle("#442511", "#441d08", "#331100", "#200900", "#331100")) + styleGaugeText(hullGauge, 12) + wireGaugeUpdate(hullGauge, "SHIPHULL", "SHIPMAXHULL", "hl") + + local shieldGauge = Geyser.Gauge:new({ + x="3%", y=42, + width="30%", height=16, + }, container) + shieldGauge.front:setStyleSheet(gaugeFrontStyle("#31d0d0", "#22cfcf", "#00b2b2", "#009494", "#00b2b2")) + shieldGauge.back:setStyleSheet(gaugeBackStyle("#113f3f", "#073f3f", "#003333", "#002222", "#001111")) + styleGaugeText(shieldGauge, 12) + wireGaugeUpdate(shieldGauge, "SHIPSHIELD", "SHIPMAXSHIELD", "sh") + + + -- Piloting indicator + local pilotLabel = Geyser.Label:new({ + x="35%", y=6, + width="13%", height=24 + }, container) + pilotLabel:echo("Pilot:", nil, "lb12") + + local pilotBoxCont = Geyser.Label:new({ + x="48%", y=10, + width="8%", height=16 + }, container) + local pilotBox = Geyser.Label:new({ + x=2, y=0, + width=16, height=16 + }, pilotBoxCont) + + registerAnonymousEventHandler("msdp.PILOTING", function() + local piloting = tonumber(msdp.PILOTING or "0") + if piloting == 1 then + pilotBox:setStyleSheet("background-color: #29efef; border: 2px solid #eeeeee;") + else + pilotBox:setStyleSheet("background-color: #073f3f; border: 2px solid #eeeeee;") + end + end) + + + local speedGauge = Geyser.Label:new({ + x="56%", y=6, + width="19%", height=24, + }, container) + + local function updateSpeed() + local speed = tonumber(msdp.SHIPSPEED or "0") + local maxSpeed = tonumber(msdp.SHIPMAXSPEED or "0") + speedGauge:echo("Sp: "..speed.."/"..maxSpeed, nil, "l12") + end + registerAnonymousEventHandler("msdp.SHIPSPEED", updateSpeed) + registerAnonymousEventHandler("msdp.SHIPMAXSPEED", updateSpeed) + + + local coordsInfo = Geyser.Label:new({ + x="35%", y=32, + width="40%", height=24, + }, container) + + local function updateCoords() + local shipX = tonumber(msdp.SHIPSYSX or "0") + local shipY = tonumber(msdp.SHIPSYSY or "0") + local shipZ = tonumber(msdp.SHIPSYSZ or "0") + coordsInfo:echo("Coords: "..shipX.." "..shipY.." "..shipZ, nil, "l12") + end + registerAnonymousEventHandler("msdp.SHIPSYSX", updateCoords) + registerAnonymousEventHandler("msdp.SHIPSYSY", updateCoords) + registerAnonymousEventHandler("msdp.SHIPSYSZ", updateCoords) + + lotj.infoPanel.spaceTickCounter = Geyser.Label:new({ + x="77%", y=6, + width="20%", height=24, + }, container) + + lotj.infoPanel.chaffIndicator = Geyser.Label:new({ + x="77%", y=32, + width="20%", height=24, + }, container) + lotj.infoPanel.chaffIndicator:echo("[Chaff]", "yellow", "c13b") + lotj.infoPanel.chaffIndicator:hide() +end + +-- Sets up timers to refresh the space tick counter +function lotj.infoPanel.markSpaceTick() + for _, timerId in ipairs(lotj.infoPanel.spaceTickTimers or {}) do + killTimer(timerId) + end + + lotj.infoPanel.spaceTickCounter:show() + lotj.infoPanel.spaceTickTimers = {} + for i = 0,20,1 do + local timerId = tempTimer(i, function() + lotj.infoPanel.spaceTickCounter:echo("Tick: "..20-i, nil, "c13") + end) + table.insert(lotj.infoPanel.spaceTickTimers, timerId) + end + + -- A few seconds after the next tick should happen, hide the counter. + -- This will be canceled if we see another tick before then. + local timerId = tempTimer(23, function() + lotj.infoPanel.spaceTickCounter:hide() + end) + table.insert(lotj.infoPanel.spaceTickTimers, timerId) +end + +function lotj.infoPanel.markChaff() + lotj.infoPanel.clearChaff() + lotj.infoPanel.chaffIndicator:show() + + -- In case we miss the "chaff cleared" message somehow, set a 20 second timer to get rid of this + lotj.infoPanel.chaffTimer = tempTimer(20, function() + lotj.infoPanel.clearChaff() + end) +end + +function lotj.infoPanel.clearChaff() + if lotj.infoPanel.chaffTimer ~= nil then + killTimer(lotj.infoPanel.chaffTimer) + lotj.infoPanel.chaffTimer = nil + end + + lotj.infoPanel.chaffIndicator:hide() +end diff --git a/src/scripts/info-panel/scripts.json b/src/scripts/info-panel/scripts.json new file mode 100644 index 0000000..0610209 --- /dev/null +++ b/src/scripts/info-panel/scripts.json @@ -0,0 +1,5 @@ +[ + { + "name": "info-panel" + } +] diff --git a/src/scripts/layout/layout.lua b/src/scripts/layout/layout.lua new file mode 100644 index 0000000..7e01a8c --- /dev/null +++ b/src/scripts/layout/layout.lua @@ -0,0 +1,140 @@ +lotj = lotj or {} +lotj.layout = lotj.layout or {} + +local rightPanelWidthPct = 40 +local upperRightHeightPct = 50 + +local inactiveTabStyle = [[ + background-color: #333333; + border: 1px solid #00aaaa; + margin: 3px 3px 0px 3px; + font-family: "Bitstream Vera Sans Mono"; +]] + +local activeTabStyle = [[ + background-color: #336666; + border: 1px solid #00aaaa; + border-bottom: none; + margin: 3px 3px 0px 3px; + font-family: "Bitstream Vera Sans Mono"; +]] + + +local function createTabbedPanel(tabData, container, tabList) + tabData.tabs = {} + tabData.contents = {} + + local tabContainer = Geyser.HBox:new({ + x = "2%", y = 0, + width = "96%", height = 30, + }, container) + + local contentsContainer = Geyser.Label:new({ + x = 0, y = 30, + width = "100%", + }, container) + + lotj.layout.resizeTabContents(container, tabContainer, contentsContainer) + registerAnonymousEventHandler("sysWindowResizeEvent", function() + lotj.layout.resizeTabContents(container, tabContainer, contentsContainer) + end) + + for _, tabInfo in ipairs(tabList) do + local keyword = tabInfo.keyword + local label = tabInfo.label + + tabData.tabs[keyword] = Geyser.Label:new({}, tabContainer) + tabData.tabs[keyword]:setClickCallback("lotj.layout.selectTab", tabData, keyword) + tabData.tabs[keyword]:setFontSize(12) + tabData.tabs[keyword]:echo("

"..label) + + tabData.contents[keyword] = Geyser.Label:new({ + x = 0, y = 0, + width = "100%", + height = "100%", + }, contentsContainer) + end +end + +function lotj.layout.selectTab(tabData, tabName) + for _, tab in pairs(tabData.tabs) do + tab:setStyleSheet(inactiveTabStyle) + tab:setBold(false) + end + for _, contents in pairs(tabData.contents) do + contents:hide() + end + + tabData.tabs[tabName]:setStyleSheet(activeTabStyle) + tabData.tabs[tabName]:setBold(true) + tabData.contents[tabName]:show() +end + +function lotj.layout.resizeTabContents(parentContainer, tabContainer, contentsContainer) + local newHeight = parentContainer:get_height()-tabContainer:get_height() + contentsContainer:resize(nil, newHeight) +end + + +registerAnonymousEventHandler("sysLoadEvent", function() + local rightPanel = Geyser.Container:new({ + width = rightPanelWidthPct.."%", + x = (100-rightPanelWidthPct).."%", + y = 0, height = "100%", + }) + registerAnonymousEventHandler("sysWindowResizeEvent", function() + local newBorder = math.floor(rightPanel:get_width()) + if getBorderRight() ~= newBorder then + setBorderRight(newBorder) + end + end) + + + -- Upper-right pane, for maps + local upperContainer = Geyser.Container:new({ + x = 0, y = 0, + width = "100%", + height = upperRightHeightPct.."%", + }, rightPanel) + + local upperTabList = {} + table.insert(upperTabList, {keyword = "map", label = "Map"}) + table.insert(upperTabList, {keyword = "system", label = "System"}) + table.insert(upperTabList, {keyword = "galaxy", label = "Galaxy"}) + + lotj.layout.upperRightTabData = {} + createTabbedPanel(lotj.layout.upperRightTabData, upperContainer, upperTabList) + + + -- Lower-right panel, for chat history + local lowerContainer = Geyser.Container:new({ + x = 0, y = upperRightHeightPct.."%", + width = "100%", + height = (100-upperRightHeightPct).."%", + }, rightPanel) + + local lowerTabList = {} + table.insert(lowerTabList, {keyword = "all", label = "All"}) + table.insert(lowerTabList, {keyword = "commnet", label = "CommNet"}) + table.insert(lowerTabList, {keyword = "clan", label = "Clan"}) + table.insert(lowerTabList, {keyword = "ooc", label = "OOC"}) + table.insert(lowerTabList, {keyword = "tell", label = "Tell"}) + table.insert(lowerTabList, {keyword = "imm", label = "Imm"}) + + lotj.layout.lowerRightTabData = {} + createTabbedPanel(lotj.layout.lowerRightTabData, lowerContainer, lowerTabList) + + + -- Lower info panel, for prompt hp/move gauges and other basic status + lotj.layout.lowerInfoPanel = Geyser.HBox:new({ + x = 0, y = -60, + width = (100-rightPanelWidthPct).."%", + height = 60, + }) + setBorderBottom(60) + + raiseEvent("lotjUICreated") + + lotj.layout.selectTab(lotj.layout.upperRightTabData, "map") + lotj.layout.selectTab(lotj.layout.lowerRightTabData, "all") +end) diff --git a/src/scripts/layout/scripts.json b/src/scripts/layout/scripts.json new file mode 100644 index 0000000..04377f2 --- /dev/null +++ b/src/scripts/layout/scripts.json @@ -0,0 +1,5 @@ +[ + { + "name": "layout" + } +] diff --git a/src/scripts/mapper/mapper.lua b/src/scripts/mapper/mapper.lua new file mode 100644 index 0000000..c6dd3df --- /dev/null +++ b/src/scripts/mapper/mapper.lua @@ -0,0 +1,527 @@ +mudlet = mudlet or {}; mudlet.mapper_script = true +lotj = lotj or {} +lotj.mapper = lotj.mapper or {} + + +local dirs = {} +-- The order of these is important. The indices of the directions must match +-- https://github.com/Mudlet/Mudlet/blob/9c13f8f946f5b82c0c2e817dab5f42588cee17e0/src/TRoom.h#L38 +table.insert(dirs, {short="n", long="north", rev="s", xyzDiff = { 0, 1, 0}}) +table.insert(dirs, {short="ne", long="northeast", rev="sw", xyzDiff = { 1, 1, 0}}) +table.insert(dirs, {short="nw", long="northwest", rev="se", xyzDiff = {-1, 1, 0}}) +table.insert(dirs, {short="e", long="east", rev="w", xyzDiff = { 1, 0, 0}}) +table.insert(dirs, {short="w", long="west", rev="e", xyzDiff = {-1, 0, 0}}) +table.insert(dirs, {short="s", long="south", rev="n", xyzDiff = { 0,-1, 0}}) +table.insert(dirs, {short="se", long="southeast", rev="nw", xyzDiff = { 1,-1, 0}}) +table.insert(dirs, {short="sw", long="southwest", rev="ne", xyzDiff = {-1,-1, 0}}) +table.insert(dirs, {short="u", long="up", rev="d", xyzDiff = { 0, 0, 1}}) +table.insert(dirs, {short="d", long="down", rev="u", xyzDiff = { 0, 0,-1}}) + +-- Given a direction short or long name, or a direction number, return an object representing it. +local function dirObj(arg) + if dirs[arg] ~= nil then + return dirs[arg] + end + + for _, dir in ipairs(dirs) do + if arg == dir.short or arg == dir.long then + return dir + end + end + return nil +end + +-- Given a direction short or long name, or a direction number, return an object representing its opposite +local function revDirObj(arg) + local dir = dirObj(arg) + if dir ~= nil then + return dirObj(dir.rev) + end + return nil +end + +-- Configuration of an amenity name to the environment code to use on rooms with it +local amenityEnvCodes = { + bacta = 269, + bank = 267, + broadcast = 270, + hotel = 265, + library = 261, + locker = 263, + package = 262, + workshop = 266, +} + +local function trim(s) + return (s:gsub("^%s*(.-)%s*$", "%1")) +end + + +------------------------------------------------------------------------------ +-- Command Handlers +------------------------------------------------------------------------------ + +-- Main "map" command handler +function lotj.mapper.mapCommand(input) + input = trim(input) + if #input == 0 then + lotj.mapper.printMainMenu() + return + end + + _, _, cmd, args = string.find(input, "([^%s]+)%s*(.*)") + cmd = string.lower(cmd) + + if cmd == "help" then + lotj.mapper.printHelp() + elseif cmd == "start" then + lotj.mapper.startMapping(args) + elseif cmd == "stop" then + lotj.mapper.stopMapping() + elseif cmd == "deletearea" then + lotj.mapper.deleteArea(args) + elseif cmd == "shift" then + lotj.mapper.shiftCurrentRoom(args) + elseif cmd == "save" then + lotj.mapper.saveMap() + else + lotj.mapper.logError("Unknown map command. Try map help.") + end +end + + +function lotj.mapper.printMainMenu() + lotj.mapper.log("Mapper Introduction and Status") + cecho([[ + +The LOTJ Mapper plugin tracks movement using MSDP variables. To begin, try map start . +Once mapping is started, move slowly between rooms to map them. Moving too quickly will cause the +mapper to skip rooms. You should wait for the map to reflect your movements before moving again +whenever you are in mapping mode. + +When you are finished mapping, use map stop to stop recording your movements, and be sure to +map save! Map data will not be saved automatically. + +Other commands are available to adjust mapping as you go. map shift , for example, +will move your current room. See map help for a full list of available commands. + +The map GUI also offers editing functionality and is ideal for moving groups of rooms, deleting +or coloring rooms, etc. + +]]) + + if lotj.mapper.mappingArea ~= nil then + cecho("Mapper status: Mapping in area "..lotj.mapper.mappingArea.."\n") + else + cecho("Mapper status: Off\n") + end +end + + +function lotj.mapper.printHelp() + lotj.mapper.log("Mapper Command List") + cecho([[ + +map start + +Begin mapping. Any new rooms you enter while mapping will be added to this area name, so you +should be sure to stop mapping before entering a ship or moving to a different zone. + +Some tips to remember: + - Move slowly, and wait for the map to reflect your movements before going to the next room. + The MUD sends data about your current room after some delay, so moving too fast will make the + mapper skip rooms or draw exits which aren't there. + - Use a light while mapping. Entering a dark room where you can't see will not update the map. + - Use map shift to adjust room positioning, especially after going through turbolifts or + voice-activated doors. It's faster to click-and-drag with the GUI to move large blocks of + rooms, though. + - Rooms in ships are all unique, even if they are the same model. In practice, mapping ships + really isn't supported yet, although platforms or ships you use frequently may be worth it. + +map stop + +Stop editing the map based on your movements. + +map save + +Save the map to the map.dat file in your Mudlet profile's directory. + +map deletearea + +Deletes all data for an area. There's no confirmation and no undo! + +map shift + +Moves the current room in whichever direction you enter. Useful for adjusting placement of +rooms when you need to space them out. +]]) +end + + +function lotj.mapper.startMapping(areaName) + areaName = trim(areaName) + if #areaName == 0 then + lotj.mapper.log("Syntax: map start ") + return + end + + if lotj.mapper.mappingArea ~= nil then + lotj.mapper.logError("Mapper already running in "..lotj.mapper.mappingArea..".") + return + end + + local areaTable = getAreaTable() + if areaTable[areaName] == nil then + addAreaName(areaName) + lotj.mapper.log("Mapping in new area "..areaName..".") + + if lotj.mapper.noAreasPrompt ~= nil then + lotj.mapper.noAreasPrompt:hide() + lotj.mapper.noAreasPrompt = nil + lotj.mapper.mapperInstance:show() + end + else + lotj.mapper.log("Mapping in existing area "..areaName..".") + end + + lotj.mapper.mappingArea = areaName + lotj.mapper.processCurrentRoom() +end + + +function lotj.mapper.stopMapping() + if lotj.mapper.mappingArea == nil then + lotj.mapper.logError("Mapper not running.") + return + end + lotj.mapper.mappingArea = nil + lotj.mapper.log("Mapping stopped. Don't forget to map save!") +end + + +function lotj.mapper.deleteArea(areaName) + areaName = trim(areaName) + if #areaName == 0 then + lotj.mapper.log("Syntax: map deletearea ") + return + end + + local areaTable = getAreaTable() + if areaTable[areaName] == nil then + lotj.mapper.logError("Area "..areaName.." does not exist.") + return + end + + if areaName == lotj.mapper.mappingArea then + lotj.mapper.stopMapping() + end + + deleteArea(areaName) + lotj.mapper.log("Area "..areaName.." deleted.") +end + + +function lotj.mapper.shiftCurrentRoom(direction) + direction = trim(direction) + if #direction == 0 then + lotj.mapper.log("Syntax: map shift ") + return + end + + local dir = dirObj(direction) + if dir == nil then + lotj.mapper.logError("Direction unknown: "..direction.."") + return + end + + local vnum = lotj.mapper.current.vnum + local room = lotj.mapper.getRoomByVnum(vnum) + if room ~= nil then + currentX, currentY, currentZ = getRoomCoordinates(vnum) + dx, dy, dz = unpack(dir.xyzDiff) + setRoomCoordinates(vnum, currentX+dx, currentY+dy, currentZ+dz) + updateMap() + centerview(vnum) + end +end + + +function lotj.mapper.saveMap() + saveMap(getMudletHomeDir() .. "/map.dat") + lotj.mapper.log("Map saved.") +end + + +------------------------------------------------------------------------------ +-- Event Handlers +------------------------------------------------------------------------------ + + +registerAnonymousEventHandler("lotjUICreated", function() + lotj.mapper.mapperInstance = Geyser.Mapper:new({ + x = 0, y = 0, + width = "100%", + height = "100%", + }, lotj.layout.upperRightTabData.contents["map"]) + setMapZoom(15) + + local hasAnyAreas = false + for name, id in pairs(getAreaTable()) do + if name ~= "Default Area" then + hasAnyAreas = true + end + end + if not hasAnyAreas then + lotj.mapper.mapperInstance:hide() + lotj.mapper.noAreasPrompt = Geyser.Label:new({ + x = 0, y = 0, + width = "100%", + height = "100%" + }, lotj.layout.upperRightTabData.contents["map"]) + lotj.mapper.noAreasPrompt:echo("No map data.

Use map help to get started.", nil, "c14") + end + + registerAnonymousEventHandler("sysDataSendRequest", "lotj.mapper.handleSentCommand") + registerAnonymousEventHandler("msdp.ROOMVNUM", "lotj.mapper.onEnterRoom") +end) + + +-- Track the most recent movement command so we know which direction we moved when automapping +function lotj.mapper.handleSentCommand(event, cmd) + -- If we're not mapping, don't bother + if lotj.mapper.mappingArea == nil then + return + end + + local dir = dirObj(trim(cmd)) + if dir ~= nil then + lotj.mapper.lastMoveDir = dir + end +end + + +-- Function used to handle a room that we've moved into. This will use the data on +-- lotj.mapper.current, compared with lotj.mapper.last, to potentially create a new room and +-- link it with an exit on the previous room. +function lotj.mapper.processCurrentRoom() + local vnum = lotj.mapper.current.vnum + local moveDir = lotj.mapper.lastMoveDir + local room = lotj.mapper.getRoomByVnum(vnum) + + if lotj.mapper.mappingArea == nil and room == nil then + lotj.mapper.logDebug("Room not found, but mapper not running.") + return + end + + local lastRoom = nil + if lotj.mapper.last ~= nil then + lastRoom = lotj.mapper.getRoomByVnum(lotj.mapper.last.vnum) + end + + -- Create the room if we don't have it yet + if room == nil then + lotj.mapper.log("Added new room: "..lotj.mapper.current.name.."") + addRoom(vnum) + setRoomArea(vnum, lotj.mapper.mappingArea) + setRoomCoordinates(vnum, 0, 0, 0) + setRoomName(vnum, lotj.mapper.current.name) + room = lotj.mapper.getRoomByVnum(vnum) + + -- Create stub exits in any known direction we see + for dir, state in pairs(lotj.mapper.current.exits) do + local exitDir = dirObj(dir) + if exitDir ~= nil then + setExitStub(vnum, exitDir.short, true) + if state == "C" then + setDoor(vnum, exitDir.short, 2) + end + end + end + + -- Position the room relative to the room we came from + if lastRoom ~= nil then + local lastX, lastY, lastZ = getRoomCoordinates(lotj.mapper.last.vnum) + + -- If we recorded a valid movement command, use that direction to position this room + if moveDir ~= nil then + local dx, dy, dz = unpack(moveDir.xyzDiff) + setRoomCoordinates(vnum, lastX+dx, lastY+dy, lastZ+dz) + else + -- We didn't have a valid movement command but we still changed rooms, so try to guess + -- where this room should be relative to the last. + + -- Find a stub with a door on the last room which matches a stub with a door on this room + -- This aims to handle cases where you've used a voice-activated locked door + local lastDoors = getDoors(lotj.mapper.last.vnum) + local currentDoors = getDoors(vnum) + local matchingStubDir = nil + for _, lastRoomStubDirNum in ipairs(getExitStubs1(lotj.mapper.last.vnum) or {}) do + local lastRoomStubDir = dirObj(lastRoomStubDirNum) + + for _, currentRoomStubDirNum in ipairs(getExitStubs1(vnum) or {}) do + local currentRoomStubDir = dirObj(currentRoomStubDirNum) + if lastRoomStubDir.short == currentRoomStubDir.rev + and lastDoors[lastRoomStubDir.short] == 2 + and currentDoors[currentRoomStubDir.short] == 2 then + + matchingStubDir = lastRoomStubDir + end + end + end + + if matchingStubDir ~= nil then + local dx, dy, dz = unpack(matchingStubDir.xyzDiff) + setRoomCoordinates(vnum, lastX+dx, lastY+dy, lastZ+dz) + lotj.mapper.log("Positioning new room "..matchingStubDir.long.." of the previous room based on matching closed doors.") + else + -- If no matching stubs were found, just find a nearby location which isn't taken by either a stub or a real room. + for dir in pairs({"n", "e", "w", "s", "ne", "nw", "se", "sw", "u", "d"}) do + local dx, dy, dz = unpack(dirObj(dir).xyzDiff) + local overlappingRoomId = lotj.mapper.getRoomByCoords(lotj.mapper.mappingArea, lastX+dx, lastY+dy, lastZ+dz) + + local hasOverlappingStub = false + for _, stubDirNum in ipairs(getExitStubs1(lotj.mapper.last.vnum) or {}) do + if dirObj(stubDirNum) == dirObj(dir) then + hasOverlappingStub = true + end + end + + if overlappingRoomId == nil and not hasOverlappingStub then + lotj.mapper.log("Exit unknown. Positioning new room "..dirObj(dir).long.." of the previous room.") + setRoomCoordinates(vnum, lastX+dx, lastY+dy, lastZ+dz) + break + end + end + end + end + end + end + + -- Link this room with the previous one if they have a matching set of exit stubs + if lastRoom ~= nil and moveDir ~= nil then + -- Always set the exit we took even if it wasn't a stub. The direction we just moved is our best + -- evidence of how rooms are connected, overriding any reverse-created exits made earlier if they + -- are different. + setExit(lotj.mapper.last.vnum, vnum, moveDir.short) + + -- Only set the reverse exit (from current room back to where we came from) if it's a stub. + -- In the case of mazes or asymmetrical exits, this may be wrong but will be fixed on moving back + -- out through this exit. + for _, currentRoomStubDirNum in ipairs(getExitStubs1(vnum) or {}) do + local currentRoomStubDir = dirObj(currentRoomStubDirNum) + if moveDir.rev == currentRoomStubDir.short then + setExit(vnum, lotj.mapper.last.vnum, moveDir.rev) + end + end + end + + centerview(vnum) +end + + +function lotj.mapper.checkAmenityLine(roomName, amenityName, wasPending) + if lotj.mapper.mappingArea == nil then + return + end + + envCode = amenityEnvCodes[string.lower(amenityName)] + if envCode == nil then + return + end + + local addAmenityRoom = nil + if lotj.mapper.current.name == roomName then + addAmenityRoom = lotj.mapper.current + elseif lotj.mapper.last.name == roomName then + addAmenityRoom = lotj.mapper.last + end + + -- If this wasn't stored for later use, we need a newline since this is being invoked on + -- seeing a room name and we don't want it mushed into that line. + if not wasPending then + echo("\n") + end + + if addAmenityRoom == nil then + -- The room name we're triggering on might be the room we just entered but we haven't + -- received the MSDP event yet, so we'll store this for the next time we do. + lotj.mapper.pendingAmenity = { + roomName = roomName, + amenityName = amenityName, + } + else + lotj.mapper.log("Set amenity "..amenityName.." on room "..addAmenityRoom.name.."") + setRoomEnv(addAmenityRoom.vnum, envCode) + updateMap() + end +end + + +-- The vnum is always sent after the name and exits, so we can use it as a trigger for +-- handling movement to a new room +function lotj.mapper.onEnterRoom() + if lotj.mapper.current ~= nil then + lotj.mapper.last = lotj.mapper.current + end + local exits = {} + if msdp.ROOMEXITS ~= "" then + exits = msdp.ROOMEXITS + end + lotj.mapper.current = { + vnum = tonumber(msdp.ROOMVNUM), + name = string.gsub(msdp.ROOMNAME, "&.", ""), + exits = exits, + } + + lotj.mapper.processCurrentRoom() + + -- Since we've handled the move, we don't want the last move command to get + -- used by anything else. + lotj.mapper.lastMoveDir = nil + + local pendingAmenity = lotj.mapper.pendingAmenity + if pendingAmenity ~= nil then + lotj.mapper.checkAmenityLine(pendingAmenity.roomName, pendingAmenity.amenityName, true) + lotj.mapper.pendingAmenity = nil + end +end + + +------------------------------------------------------------------------------ +-- Utility Functions +------------------------------------------------------------------------------ + + +function lotj.mapper.log(text) + cecho("[LOTJ Mapper] "..text.."\n") +end + +function lotj.mapper.logDebug(text) + if lotj.mapper.debug then + lotj.mapper.log("Debug: "..text) + end +end + +function lotj.mapper.logError(text) + lotj.mapper.log("Error: "..text) +end + +function lotj.mapper.getRoomByVnum(vnum) + return getRooms()[vnum] +end + +function lotj.mapper.getRoomByCoords(areaName, x, y, z) + local areaRooms = getAreaRooms(getAreaTable()[areaName]) or {} + for _, roomId in pairs(areaRooms) do + local roomX, roomY, roomZ = getRoomCoordinates(roomId) + if roomX == x and roomY == y and roomZ == z then + return roomId + end + end + return nil +end + +function doSpeedWalk() + echo("Path we need to take: " .. table.concat(speedWalkDir, ", ") .. "\n") + echo("A future version of the mapper script might actually execute these commands.\n") +end diff --git a/src/scripts/mapper/scripts.json b/src/scripts/mapper/scripts.json new file mode 100644 index 0000000..1842e73 --- /dev/null +++ b/src/scripts/mapper/scripts.json @@ -0,0 +1,5 @@ +[ + { + "name": "mapper" + } +] \ No newline at end of file diff --git a/src/scripts/msdp/msdp.lua b/src/scripts/msdp/msdp.lua new file mode 100644 index 0000000..119b892 --- /dev/null +++ b/src/scripts/msdp/msdp.lua @@ -0,0 +1,45 @@ +registerAnonymousEventHandler("sysLoadEvent", function() + registerAnonymousEventHandler("msdp.COMMANDS", function() + local msdpVars = {} + + table.insert(msdpVars, "HEALTH") + table.insert(msdpVars, "HEALTHMAX") + table.insert(msdpVars, "WIMPY") + table.insert(msdpVars, "MOVEMENT") + table.insert(msdpVars, "MOVEMENTMAX") + table.insert(msdpVars, "MANA") + table.insert(msdpVars, "MANAMAX") + + table.insert(msdpVars, "OPPONENTNAME") + table.insert(msdpVars, "OPPONENTHEALTH") + table.insert(msdpVars, "OPPONENTHEALTHMAX") + + table.insert(msdpVars, "COMMCHANNEL") + table.insert(msdpVars, "COMMENCRYPT") + table.insert(msdpVars, "OOCLIMIT") + + table.insert(msdpVars, "ROOMNAME") + table.insert(msdpVars, "ROOMEXITS") + table.insert(msdpVars, "ROOMVNUM") + + table.insert(msdpVars, "PILOTING") + table.insert(msdpVars, "SHIPSPEED") + table.insert(msdpVars, "SHIPMAXSPEED") + table.insert(msdpVars, "SHIPHULL") + table.insert(msdpVars, "SHIPMAXHULL") + table.insert(msdpVars, "SHIPSHIELD") + table.insert(msdpVars, "SHIPMAXSHIELD") + table.insert(msdpVars, "SHIPENERGY") + table.insert(msdpVars, "SHIPMAXENERGY") + table.insert(msdpVars, "SHIPSYSX") + table.insert(msdpVars, "SHIPSYSY") + table.insert(msdpVars, "SHIPSYSZ") + table.insert(msdpVars, "SHIPGALX") + table.insert(msdpVars, "SHIPGALY") + table.insert(msdpVars, "SHIPSYSNAME") + + for _, varName in ipairs(msdpVars) do + sendMSDP("REPORT", varName) + end + end) +end) diff --git a/src/scripts/msdp/scripts.json b/src/scripts/msdp/scripts.json new file mode 100644 index 0000000..58ff966 --- /dev/null +++ b/src/scripts/msdp/scripts.json @@ -0,0 +1,5 @@ +[ + { + "name": "msdp" + } +] diff --git a/src/scripts/system-map/scripts.json b/src/scripts/system-map/scripts.json new file mode 100644 index 0000000..de521da --- /dev/null +++ b/src/scripts/system-map/scripts.json @@ -0,0 +1,5 @@ +[ + { + "name": "system-map" + } +] \ No newline at end of file diff --git a/src/scripts/system-map/system-map.lua b/src/scripts/system-map/system-map.lua new file mode 100644 index 0000000..bb304af --- /dev/null +++ b/src/scripts/system-map/system-map.lua @@ -0,0 +1,326 @@ +lotj = lotj or {} +lotj.systemMap = lotj.systemMap or { + mapRange = 2000, + radarItems = {}, + genPoints = {}, + genLabels = {} +} +local pointSize = 12 +local labelWidth = 200 +local labelHeight = 16 + +local controlButtonStyle = [[ + background-color: grey; + border: 2px solid white; +]] + +registerAnonymousEventHandler("lotjUICreated", function() + disableTrigger("system-map-radar") + + local tabContents = lotj.layout.upperRightTabData.contents["system"] + lotj.systemMap.container = Geyser.Label:new({}, tabContents) + lotj.systemMap.container:setStyleSheet([[ + background-color: black; + ]]) + lotj.systemMap.resizeToSquare() + registerAnonymousEventHandler("sysWindowResizeEvent", lotj.systemMap.resizeToSquare) + + local zoomInButton = Geyser.Label:new({ + x = "2%", y = 10, + width = 28, height = 28, + }, tabContents) + zoomInButton:setStyleSheet(controlButtonStyle) + zoomInButton:echo("+", "white", "c16b") + zoomInButton:setClickCallback(function() + if lotj.systemMap.mapRange > 1000 then + lotj.systemMap.mapRange = lotj.systemMap.mapRange - 1000 + elseif lotj.systemMap.mapRange == 1000 then + lotj.systemMap.mapRange = 500 + else + return + end + lotj.systemMap.drawMap() + end) + + local zoomOutButton = Geyser.Label:new({ + x = "2%", y = 44, + width = 28, height = 28, + }, tabContents) + zoomOutButton:setStyleSheet(controlButtonStyle) + zoomOutButton:echo("-", "white", "c16b") + zoomOutButton:setClickCallback(function() + if lotj.systemMap.mapRange >= 1000 then + lotj.systemMap.mapRange = lotj.systemMap.mapRange + 1000 + elseif lotj.systemMap.mapRange == 500 then + lotj.systemMap.mapRange = 1000 + else + return + end + lotj.systemMap.drawMap() + end) + + local refreshButton = Geyser.Label:new({ + x = "2%", y = 78, + width = 28, height = 28, + }, tabContents) + refreshButton:setStyleSheet(controlButtonStyle) + refreshButton:echo("R", "white", "c16b") + refreshButton:setClickCallback(function() + lotj.systemMap.maskNextRadarOutput = true + expandAlias("radar", false) + end) + + + local rangeCircle = rangeCircle or Geyser.Label:new({fillBg = 0}, lotj.systemMap.container) + rangeCircle:move(0, 0) + + lotj.systemMap.rangeLabel = lotj.systemMap.rangeLabel or Geyser.Label:new({fillBg = 0}, lotj.systemMap.container) + lotj.systemMap.rangeLabel:resize(50, 20) + lotj.systemMap.rangeLabel:echo(lotj.systemMap.mapRange, "green", "10c") + + local function positionRangeCircle() + local containerSize = lotj.systemMap.container:get_height() + lotj.systemMap.rangeLabel:move(containerSize-math.ceil(containerSize/10)-25, math.ceil(containerSize/10)) + rangeCircle:resize(containerSize, containerSize) + rangeCircle:setStyleSheet([[ + border: 1px dashed green; + border-radius: ]]..math.floor(containerSize/2)..[[px; + ]]) + end + positionRangeCircle() + registerAnonymousEventHandler("sysWindowResizeEvent", positionRangeCircle) + + registerAnonymousEventHandler("msdp.SHIPSYSX", "lotj.systemMap.drawMap") + registerAnonymousEventHandler("msdp.SHIPSYSY", "lotj.systemMap.drawMap") + registerAnonymousEventHandler("msdp.SHIPSYSZ", "lotj.systemMap.drawMap") +end) + +function lotj.systemMap.resetItems() + lotj.systemMap.radarItems = {} +end + +function lotj.systemMap.addItem(item) + table.insert(lotj.systemMap.radarItems, item) +end + +function lotj.systemMap.drawMap() + lotj.systemMap.rangeLabel:echo(lotj.systemMap.mapRange, "green", "10c") + + -- Hide any previously generated elements which we may be displaying + for _, elem in ipairs(lotj.systemMap.genPoints) do + elem:hide() + end + for _, elem in ipairs(lotj.systemMap.genLabels) do + elem:hide() + end + + -- We use ship max speed as a proxy for "do we have ship data at all" + if tonumber(msdp.SHIPMAXSPEED or "0") == 0 then + return + end + + local shipX = tonumber(msdp.SHIPSYSX or "0") + local shipY = tonumber(msdp.SHIPSYSY or "0") + local shipZ = tonumber(msdp.SHIPSYSZ or "0") + local selfData = {name="You", x=shipX, y=shipY, z=shipZ} + + local itemsToDraw = {} + table.insert(itemsToDraw, selfData) + for _, item in ipairs(lotj.systemMap.radarItems) do + if lotj.systemMap.dist(selfData, item) <= lotj.systemMap.mapRange then + table.insert(itemsToDraw, item) + end + end + + local drawnItems = {} + for i, item in ipairs(itemsToDraw) do + local point, label = lotj.systemMap.pointAndLabel(i) + + local color = "yellow" + if item.class and string.match(item.class, "Pirated") then + color = "red" + elseif item == selfData then + color = "white" + end + + point:resize(pointSize, pointSize) + point:setStyleSheet([[ + border-radius: ]]..math.floor(pointSize/2)..[[px; + background-color: ]]..color..[[; + ]]) + + local x, y = lotj.systemMap.computeXY(selfData, item) + point:show() + point:move(x, y) + + local labelXOffset, labelYOffset = lotj.systemMap.computeLabelPos(drawnItems, x, y) + local labelX = x+labelXOffset + local labelY = y+labelYOffset + label:show() + label:move(labelX, labelY) + lotj.systemMap.printLabels(point, label, color, labelXOffset, item, selfData) + + table.insert(drawnItems, {x = x, y = y, labelX = labelX, labelY = labelY, labelXOffset = labelXOffset, labelYOffset = labelYOffset}) + end +end + +-- Add appropriate text to the point and label +function lotj.systemMap.printLabels(point, label, color, labelXOffset, item, selfData) + local labelStr = item.name + + -- Prepend an up/down arrow to show Z diff + local zDiff = item.z - selfData.z + if zDiff ~= 0 then + local arrowFontSize = 3 + math.floor(11 * math.abs(zDiff) / lotj.systemMap.mapRange + 0.5) + local arrowChar = "▲" -- Up arrow + if zDiff < 0 then + arrowChar = "▼" -- Down arrow + end + point:echo(arrowChar, "black", arrowFontSize.."c") + else + point:echo("") + end + + -- Append proximity and class + if item ~= selfData then + labelStr = labelStr.." ("..math.floor(lotj.systemMap.dist(item, selfData) + 0.5) + if item.class ~= nil then + labelStr = labelStr..", "..item.class + end + labelStr = labelStr..")" + end + + -- Set alignment based on whether the label is to the right or left of the point + if labelXOffset > 0 then + label:echo(labelStr, color, "11l") + else + label:echo(labelStr, color, "11r") + end +end + +-- Based on the position, size of map, and zoom level, determine the X and Y placement for the given item +function lotj.systemMap.computeXY(selfData, item) + local mapMinX = selfData.x-lotj.systemMap.mapRange + local mapMinY = selfData.y-lotj.systemMap.mapRange + local containerSize = lotj.systemMap.container:get_height() + + local x = math.floor(containerSize * ((item.x - mapMinX) / (lotj.systemMap.mapRange*2)) + 0.5) + local y = math.floor(containerSize * (1 - (item.y - mapMinY) / (lotj.systemMap.mapRange*2)) + 0.5) + + return x-pointSize/2, y-pointSize/2 +end + +-- Return true if the first rectancle overlaps with any part of the second rectangle +local function overlaps(x1, y1, w1, h1, x2, y2, w2, h2) + local xOverlap = (x1 >= x2 and x1 <= x2+w2) or (x1+w1 >= x2 and x1+w1 <= x2+w2) + local yOverlap = (y1 >= y2 and y1 <= y2+h2) or (y1+h1 >= y2 and y1+h1 <= y2+h2) + return xOverlap and yOverlap +end + +-- Determine whether a given rectangle overlaps with the points or labels in the given array of items +local function anyOverlaps(x, y, w, h, items) + for _, item in ipairs(items) do + -- Overlap with this item's point? + if overlaps(x, y, w, h, item.x, item.y, pointSize, pointSize) then + return true + end + -- Overlap with this item's label? + if overlaps(x, y, w, h, item.labelX, item.labelY, labelWidth, labelHeight) then + return true + end + end + return false +end + +-- Find a suitable label X and Y offset for a new label, attempting to avoid overlap with anything previously drawn +function lotj.systemMap.computeLabelPos(drawnItems, itemX, itemY) + local labelXOffset = pointSize + local labelYOffset = pointSize + + -- If we find an item at the same coords, simply put the label below the last one for those coords + local foundSameCoords = false + for _, item in ipairs(drawnItems) do + if item.x == itemX and item.y == itemY then + foundSameCoords = true + if item.labelY > item.y then + labelYOffset = item.labelYOffset + labelHeight + else + labelYOffset = item.labelYOffset - labelHeight + end + end + end + if foundSameCoords then + return labelXOffset, labelYOffset + end + + -- Try four different label positions to find one without any overlap + local offsetsToTry = {} + table.insert(offsetsToTry, {x = pointSize, y = pointSize}) + table.insert(offsetsToTry, {x = pointSize, y = labelHeight * -1}) + table.insert(offsetsToTry, {x = labelWidth * -1, y = labelHeight * -1}) + table.insert(offsetsToTry, {x = labelWidth * -1, y = pointSize}) + for _, offsets in ipairs(offsetsToTry) do + if not anyOverlaps(itemX+offsets.x, itemY+offsets.y, labelWidth, labelHeight, drawnItems) then + return offsets.x, offsets.y + end + end + + -- We couldn't find a non-overlapping position, so just put it to the lower right + return labelXOffset, labelYOffset +end + +-- Return existing (or create new) Geyser labels for a given point and label +-- We store and reuse these so that we don't accumulate infinite label objects, since Geyser doesn't give us a way to delete elements, only hide them +function lotj.systemMap.pointAndLabel(idx) + lotj.systemMap.genPoints[idx] = lotj.systemMap.genPoints[idx] or Geyser.Label:new({}, lotj.systemMap.container) + lotj.systemMap.genLabels[idx] = lotj.systemMap.genLabels[idx] or Geyser.Label:new({fillBg = 0, width = labelWidth, height = labelHeight}, lotj.systemMap.container) + return lotj.systemMap.genPoints[idx], lotj.systemMap.genLabels[idx] +end + +-- Compute distance between one X/Y/Z coord and another +function lotj.systemMap.dist(coordsA, coordsB) + local xDiff = math.abs(coordsA.x-coordsB.x) + local yDiff = math.abs(coordsA.y-coordsB.y) + local zDiff = math.abs(coordsA.z-coordsB.z) + + local xyDiff = math.sqrt(xDiff*xDiff + yDiff*yDiff) + local totalDiff = math.sqrt(xyDiff*xyDiff + zDiff*zDiff) + return totalDiff +end + +-- Resize the map to the largest possible square when the window dimensions change +function lotj.systemMap.resizeToSquare() + local tabContents = lotj.layout.upperRightTabData.contents["system"] + local contH = tabContents:get_height() + local contW = tabContents:get_width() + + local x = 0 + local y = 0 + local width = "100%" + local height = "100%" + if contW >= contH then + width = contH + x = (contW-contH)/2 + else + height = contW + y = (contH-contW)/2 + end + + lotj.systemMap.container:move(x, y) + lotj.systemMap.container:resize(width, height) +end + +function lotj.systemMap.findTarget(targetName) + targetName = targetName:lower() + local target = nil + for _, item in ipairs(lotj.systemMap.radarItems) do + if item.name:lower():sub(1, #targetName) == targetName then + target = item + end + end + return target +end + +function lotj.systemMap.log(text) + cecho("[System Map] "..text.."\n") +end diff --git a/src/triggers/chat/commnet-translated.lua b/src/triggers/chat/commnet-translated.lua new file mode 100644 index 0000000..9445823 --- /dev/null +++ b/src/triggers/chat/commnet-translated.lua @@ -0,0 +1,6 @@ +if lotj.chat.commnetLastChannel == matches[2] and lotj.chat.commnetLastMessage == matches[3] then + deleteLine() + echo(" (Translated)") +else + lotj.chat.routeMessage("commnet") +end diff --git a/src/triggers/chat/commnet.lua b/src/triggers/chat/commnet.lua new file mode 100644 index 0000000..8f46750 --- /dev/null +++ b/src/triggers/chat/commnet.lua @@ -0,0 +1,6 @@ +lotj.chat.routeMessage("commnet") + +-- Track commnet messages to potentially squash a redundant translation +-- message on the next line +lotj.chat.commnetLastChannel = matches[2] +lotj.chat.commnetLastMessage = matches[3] diff --git a/src/triggers/chat/triggers.json b/src/triggers/chat/triggers.json new file mode 100644 index 0000000..a265a31 --- /dev/null +++ b/src/triggers/chat/triggers.json @@ -0,0 +1,76 @@ +[ + { + "name": "commnet", + "patterns": [ + { + "pattern": "^CommNet ([0-9]+) \\[.*\\][()a-zA-Z<> ]*: (.*)", + "type": "regex" + } + ] + }, + { + "name": "commnet-translated", + "patterns": [ + { + "pattern": "^.* buzzes '\\(Translating channel ([0-9]+)\\) (.*)'$", + "type": "regex" + } + ] + }, + { + "name": "clan", + "patterns": [ + { + "pattern": "{.*}<.*>\\[[a-zA-Z ]+\\][()<>A-Za-z ]*: ", + "type": "regex" + }, + { + "pattern": "[Incoming Transmission from", + "type": "substring" + }, + { + "pattern": "[Outgoing Transmission to", + "type": "substring" + } + ], + "script": "lotj.chat.routeMessage(\"clan\")" + }, + { + "name": "ooc", + "patterns": [ + { + "pattern": "^\\((OOC|IMM|RPC|NEWBIE)\\) [*]?[A-Za-z]+: .*$", + "type": "regex" + } + ], + "script": "lotj.chat.routeMessage(\"ooc\")" + }, + { + "name": "immchat", + "patterns": [ + { + "pattern": "^\\( IMM \\| CHAT \\) .* mortchats to you '.*'$", + "type": "regex" + }, + { + "pattern": "^\\( IMM \\| CHAT \\)\\[.*\\]: '.*'$", + "type": "regex" + } + ], + "script": "lotj.chat.routeMessage(\"imm\")" + }, + { + "name": "tell", + "patterns": [ + { + "pattern": "^\\(OOC\\) .* tells you '.*'$", + "type": "regex" + }, + { + "pattern": "^\\(OOC\\) You tell .* '.*'$", + "type": "regex" + } + ], + "script": "lotj.chat.routeMessage(\"tell\")" + } +] diff --git a/src/triggers/galaxy-map/any-planet-line.lua b/src/triggers/galaxy-map/any-planet-line.lua new file mode 100644 index 0000000..3dc755e --- /dev/null +++ b/src/triggers/galaxy-map/any-planet-line.lua @@ -0,0 +1,5 @@ +-- Swallow lines and extend the triggers as long as we haven't found the end of the planet yet +if gatherPlanetState ~= nil then + setTriggerStayOpen("gather-planet", 1) +end +deleteLine() diff --git a/src/triggers/galaxy-map/gather-planet.lua b/src/triggers/galaxy-map/gather-planet.lua new file mode 100644 index 0000000..339dbe9 --- /dev/null +++ b/src/triggers/galaxy-map/gather-planet.lua @@ -0,0 +1,7 @@ +gatherPlanetState = { + section = "basics" +} + +deleteLine() +moveCursor(0,getLineNumber()-1) +deleteLine() diff --git a/src/triggers/galaxy-map/gather-planets.lua b/src/triggers/galaxy-map/gather-planets.lua new file mode 100644 index 0000000..e943ed5 --- /dev/null +++ b/src/triggers/galaxy-map/gather-planets.lua @@ -0,0 +1,7 @@ +deleteLine() + +gatherPlanetsState = { + pendingBasic = {}, + pendingResources = {}, + pendingCommands = 0, +} diff --git a/src/triggers/galaxy-map/no-datapad.lua b/src/triggers/galaxy-map/no-datapad.lua new file mode 100644 index 0000000..9d0bbb1 --- /dev/null +++ b/src/triggers/galaxy-map/no-datapad.lua @@ -0,0 +1,3 @@ +echo("\n") +lotj.galaxyMap.log("Error gathering galaxy map data. Please fix the problem and try again.") +disableTrigger("galaxy-map-refresh") diff --git a/src/triggers/galaxy-map/no-resources.lua b/src/triggers/galaxy-map/no-resources.lua new file mode 100644 index 0000000..14adea8 --- /dev/null +++ b/src/triggers/galaxy-map/no-resources.lua @@ -0,0 +1,2 @@ +gatherPlanetState.section = "resources" +gatherPlanetState.resources = {} diff --git a/src/triggers/galaxy-map/planet-coords.lua b/src/triggers/galaxy-map/planet-coords.lua new file mode 100644 index 0000000..3b68c95 --- /dev/null +++ b/src/triggers/galaxy-map/planet-coords.lua @@ -0,0 +1,5 @@ +gatherPlanetState.coords = { + x = tonumber(matches[2]), + y = tonumber(matches[3]), + z = tonumber(matches[4]), +} diff --git a/src/triggers/galaxy-map/planet-empty-line.lua b/src/triggers/galaxy-map/planet-empty-line.lua new file mode 100644 index 0000000..0d2a3c3 --- /dev/null +++ b/src/triggers/galaxy-map/planet-empty-line.lua @@ -0,0 +1,14 @@ +-- If we've gotten into the list of resources, an empty line means we're done +if gatherPlanetState and gatherPlanetState.section == "resources" then + echo("\n") + lotj.galaxyMap.log("Collected resource data for "..gatherPlanetState.name) + lotj.galaxyMap.recordPlanet(gatherPlanetState) + + gatherPlanetsState.pendingResources[gatherPlanetState.name] = nil + gatherPlanetsState.pendingCommands = gatherPlanetsState.pendingCommands - 1 + if gatherPlanetsState.pendingCommands == 0 then + lotj.galaxyMap.enqueuePendingRefreshCommands() + end + + gatherPlanetState = nil +end diff --git a/src/triggers/galaxy-map/planet-end.lua b/src/triggers/galaxy-map/planet-end.lua new file mode 100644 index 0000000..eebaee1 --- /dev/null +++ b/src/triggers/galaxy-map/planet-end.lua @@ -0,0 +1,11 @@ +echo("\n") +lotj.galaxyMap.log("Collected basic data for "..gatherPlanetState.name) +lotj.galaxyMap.recordPlanet(gatherPlanetState) + +gatherPlanetsState.pendingBasic[gatherPlanetState.name] = nil +gatherPlanetsState.pendingCommands = gatherPlanetsState.pendingCommands - 1 +if gatherPlanetsState.pendingCommands == 0 then + lotj.galaxyMap.enqueuePendingRefreshCommands() +end + +gatherPlanetState = nil diff --git a/src/triggers/galaxy-map/planets-line.lua b/src/triggers/galaxy-map/planets-line.lua new file mode 100644 index 0000000..7db4e94 --- /dev/null +++ b/src/triggers/galaxy-map/planets-line.lua @@ -0,0 +1,29 @@ +deleteLine() + +local line = matches[2] + +local function starts_with(str, start) + return str:sub(1, #start) == start +end + +if starts_with(line, "Use SHOWPLANET for more information.") then + lotj.galaxyMap.enqueuePendingRefreshCommands() + return +end + +line = line:gsub("%(UFG%)", "") +line = line:gsub(" +", ";") +local _, _, planet, system, gov, support = line:find("([^;]+);([^;]+);([^;]+);([^;]+)") + +if planet ~= "Planet" then + lotj.galaxyMap.recordPlanet({ + name = planet, + system = system, + gov = gov, + }) + + gatherPlanetsState.pendingBasic[planet] = true + gatherPlanetsState.pendingResources[planet] = true +end + +setTriggerStayOpen("gather-planets", 1) diff --git a/src/triggers/galaxy-map/resource-price.lua b/src/triggers/galaxy-map/resource-price.lua new file mode 100644 index 0000000..ef17cce --- /dev/null +++ b/src/triggers/galaxy-map/resource-price.lua @@ -0,0 +1,7 @@ +gatherPlanetState.section = "resources" + +local resource = matches[2]:match "^%s*(.-)%s*$" +local price = tonumber(matches[3]) + +gatherPlanetState.resources = gatherPlanetState.resources or {} +gatherPlanetState.resources[resource] = price diff --git a/src/triggers/galaxy-map/showplanet-fail b/src/triggers/galaxy-map/showplanet-fail new file mode 100644 index 0000000..a65ae3a --- /dev/null +++ b/src/triggers/galaxy-map/showplanet-fail @@ -0,0 +1,4 @@ +gatherPlanetsState.pendingCommands = gatherPlanetsState.pendingCommands - 1 +if gatherPlanetsState.pendingCommands == 0 then + lotj.galaxyMap.enqueuePendingRefreshCommands() +end diff --git a/src/triggers/galaxy-map/system-line.lua b/src/triggers/galaxy-map/system-line.lua new file mode 100644 index 0000000..f90c9b4 --- /dev/null +++ b/src/triggers/galaxy-map/system-line.lua @@ -0,0 +1,8 @@ +local systemName = matches[2]:match "^%s*(.-)%s*$" +local xCoord = tonumber(matches[3]) +local yCoord = tonumber(matches[4]) + +lotj.galaxyMap.recordSystem(systemName, xCoord, yCoord) + +setTriggerStayOpen("gather-starsystems", 1) +deleteLine() diff --git a/src/triggers/galaxy-map/triggers.json b/src/triggers/galaxy-map/triggers.json new file mode 100644 index 0000000..16ccc0d --- /dev/null +++ b/src/triggers/galaxy-map/triggers.json @@ -0,0 +1,211 @@ +[ + { + "name": "galaxy-map-refresh", + "isActive": "no", + "isFolder": "yes", + "children": [ + { + "name": "gather-planets", + "fireLength": 1, + "patterns": [ + { + "pattern": "Planet\\s+Starsystem\\s+Governed By\\s+Popular Support", + "type": "regex" + } + ], + "children": [ + { + "name": "planets-line", + "patterns": [ + { + "pattern": "(.*)", + "type": "regex" + } + ] + } + ] + }, + { + "name": "gather-planet", + "multiline": "yes", + "multilineDelta": 1, + "fireLength": 4, + "patterns": [ + { + "pattern": "You use the datapad to lookup the information.", + "type": "substring" + }, + { + "pattern": "--Planet Data: -------------------------------", + "type": "substring" + } + ], + "children": [ + { + "name": "planet-name", + "patterns": [ + { + "pattern": "Planet: (.*)", + "type": "regex" + } + ], + "script": "gatherPlanetState.name = matches[2]" + }, + { + "name": "planet-starsys", + "patterns": [ + { + "pattern": "Starsystem: (.*)", + "type": "regex" + } + ], + "script": "gatherPlanetState.system = matches[2]" + }, + { + "name": "planet-coords", + "patterns": [ + { + "pattern": "Coordinates: ([0-9-]+) ([0-9-]+) ([0-9-]+)", + "type": "regex" + } + ] + }, + { + "name": "planet-gov", + "patterns": [ + { + "pattern": "Governed By: (.*)", + "type": "regex" + } + ], + "script": "gatherPlanetState.gov = matches[2]" + }, + { + "name": "planet-description-hdr", + "patterns": [ + { + "pattern": "--Planetary Information: ---------------------", + "type": "substring" + } + ], + "script": "gatherPlanetState.section = \"description\"" + }, + { + "name": "planet-resources-hdr", + "patterns": [ + { + "pattern": "--Planetary Resources: -----------------------", + "type": "substring" + } + ], + "script": "gatherPlanetState.section = \"resources-basic\"" + }, + { + "name": "resource-price", + "patterns": [ + { + "pattern": "^([\\w ]*)\\s+\\( Price per unit: ([0-9.]+)\\s*\\)", + "type": "regex" + } + ] + }, + { + "name": "no-resources", + "patterns": [ + { + "pattern": "(No resources available on this planet)", + "type": "substring" + } + ] + }, + { + "name": "freeport", + "patterns": [ + { + "pattern": "(.*) is a freeport.", + "type": "regex" + } + ], + "script": "gatherPlanetState.freeport = true" + }, + { + "name": "tax", + "patterns": [ + { + "pattern": "Tax Rate: ([0-9]+)%", + "type": "regex" + } + ], + "script": "gatherPlanetState.taxRate = tonumber(matches[2])" + }, + { + "name": "any-planet-line", + "patterns": [ + { + "pattern": "(.*)", + "type": "regex" + } + ] + }, + { + "name": "planet-empty-line", + "patterns": [ + { + "pattern": "^$", + "type": "regex" + } + ] + }, + { + "name": "planet-end", + "patterns": [ + { + "pattern": "Use 'SHOWPLANET RESOURCES' for current resources.", + "type": "prefix" + } + ] + } + ] + }, + { + "name": "gather-starsystems", + "fireLength": 1, + "patterns": [ + { + "pattern": "Listing publicly known starsystems:", + "type": "substring" + } + ], + "children": [ + { + "name": "system-line", + "patterns": [ + { + "pattern": "^(.*) \\( ([0-9-]+), ([0-9-]+) \\)$", + "type": "regex" + } + ] + } + ] + }, + { + "name": "no-datapad", + "patterns": [ + { + "pattern": "You must hold a datapad to do this.", + "type": "substring" + } + ] + }, + { + "name": "showplanet-fail", + "patterns": [ + { + "pattern": "^You fail.$", + "type": "regex" + } + ] + } + ] + } +] diff --git a/src/triggers/info-panel/triggers.json b/src/triggers/info-panel/triggers.json new file mode 100644 index 0000000..d27a1bf --- /dev/null +++ b/src/triggers/info-panel/triggers.json @@ -0,0 +1,32 @@ +[ + { + "name": "spacetick", + "patterns": [ + { + "pattern": "^Remaining jump time:", + "type": "regex" + } + ], + "script": "lotj.infoPanel.markSpaceTick()" + }, + { + "name": "chaff", + "patterns": [ + { + "pattern": "A burst of chaff is released from the ship.", + "type": "substring" + } + ], + "script": "lotj.infoPanel.markChaff()" + }, + { + "name": "chaff-cleared", + "patterns": [ + { + "pattern": "The chaff has cleared, leaving the ship vulnerable again.", + "type": "substring" + } + ], + "script": "lotj.infoPanel.clearChaff()" + } +] \ No newline at end of file diff --git a/src/triggers/layout/leave-ship.lua b/src/triggers/layout/leave-ship.lua new file mode 100644 index 0000000..0cdd9d4 --- /dev/null +++ b/src/triggers/layout/leave-ship.lua @@ -0,0 +1 @@ +lotj.layout.selectTab(lotj.layout.upperRightTabData, "map") diff --git a/src/triggers/layout/ship-launched.lua b/src/triggers/layout/ship-launched.lua new file mode 100644 index 0000000..8c012a1 --- /dev/null +++ b/src/triggers/layout/ship-launched.lua @@ -0,0 +1 @@ +lotj.layout.selectTab(lotj.layout.upperRightTabData, "system") diff --git a/src/triggers/layout/triggers.json b/src/triggers/layout/triggers.json new file mode 100644 index 0000000..e00b862 --- /dev/null +++ b/src/triggers/layout/triggers.json @@ -0,0 +1,20 @@ +[ + { + "name": "leave-ship", + "patterns": [ + { + "pattern": "You exit the ship.", + "type": "substring" + } + ] + }, + { + "name": "ship-launched", + "patterns": [ + { + "pattern": "The ship leaves the platform far behind as it flies into space", + "type": "substring" + } + ] + } +] diff --git a/src/triggers/mapper/triggers.json b/src/triggers/mapper/triggers.json new file mode 100644 index 0000000..ce867d0 --- /dev/null +++ b/src/triggers/mapper/triggers.json @@ -0,0 +1,12 @@ +[ + { + "name": "room-amenities", + "patterns": [ + { + "pattern": "^(.*) \\[([^]]+)\\]$", + "type": "regex" + } + ], + "script": "lotj.mapper.checkAmenityLine(matches[2], matches[3])" + } +] diff --git a/src/triggers/system-map/auto-radar.lua b/src/triggers/system-map/auto-radar.lua new file mode 100644 index 0000000..751301d --- /dev/null +++ b/src/triggers/system-map/auto-radar.lua @@ -0,0 +1,2 @@ +lotj.systemMap.maskNextRadarOutput = true +expandAlias("radar", false) diff --git a/src/triggers/system-map/radar-blank-line.lua b/src/triggers/system-map/radar-blank-line.lua new file mode 100644 index 0000000..ab49065 --- /dev/null +++ b/src/triggers/system-map/radar-blank-line.lua @@ -0,0 +1,5 @@ +if lotj.systemMap.maskNextRadarOutput then + deleteLine() +end + +setTriggerStayOpen("system-map-radar", 1) diff --git a/src/triggers/system-map/radar-item.lua b/src/triggers/system-map/radar-item.lua new file mode 100644 index 0000000..c92caee --- /dev/null +++ b/src/triggers/system-map/radar-item.lua @@ -0,0 +1,32 @@ +local trimName = matches[2]:gsub("^%s*(.-)%s*$", "%1") + +if trimName == "Your Coordinates:" then + setTriggerStayOpen("system-map-radar", 0) + disableTrigger("system-map-radar") + + echo("\n") + lotj.systemMap.log("Radar data collected.") + lotj.systemMap.maskNextRadarOutput = false + lotj.systemMap.inRadarOutput = false + lotj.systemMap.drawMap() + + return +end + +_, _, class, name = trimName:find("(.*) '(.*)'") +if name == nil then + name = trimName +end + +lotj.systemMap.addItem({ + class = class, + name = name, + x = tonumber(matches[3]), + y = tonumber(matches[4]), + z = tonumber(matches[5]), +}) + +if lotj.systemMap.maskNextRadarOutput then + deleteLine() +end +setTriggerStayOpen("system-map-radar", 1) diff --git a/src/triggers/system-map/system-map-radar.lua b/src/triggers/system-map/system-map-radar.lua new file mode 100644 index 0000000..bf77b0d --- /dev/null +++ b/src/triggers/system-map/system-map-radar.lua @@ -0,0 +1,22 @@ +-- Trigger groups seem to fire the parent trigger on child matches +if not matches or #matches == 0 then + return +end + +-- Occasionally we catch the space prompt here. We want to ignore that. +if string.match(matches[2], "Fuel Level:") then + return +end + +if lotj.systemMap.maskNextRadarOutput then + deleteLine() +end + +-- If we're already in the block of radar output, don't do any top-level setup +if lotj.systemMap.inRadarOutput then + return +end + +setTriggerStayOpen("system-map-radar", 1) +lotj.systemMap.resetItems() +lotj.systemMap.inRadarOutput = true diff --git a/src/triggers/system-map/triggers.json b/src/triggers/system-map/triggers.json new file mode 100644 index 0000000..345d62e --- /dev/null +++ b/src/triggers/system-map/triggers.json @@ -0,0 +1,45 @@ +[ + { + "name": "system-map-radar", + "isActive": "no", + "patterns": [ + { + "pattern": "^(.*)\\s+([-0-9]+) ([-0-9]+) ([-0-9]+)$", + "type": "regex" + } + ], + "children": [ + { + "name": "radar-blank-line", + "patterns": [ + { + "pattern": "^$", + "type": "regex" + } + ] + }, + { + "name": "radar-item", + "patterns": [ + { + "pattern": "^(.*)\\s+([-0-9]+) ([-0-9]+) ([-0-9]+)$", + "type": "regex" + } + ] + } + ] + }, + { + "name": "auto-radar", + "patterns": [ + { + "pattern": "The ship leaves the platform far behind as it flies into space", + "type": "substring" + }, + { + "pattern": "Hyperjump complete", + "type": "substring" + } + ] + } +] \ No newline at end of file