commit 560768bc61e884aa1cc2293be6415c19c2c56639 Author: Matt Wagner Date: Sat Oct 24 23:38:05 2020 -0700 Initial commit 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 0000000..b5dd7fa Binary files /dev/null and b/src/resources/space.jpg differ 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