Dateien nach "/" hochladen
This commit is contained in:
+322
@@ -0,0 +1,322 @@
|
||||
-- Freewood-OctoWoW-Codex: Ortspunkte (Eingang zu Instanz oder Hoehle, Berg-Aufstieg, Engstelle, Zielpunkt)
|
||||
--
|
||||
-- Berechtigte (Modul Freewood-OctoWoW-Dev, Rolle Bearbeiter oder hoeher) erfassen einzelne Punkte, an denen der Levelplan vorbeimuss:
|
||||
-- zum Beispiel der Eingang einer Instanz fuer eine Dungeon-Quest. Ein Punkt gehoert zu einer oder mehreren Quests (q) und/oder
|
||||
-- zu einem ganzen Gebiet (am = Karte; negativ = Instanz). Gespeichert wird in db.spots[id]; das normale Teilen verschickt sie als
|
||||
-- Datensatz "h<ID>", angenommen nur von Absendern, die selbst berechtigt sind.
|
||||
--
|
||||
-- db.spots[id] = { k = Art, f = "A" | "H" | nil, n = Name, p = { m, x, y }, q = { Quest-IDs }, am = Gebiet, del, upd }
|
||||
-- Die ID ist ein Pruefwert aus Fraktion und Ort (auf etwa 0,5 % gerundet): wer an derselben Stelle erneut speichert, aendert den Punkt.
|
||||
|
||||
local ADDON, NS = ...
|
||||
|
||||
local FW = _G.Freewood
|
||||
local Codex = NS and NS.Codex
|
||||
if not FW or not Codex then
|
||||
return
|
||||
end
|
||||
|
||||
local Spots = { draft = { q = {} }, rev = 0 }
|
||||
Codex.Spots = Spots
|
||||
NS.Spots = Spots
|
||||
|
||||
Spots.KINDS = { "instance", "cave", "mountain", "pass", "point", "other" }
|
||||
local KIND_OK = {}
|
||||
for _, k in ipairs(Spots.KINDS) do KIND_OK[k] = true end
|
||||
Spots.MAX_QUESTS = 20
|
||||
|
||||
local function Now() return _G.time and _G.time() or 0 end
|
||||
|
||||
local function DB()
|
||||
local db = Codex.db
|
||||
if not db then return nil end
|
||||
db.spots = db.spots or {}
|
||||
return db
|
||||
end
|
||||
|
||||
local function CanEdit()
|
||||
return Codex.Trust ~= nil and Codex.Trust.CanEdit()
|
||||
end
|
||||
|
||||
local function Round(v, f) return math.floor(v * f + 0.5) end
|
||||
|
||||
local function Hash(text)
|
||||
local h = 5381
|
||||
for i = 1, #text do h = (h * 33 + text:byte(i)) % 99999989 end
|
||||
return h
|
||||
end
|
||||
|
||||
function Spots.Id(rec)
|
||||
local p = rec.p or {}
|
||||
return Hash(table.concat({ "s", tostring(rec.f or "-"), tostring(p.m), Round(p.x or 0, 200), Round(p.y or 0, 200) }, "|"))
|
||||
end
|
||||
|
||||
-- Aenderung melden (auch fuer empfangene Datensaetze: ruft CodexShare auf)
|
||||
function Spots.Bump(id)
|
||||
Spots.rev = Spots.rev + 1
|
||||
Spots.index = nil
|
||||
FW:Fire("CODEX_SPOTS_CHANGED", id)
|
||||
end
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Entwurf
|
||||
------------------------------------------------------------------------
|
||||
|
||||
function Spots.Draft()
|
||||
return Spots.draft
|
||||
end
|
||||
|
||||
function Spots.Reset()
|
||||
local d = Spots.draft
|
||||
d.p, d.am, d.kind, d.faction, d.name, d.editing = nil, nil, nil, nil, nil, nil
|
||||
d.q = {}
|
||||
FW:Fire("CODEX_SPOTS_DRAFT")
|
||||
end
|
||||
|
||||
-- Ort des Spielers { m, x, y } oder nil
|
||||
function Spots.Here()
|
||||
local T = Codex.Transport
|
||||
if T and T.Here then return T.Here() end
|
||||
return nil
|
||||
end
|
||||
|
||||
function Spots.Capture()
|
||||
local here = Spots.Here()
|
||||
if not here then return false end
|
||||
Spots.draft.p = here
|
||||
FW:Fire("CODEX_SPOTS_DRAFT")
|
||||
return true
|
||||
end
|
||||
|
||||
-- Gebiet, in dem der Spieler gerade steht: Instanz (negativ) oder Karte
|
||||
function Spots.AreaHere()
|
||||
local inst = Codex.CurrentInstance and Codex.CurrentInstance()
|
||||
if inst then return -inst end
|
||||
local here = Spots.Here()
|
||||
return here and here.m or nil
|
||||
end
|
||||
|
||||
function Spots.SetArea(am)
|
||||
Spots.draft.am = am
|
||||
FW:Fire("CODEX_SPOTS_DRAFT")
|
||||
end
|
||||
|
||||
function Spots.AddQuest(id)
|
||||
id = tonumber(id)
|
||||
local d = Spots.draft
|
||||
if not id or id < 1 or id ~= math.floor(id) or #d.q >= Spots.MAX_QUESTS then return false end
|
||||
for _, v in ipairs(d.q) do if v == id then return true end end
|
||||
d.q[#d.q + 1] = id
|
||||
FW:Fire("CODEX_SPOTS_DRAFT")
|
||||
return true
|
||||
end
|
||||
|
||||
function Spots.RemoveQuest(id)
|
||||
local d = Spots.draft
|
||||
for i, v in ipairs(d.q) do
|
||||
if v == id then table.remove(d.q, i) FW:Fire("CODEX_SPOTS_DRAFT") return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- Quests zu einem Suchtext (Nummer oder Teil des Namens): { { id, title } }, hoechstens limit
|
||||
function Spots.FindQuests(text, limit)
|
||||
limit = limit or 8
|
||||
local out = {}
|
||||
text = tostring(text or ""):gsub("^%s+", ""):gsub("%s+$", "")
|
||||
if text == "" then return out end
|
||||
local num = tonumber(text)
|
||||
local seed = Codex.Seed and Codex.Seed.Ready and Codex.Seed.Ready() and Codex.Seed or nil
|
||||
local V = Codex.V and Codex.V()
|
||||
local function Title(id)
|
||||
return Codex.QuestTitle(V and V.quests[id], id)
|
||||
end
|
||||
if num then
|
||||
out[1] = { id = num, title = Title(num) }
|
||||
return out
|
||||
end
|
||||
local needle = text:lower()
|
||||
local seen = {}
|
||||
for id, q in pairs(V and V.quests or {}) do
|
||||
local t = Title(id)
|
||||
if t and t:lower():find(needle, 1, true) then
|
||||
seen[id] = true
|
||||
out[#out + 1] = { id = id, title = t }
|
||||
end
|
||||
end
|
||||
if seed and seed.IDs then
|
||||
for _, id in ipairs(seed.IDs("quests")) do
|
||||
if not seen[id] and seed.Haystack("quests", id):find(needle, 1, true) then
|
||||
out[#out + 1] = { id = id, title = Title(id) }
|
||||
end
|
||||
end
|
||||
end
|
||||
table.sort(out, function(a, b)
|
||||
if a.title ~= b.title then return a.title < b.title end
|
||||
return a.id < b.id
|
||||
end)
|
||||
while #out > limit do out[#out] = nil end
|
||||
return out
|
||||
end
|
||||
|
||||
-- Entwurf pruefen. Rueckgabe: true oder false, Grund
|
||||
function Spots.CheckDraft()
|
||||
local d = Spots.draft
|
||||
if not d.p then return false, "pos" end
|
||||
if not KIND_OK[d.kind or "instance"] then return false, "kind" end
|
||||
if not d.am and #d.q == 0 then return false, "link" end
|
||||
return true
|
||||
end
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Speichern, Bearbeiten, Loeschen
|
||||
------------------------------------------------------------------------
|
||||
|
||||
local function Touch(id)
|
||||
if Codex.Share and Codex.Share.InvalidateScan then Codex.Share.InvalidateScan() end
|
||||
Spots.Bump(id)
|
||||
FW:Fire("CODEX_CHANGED", "spots", id)
|
||||
end
|
||||
|
||||
local function Describe(rec)
|
||||
local M = Codex.MapName
|
||||
return string.format("%s: %s (%.1f / %.1f)%s", tostring(rec.k), M and M(rec.p.m) or rec.p.m, rec.p.x * 100, rec.p.y * 100,
|
||||
rec.am and (" > " .. (M and M(rec.am) or rec.am)) or "")
|
||||
end
|
||||
|
||||
local function Log(id, field, old, new)
|
||||
if Codex.DevLog then
|
||||
pcall(Codex.DevLog, { who = Codex.Trust and Codex.Trust.SelfKey() or "?", kind = "h", id = id, field = field, old = old, new = new })
|
||||
end
|
||||
end
|
||||
|
||||
-- Rueckgabe: id oder nil, Grund
|
||||
function Spots.Save()
|
||||
if not CanEdit() then return nil, "rights" end
|
||||
local ok, why = Spots.CheckDraft()
|
||||
if not ok then return nil, why end
|
||||
local d = Spots.draft
|
||||
local q = {}
|
||||
for _, v in ipairs(d.q) do q[#q + 1] = v end
|
||||
table.sort(q)
|
||||
local name = type(d.name) == "string" and d.name:sub(1, 40) or ""
|
||||
local rec = {
|
||||
k = d.kind or "instance", f = (d.faction == "A" or d.faction == "H") and d.faction or nil,
|
||||
n = name ~= "" and name or nil, p = { m = d.p.m, x = d.p.x, y = d.p.y }, q = q, am = d.am, upd = Now(),
|
||||
}
|
||||
local db = DB()
|
||||
local id = Spots.Id(rec)
|
||||
local old = db.spots[id]
|
||||
rec.upd = math.max(rec.upd, (old and old.upd or 0) + 1)
|
||||
db.spots[id] = rec
|
||||
Log(id, "add", old and not old.del and Describe(old) or nil, Describe(rec))
|
||||
Touch(id)
|
||||
Spots.Reset()
|
||||
return id
|
||||
end
|
||||
|
||||
-- Liste { id, k, f, n, p, q, am, upd, del }; je ID der neueste Stand; geloeschte nur mit withDeleted
|
||||
function Spots.All(withDeleted)
|
||||
local best = {}
|
||||
local db = DB()
|
||||
if not db then return {} end
|
||||
for id, rec in pairs(db.spots) do
|
||||
if type(rec) == "table" and rec.p then best[id] = rec end
|
||||
end
|
||||
if Codex.Share and Codex.Share.RemoteRecords then
|
||||
for _, rec in ipairs(Codex.Share.RemoteRecords("h")) do
|
||||
local cur = best[rec.id]
|
||||
if not cur or (rec.upd or 0) > (cur.upd or 0) then best[rec.id] = rec end
|
||||
end
|
||||
end
|
||||
local out = {}
|
||||
for id, rec in pairs(best) do
|
||||
if withDeleted or not rec.del then
|
||||
out[#out + 1] = { id = id, k = rec.k, f = rec.f, n = rec.n, p = rec.p, q = rec.q or {}, am = rec.am, upd = rec.upd, del = rec.del }
|
||||
end
|
||||
end
|
||||
table.sort(out, function(x, y)
|
||||
if x.p.m ~= y.p.m then return x.p.m < y.p.m end
|
||||
return x.id < y.id
|
||||
end)
|
||||
return out
|
||||
end
|
||||
|
||||
function Spots.Get(id)
|
||||
for _, r in ipairs(Spots.All(true)) do
|
||||
if r.id == id then return r end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Punkt in den Entwurf laden (zum Aendern); Speichern an derselben Stelle ersetzt ihn
|
||||
function Spots.Edit(id)
|
||||
local r = Spots.Get(id)
|
||||
if not r or r.del then return false end
|
||||
local d = Spots.draft
|
||||
d.p = { m = r.p.m, x = r.p.x, y = r.p.y }
|
||||
d.kind, d.faction, d.name, d.am, d.editing = r.k, r.f, r.n, r.am, id
|
||||
d.q = {}
|
||||
for _, v in ipairs(r.q) do d.q[#d.q + 1] = v end
|
||||
FW:Fire("CODEX_SPOTS_DRAFT")
|
||||
return true
|
||||
end
|
||||
|
||||
function Spots.Delete(id)
|
||||
if not CanEdit() then return false end
|
||||
local rec = Spots.Get(id)
|
||||
if not rec then return false end
|
||||
local db = DB()
|
||||
db.spots[id] = { k = rec.k, f = rec.f, n = rec.n, p = rec.p, q = rec.q, am = rec.am, del = true,
|
||||
upd = math.max(Now(), (rec.upd or 0) + 1) }
|
||||
Log(id, "del", Describe(rec), nil)
|
||||
Touch(id)
|
||||
return true
|
||||
end
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Lesen fuer den Levelplan
|
||||
------------------------------------------------------------------------
|
||||
|
||||
local EMPTY = {}
|
||||
|
||||
local function BuildIndex()
|
||||
local idx = { quest = {}, area = {} }
|
||||
for _, r in ipairs(Spots.All()) do
|
||||
local s = { id = r.id, k = r.k, f = r.f, n = r.n, m = r.p.m, x = r.p.x, y = r.p.y }
|
||||
for _, qid in ipairs(r.q) do
|
||||
idx.quest[qid] = idx.quest[qid] or {}
|
||||
table.insert(idx.quest[qid], s)
|
||||
end
|
||||
if r.am then
|
||||
idx.area[r.am] = idx.area[r.am] or {}
|
||||
table.insert(idx.area[r.am], s)
|
||||
end
|
||||
end
|
||||
idx.empty = next(idx.quest) == nil and next(idx.area) == nil
|
||||
return idx
|
||||
end
|
||||
|
||||
-- Punkte, die fuer ein Ziel gelten: Quest-ID (oder nil) und Karte des Ziels (Instanzen negativ); faction = "A" | "H" | nil.
|
||||
-- Rueckgabe: Liste { id, k, n, m, x, y } (leer, wenn nichts passt)
|
||||
function Spots.For(questID, m, faction)
|
||||
if not Spots.index then Spots.index = BuildIndex() end
|
||||
local idx = Spots.index
|
||||
if idx.empty then return EMPTY end
|
||||
local out, seen = {}, {}
|
||||
local function Add(list)
|
||||
for _, s in ipairs(list or {}) do
|
||||
if not seen[s.id] and not (faction and s.f and s.f ~= faction) then
|
||||
seen[s.id] = true
|
||||
out[#out + 1] = s
|
||||
end
|
||||
end
|
||||
end
|
||||
if questID then Add(idx.quest[questID]) end
|
||||
if m then Add(idx.area[m]) end
|
||||
return out
|
||||
end
|
||||
|
||||
function Spots.Count()
|
||||
return #Spots.All()
|
||||
end
|
||||
+1262
File diff suppressed because it is too large
Load Diff
+505
@@ -0,0 +1,505 @@
|
||||
-- Freewood-OctoWoW-Codex: Flugrouten (Taxi)
|
||||
--
|
||||
-- Erfasst beim Flug: Start- und Zielpunkt, die Strecke (Punkte alle paar Sekunden), die Flugdauer und den bezahlten Preis.
|
||||
-- Je Charakter: welche Flugpunkte er kennt, wie viel Gold er fuer Fluege ausgegeben hat und wie oft er geflogen ist.
|
||||
--
|
||||
-- db.taxi.nodes[id] = { n = Name, w = Welt, tx, ty = Position auf der Flugkarte, a, b = Weltposition (erster, zweiter Wert von UnitPosition) }
|
||||
-- db.taxi.routes[id] = { a = Start-ID, b = Ziel-ID, t = Sekunden, c = Preis in Kupfer, n = Anzahl, p = { a1, b1, a2, b2, ... }, upd = Stand }
|
||||
-- db.taxiChar[Charakter] = { known = { [id] = true }, spent = Kupfer, trips = Anzahl, log = { { t, a, b, c, d }, ... } }
|
||||
-- Die Flugpunkt-ID kommt aus der Position auf der Flugkarte (sprachunabhaengig), sonst aus dem Namen.
|
||||
-- Zwischenlandungen: aufgezeichnet wird die Gesamtstrecke bis zum gewaehlten Ziel.
|
||||
|
||||
local ADDON, NS = ...
|
||||
|
||||
local FW = _G.Freewood
|
||||
local Codex = NS and NS.Codex
|
||||
if not FW or not Codex then
|
||||
return
|
||||
end
|
||||
|
||||
local Taxi = { state = {} }
|
||||
Codex.Taxi = Taxi
|
||||
NS.Taxi = Taxi
|
||||
local state = Taxi.state
|
||||
|
||||
local SAMPLE = 3 -- Sekunden zwischen zwei Streckenpunkten
|
||||
local MAX_POINTS = 120 -- Punkte je Route (je 2 Zahlen)
|
||||
local MAX_ROUTES = 600
|
||||
local MAX_NODES = 300
|
||||
local MAX_LOG = 30
|
||||
local PRICE_WINDOW = 6 -- Sekunden nach dem Start, in denen der abgezogene Betrag nachgemessen wird
|
||||
|
||||
local function Clock() return _G.GetTime and _G.GetTime() or 0 end
|
||||
local function Now() return _G.time and _G.time() or 0 end
|
||||
local function Round(v) return math.floor((v or 0) + 0.5) end
|
||||
|
||||
local function DB()
|
||||
local db = Codex.db
|
||||
if not db then return nil end
|
||||
local t = db.taxi
|
||||
if type(t) ~= "table" then t = {}; db.taxi = t end
|
||||
t.nodes = t.nodes or {}
|
||||
t.routes = t.routes or {}
|
||||
return t
|
||||
end
|
||||
|
||||
local function CharRec(create)
|
||||
local db = Codex.db
|
||||
local key = Codex.Trust and Codex.Trust.SelfKey and Codex.Trust.SelfKey() or nil
|
||||
if not db or not key then return nil end
|
||||
db.taxiChar = db.taxiChar or {}
|
||||
local r = db.taxiChar[key]
|
||||
if not r and create ~= false then
|
||||
r = { known = {}, spent = 0, trips = 0, log = {} }
|
||||
db.taxiChar[key] = r
|
||||
end
|
||||
return r
|
||||
end
|
||||
|
||||
function Taxi.Money(c)
|
||||
c = math.floor(tonumber(c) or 0)
|
||||
local g, s, k = math.floor(c / 10000), math.floor(c / 100) % 100, c % 100
|
||||
local out = {}
|
||||
if g > 0 then out[#out + 1] = g .. "g" end
|
||||
if s > 0 then out[#out + 1] = s .. "s" end
|
||||
if k > 0 or #out == 0 then out[#out + 1] = k .. "c" end
|
||||
return table.concat(out, " ")
|
||||
end
|
||||
|
||||
function Taxi.Duration(sec)
|
||||
sec = math.floor(tonumber(sec) or 0)
|
||||
return string.format("%d:%02d", math.floor(sec / 60), sec % 60)
|
||||
end
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Abfragen (fuer Karte und Planer)
|
||||
------------------------------------------------------------------------
|
||||
|
||||
Taxi.rev = 0
|
||||
local merged
|
||||
|
||||
-- Eigene und empfangene Routen zusammen (eigene haben Vorrang)
|
||||
function Taxi.Routes()
|
||||
local t = DB()
|
||||
if not t then return {} end
|
||||
local rem = Codex.db.taxir
|
||||
if not rem or next(rem) == nil then return t.routes end
|
||||
if merged and merged.rev == Taxi.rev then return merged.routes end
|
||||
local out = {}
|
||||
for k, r in pairs(rem) do out[k] = r end
|
||||
for k, r in pairs(t.routes) do out[k] = r end
|
||||
merged = { rev = Taxi.rev, routes = out }
|
||||
return out
|
||||
end
|
||||
|
||||
function Taxi.Node(id)
|
||||
local t = DB()
|
||||
if not t then return nil end
|
||||
local n = t.nodes[id]
|
||||
if n then return n end
|
||||
local rn = Codex.db.taxin
|
||||
return rn and rn[id] or nil
|
||||
end
|
||||
|
||||
-- Kennt dieser Charakter den Flugpunkt?
|
||||
function Taxi.Known(id)
|
||||
local c = CharRec(false)
|
||||
return c and c.known and c.known[id] and true or false
|
||||
end
|
||||
|
||||
function Taxi.CharStats()
|
||||
local c = CharRec(false)
|
||||
if not c then return nil end
|
||||
local n = 0
|
||||
for _ in pairs(c.known or {}) do n = n + 1 end
|
||||
return { known = n, spent = c.spent or 0, trips = c.trips or 0, secs = c.secs or 0, log = c.log, routes = c.rt }
|
||||
end
|
||||
|
||||
-- Wie oft dieser Charakter die Route geflogen ist und wie lange insgesamt: n, Sekunden
|
||||
function Taxi.RouteStat(a, b)
|
||||
local c = CharRec(false)
|
||||
local r = c and c.rt and c.rt[a .. ">" .. b]
|
||||
if not r then return 0, 0 end
|
||||
return r.n or 0, r.sec or 0
|
||||
end
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Flugkarte lesen
|
||||
------------------------------------------------------------------------
|
||||
|
||||
local function NodeId(i, world)
|
||||
if _G.TaxiNodePosition then
|
||||
local ok, x, y = pcall(_G.TaxiNodePosition, i)
|
||||
if ok and type(x) == "number" and type(y) == "number" then
|
||||
return string.format("%d:%d:%d", world or 0, Round(x * 1000), Round(y * 1000)), x, y
|
||||
end
|
||||
end
|
||||
local name = _G.TaxiNodeName and _G.TaxiNodeName(i)
|
||||
if type(name) == "string" and name ~= "" then return "n:" .. name, nil, nil end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Kartenposition des Flugpunkts (Karte, x, y), solange der Spieler dort steht; fuer den Levelplaner
|
||||
local function SetMapPos(n)
|
||||
local loc = FW.Util and FW.Util.GetPlayerLocation and FW.Util.GetPlayerLocation()
|
||||
if loc and loc.mapID and loc.x and loc.y and loc.x > 0 and loc.y > 0 then
|
||||
if n.m ~= loc.mapID or not n.x then
|
||||
n.m, n.x, n.y = loc.mapID, loc.x, loc.y
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function Taxi.Scan()
|
||||
local count = _G.NumTaxiNodes and _G.NumTaxiNodes() or 0
|
||||
if type(count) ~= "number" or count <= 0 then return end
|
||||
local T, C = DB(), CharRec(true)
|
||||
if not T or not C then return end
|
||||
local P = Codex.Path and Codex.Path.Read and Codex.Path.Read()
|
||||
local world = P and P.world or 0
|
||||
local nodes = 0
|
||||
for _ in pairs(T.nodes) do nodes = nodes + 1 end
|
||||
state.slot, state.cur = {}, nil
|
||||
local changed = false
|
||||
for i = 1, math.min(count, 120) do
|
||||
local kind = _G.TaxiNodeGetType and _G.TaxiNodeGetType(i)
|
||||
local name = _G.TaxiNodeName and _G.TaxiNodeName(i)
|
||||
local id, tx, ty = NodeId(i, world)
|
||||
if id then
|
||||
local n = T.nodes[id]
|
||||
if not n and nodes < MAX_NODES then
|
||||
n = {}
|
||||
T.nodes[id] = n
|
||||
nodes = nodes + 1
|
||||
end
|
||||
if n then
|
||||
if type(name) == "string" and name ~= "" then n.n = name:sub(1, 60) end
|
||||
n.w, n.tx, n.ty = world, tx, ty
|
||||
end
|
||||
if kind ~= "NONE" and not C.known[id] then C.known[id] = true; changed = true end
|
||||
if kind == "CURRENT" then
|
||||
state.cur = id
|
||||
if n and P then n.a, n.b = Round(P.y), Round(P.x) end
|
||||
if n and SetMapPos(n) then changed = true end
|
||||
end
|
||||
local cost = _G.TaxiNodeCost and tonumber(_G.TaxiNodeCost(i)) or nil
|
||||
state.slot[i] = { id = id, cost = cost and math.floor(cost) or nil, kind = kind }
|
||||
end
|
||||
end
|
||||
if changed then
|
||||
Taxi.rev = Taxi.rev + 1
|
||||
FW:Fire("CODEX_TAXI_CHANGED")
|
||||
end
|
||||
if Taxi.OnOpen then pcall(Taxi.OnOpen) end
|
||||
end
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Teilen (Share/Sync, Datensatzart "f", je Route ein Datensatz)
|
||||
------------------------------------------------------------------------
|
||||
-- Zahl zur Route: Flugpunkt-Zahl (Welt, x, y) * 10^7 + Flugpunkt-Zahl des Ziels. Nur Flugpunkte mit Positions-ID sind teilbar.
|
||||
|
||||
local NODE_W = 1002001
|
||||
local SHARE_POINTS = 40
|
||||
|
||||
local function NodeNum(id)
|
||||
local w, x, y = tostring(id):match("^(%d+):(%d+):(%d+)$")
|
||||
w, x, y = tonumber(w), tonumber(x), tonumber(y)
|
||||
if not w or w > 8 or x > 1000 or y > 1000 then return nil end
|
||||
return w * NODE_W + x * 1001 + y
|
||||
end
|
||||
|
||||
local function NodeFromNum(n)
|
||||
if type(n) ~= "number" or n < 0 or n >= 9 * NODE_W then return nil end
|
||||
local w, r = math.floor(n / NODE_W), n % NODE_W
|
||||
return string.format("%d:%d:%d", w, math.floor(r / 1001), r % 1001)
|
||||
end
|
||||
|
||||
local function RouteNum(a, b)
|
||||
local na, nb = NodeNum(a), NodeNum(b)
|
||||
if not na or not nb then return nil end
|
||||
return na * 10000000 + nb
|
||||
end
|
||||
|
||||
local function RouteIds(num)
|
||||
local a, b = NodeFromNum(math.floor(num / 10000000)), NodeFromNum(num % 10000000)
|
||||
if not a or not b then return nil end
|
||||
return a, b
|
||||
end
|
||||
|
||||
function Taxi.SyncList()
|
||||
local out = {}
|
||||
local t = DB()
|
||||
if not t then return out end
|
||||
for _, r in pairs(t.routes) do
|
||||
local num = r.upd and RouteNum(r.a, r.b)
|
||||
if num then out[#out + 1] = { key = "f" .. string.format("%.0f", num), upd = r.upd } end
|
||||
end
|
||||
return out
|
||||
end
|
||||
|
||||
local function NodeShare(id)
|
||||
local n = Taxi.Node(id) or {}
|
||||
return { n = n.n, a = n.a, b = n.b, m = n.m, x = n.x, y = n.y }
|
||||
end
|
||||
|
||||
-- Eigene Route als Datensatz (Strecke auf hoechstens SHARE_POINTS Punkte verkleinert)
|
||||
function Taxi.RouteRecord(num)
|
||||
local a, b = RouteIds(num)
|
||||
local t = DB()
|
||||
local r = a and t and t.routes[a .. ">" .. b]
|
||||
if not r or not r.upd or type(r.p) ~= "table" then return nil end
|
||||
local pts, m = {}, math.floor(#r.p / 2)
|
||||
if m < 2 then return nil end
|
||||
local take = math.min(m, SHARE_POINTS)
|
||||
for i = 1, take do
|
||||
local j = (take == 1) and 1 or (1 + math.floor((i - 1) * (m - 1) / (take - 1) + 0.5))
|
||||
pts[#pts + 1] = r.p[j * 2 - 1]
|
||||
pts[#pts + 1] = r.p[j * 2]
|
||||
end
|
||||
return { t = r.t, c = r.c, n = r.n or 1, p = pts, A = NodeShare(a), B = NodeShare(b) }, r.upd
|
||||
end
|
||||
|
||||
local function NodeFromRecord(id, rn)
|
||||
local w, x, y = tostring(id):match("^(%d+):(%d+):(%d+)$")
|
||||
return { n = rn.n, w = tonumber(w), tx = tonumber(x) / 1000, ty = tonumber(y) / 1000, a = rn.a, b = rn.b, m = rn.m, x = rn.x, y = rn.y }
|
||||
end
|
||||
|
||||
-- Empfangene Route einmischen. Rueckgabe: true, wenn gespeichert
|
||||
function Taxi.StoreRemote(rec)
|
||||
local db = Codex.db
|
||||
if not db or type(rec) ~= "table" then return false end
|
||||
local a, b = RouteIds(rec.id)
|
||||
if not a then return false end
|
||||
db.taxir = db.taxir or {}
|
||||
db.taxin = db.taxin or {}
|
||||
local rid = a .. ">" .. b
|
||||
local old = db.taxir[rid]
|
||||
if old and (old.upd or 0) >= rec.upd then return false end
|
||||
if not old then
|
||||
local count = 0
|
||||
for _ in pairs(db.taxir) do count = count + 1 end
|
||||
if count >= MAX_ROUTES then return false end
|
||||
end
|
||||
db.taxir[rid] = { a = a, b = b, t = rec.t, c = rec.c, n = rec.n, p = rec.p, upd = rec.upd }
|
||||
local own = DB()
|
||||
for id, rn in pairs({ [a] = rec.A, [b] = rec.B }) do
|
||||
if not (own and own.nodes[id]) and not db.taxin[id] then
|
||||
local nodes = 0
|
||||
for _ in pairs(db.taxin) do nodes = nodes + 1 end
|
||||
if nodes < MAX_NODES then db.taxin[id] = NodeFromRecord(id, rn) end
|
||||
end
|
||||
end
|
||||
Taxi.rev = Taxi.rev + 1
|
||||
return true
|
||||
end
|
||||
|
||||
-- Levelplaner: Fluege als Kanten { id, k, t, wait, from = { m, x, y }, to = { m, x, y } }, nur zwischen Flugpunkten,
|
||||
-- die dieser Charakter kennt und deren Kartenposition bekannt ist
|
||||
function Taxi.PlannerOn()
|
||||
local db = Codex.db
|
||||
return not (db and db.settings and db.settings.taxiPlan == false)
|
||||
end
|
||||
|
||||
local edgeCache
|
||||
function Taxi.Edges()
|
||||
if not Taxi.PlannerOn() then return {} end
|
||||
local key = Codex.Trust and Codex.Trust.SelfKey and Codex.Trust.SelfKey() or "?"
|
||||
if edgeCache and edgeCache.rev == Taxi.rev and edgeCache.key == key then return edgeCache.list end
|
||||
local list = {}
|
||||
for rid, r in pairs(Taxi.Routes()) do
|
||||
local na, nb = Taxi.Node(r.a), Taxi.Node(r.b)
|
||||
if na and nb and na.m and nb.m and r.t and Taxi.Known(r.a) and Taxi.Known(r.b) then
|
||||
list[#list + 1] = { id = "f" .. rid, k = "flight", t = r.t, wait = 20, name = nb.n, fa = r.a, fb = r.b,
|
||||
from = { m = na.m, x = na.x, y = na.y }, to = { m = nb.m, x = nb.x, y = nb.y } }
|
||||
end
|
||||
end
|
||||
edgeCache = { rev = Taxi.rev, key = key, list = list }
|
||||
return list
|
||||
end
|
||||
|
||||
function Taxi.ClearRemote()
|
||||
local db = Codex.db
|
||||
if db then db.taxir, db.taxin = nil, nil end
|
||||
Taxi.rev = Taxi.rev + 1
|
||||
end
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- Flug erfassen
|
||||
------------------------------------------------------------------------
|
||||
|
||||
local function OnTake(slot)
|
||||
local s = state.slot and state.slot[slot]
|
||||
if not s or not state.cur then return end
|
||||
state.pending = { from = state.cur, to = s.id, quote = s.cost or 0, money0 = _G.GetMoney and _G.GetMoney() or 0,
|
||||
asked = Clock(), started = false }
|
||||
end
|
||||
|
||||
local function Finish(p, now)
|
||||
state.pending = nil
|
||||
Taxi.flight = nil
|
||||
local T, C = DB(), CharRec(true)
|
||||
if not T or not C then return end
|
||||
local dur = now - p.t0
|
||||
if dur < 5 then return end
|
||||
-- Endpunkt: Position der Landung
|
||||
local P = Codex.Path and Codex.Path.Read and Codex.Path.Read()
|
||||
if P then
|
||||
p.pts[#p.pts + 1] = Round(P.y)
|
||||
p.pts[#p.pts + 1] = Round(P.x)
|
||||
local n = T.nodes[p.to]
|
||||
if n and not n.a then n.a, n.b = Round(P.y), Round(P.x) end
|
||||
if n and not n.m then SetMapPos(n) end
|
||||
end
|
||||
local paid = p.paid or 0
|
||||
if paid <= 0 or paid > p.quote * 3 + 100 then paid = p.quote or 0 end
|
||||
local rid = p.from .. ">" .. p.to
|
||||
local r = T.routes[rid]
|
||||
local isNew = false
|
||||
if not r then
|
||||
local count = 0
|
||||
for _ in pairs(T.routes) do count = count + 1 end
|
||||
if count >= MAX_ROUTES then return end
|
||||
r = { a = p.from, b = p.to, t = Round(dur), c = paid, n = 1, p = p.pts, upd = Now() }
|
||||
T.routes[rid] = r
|
||||
isNew = true
|
||||
else
|
||||
r.n = (r.n or 1) + 1
|
||||
r.t = Round((r.t * (r.n - 1) + dur) / r.n)
|
||||
r.c = paid
|
||||
if #p.pts > #(r.p or {}) then r.p = p.pts end
|
||||
r.upd = Now()
|
||||
end
|
||||
C.known[p.to] = true
|
||||
C.spent = (C.spent or 0) + paid
|
||||
C.trips = (C.trips or 0) + 1
|
||||
C.secs = (C.secs or 0) + Round(dur)
|
||||
C.rt = C.rt or {}
|
||||
local rs = C.rt[rid]
|
||||
if not rs then rs = { n = 0, sec = 0, gold = 0 }; C.rt[rid] = rs end
|
||||
rs.n, rs.sec, rs.gold = rs.n + 1, rs.sec + Round(dur), (rs.gold or 0) + paid
|
||||
C.log = C.log or {}
|
||||
C.log[#C.log + 1] = { t = Now(), a = p.from, b = p.to, c = paid, d = Round(dur) }
|
||||
while #C.log > MAX_LOG do table.remove(C.log, 1) end
|
||||
if isNew then
|
||||
local na, nb = T.nodes[p.from], T.nodes[p.to]
|
||||
FW:Print(string.format("Codex: Flugroute %s -> %s gespeichert (%s, %s, %d Punkte)",
|
||||
na and na.n or p.from, nb and nb.n or p.to, Taxi.Duration(dur), Taxi.Money(paid), math.floor(#p.pts / 2)))
|
||||
end
|
||||
Taxi.rev = Taxi.rev + 1
|
||||
FW:Fire("CODEX_TAXI_CHANGED")
|
||||
FW:Fire("CODEX_CHANGED", "taxi")
|
||||
end
|
||||
|
||||
local function Tick()
|
||||
local p = state.pending
|
||||
if not p then return end
|
||||
local now = Clock()
|
||||
local on = _G.UnitOnTaxi and _G.UnitOnTaxi("player") and true or false
|
||||
if on and not p.started then
|
||||
p.started, p.t0, p.nextAt, p.pts = true, now, now, {}
|
||||
local r = Taxi.Routes()[p.from .. ">" .. p.to]
|
||||
local nd = Taxi.Node(p.to)
|
||||
Taxi.flight = { t0 = now, exp = r and r.t or nil, name = nd and nd.n or "?" }
|
||||
end
|
||||
if p.started then
|
||||
if on then
|
||||
if now - p.t0 <= PRICE_WINDOW then
|
||||
local d = p.money0 - (_G.GetMoney and _G.GetMoney() or 0)
|
||||
if d > (p.paid or 0) then p.paid = d end
|
||||
end
|
||||
if now >= p.nextAt and #p.pts < MAX_POINTS * 2 then
|
||||
local P = Codex.Path and Codex.Path.Read and Codex.Path.Read()
|
||||
if P then
|
||||
p.pts[#p.pts + 1] = Round(P.y)
|
||||
p.pts[#p.pts + 1] = Round(P.x)
|
||||
end
|
||||
p.nextAt = now + SAMPLE
|
||||
end
|
||||
else
|
||||
Finish(p, now)
|
||||
end
|
||||
elseif now - p.asked > 15 then
|
||||
state.pending = nil -- Flug nicht angetreten
|
||||
Taxi.flight = nil
|
||||
end
|
||||
end
|
||||
|
||||
local acc = 0
|
||||
local frame = _G.CreateFrame and _G.CreateFrame("Frame")
|
||||
if frame then
|
||||
frame:SetScript("OnUpdate", function(_, elapsed)
|
||||
acc = acc + (elapsed or 0)
|
||||
if acc < 1 then return end
|
||||
acc = 0
|
||||
pcall(Tick)
|
||||
end)
|
||||
end
|
||||
|
||||
-- Tooltip am Flugziel auf der Flugkarte: Reisedauer (und wie oft schon geflogen)
|
||||
function Taxi.ButtonTip(button)
|
||||
if not (button and button.GetID and state.cur and state.slot) then return end
|
||||
local s = state.slot[button:GetID()]
|
||||
local tt = _G.GameTooltip
|
||||
if not s or not tt or s.id == state.cur then return end
|
||||
local r = Taxi.Routes()[state.cur .. ">" .. s.id]
|
||||
local L = FW.L
|
||||
if r and r.t then
|
||||
tt:AddLine(string.format(L.CODEX_TAXI_TIP_TIME, Taxi.Duration(r.t)), 0.94, 0.75, 0.36)
|
||||
else
|
||||
tt:AddLine(L.CODEX_TAXI_TIP_NONE, 0.6, 0.59, 0.55)
|
||||
end
|
||||
local n, sec = Taxi.RouteStat(state.cur, s.id)
|
||||
if n > 0 then
|
||||
tt:AddLine(string.format(L.CODEX_TAXI_TIP_COUNT, n, Taxi.Duration(sec)), 0.6, 0.59, 0.55)
|
||||
end
|
||||
tt:Show()
|
||||
end
|
||||
|
||||
local function Install()
|
||||
if Taxi.hooked then return end
|
||||
Taxi.hooked = true
|
||||
if _G.hooksecurefunc and _G.TaxiNodeOnButtonEnter then
|
||||
pcall(_G.hooksecurefunc, "TaxiNodeOnButtonEnter", function(button) pcall(Taxi.ButtonTip, button) end)
|
||||
end
|
||||
if _G.hooksecurefunc and _G.TakeTaxiNode then
|
||||
pcall(_G.hooksecurefunc, "TakeTaxiNode", function(slot) pcall(OnTake, slot) end)
|
||||
end
|
||||
FW:RegisterEvent("TAXIMAP_OPENED", function() pcall(Taxi.Scan) end, Taxi)
|
||||
end
|
||||
Install()
|
||||
|
||||
------------------------------------------------------------------------
|
||||
-- /fwtaxi
|
||||
------------------------------------------------------------------------
|
||||
|
||||
SLASH_FREEWOODTAXI1 = "/fwtaxi"
|
||||
SlashCmdList["FREEWOODTAXI"] = function(arg)
|
||||
local T = DB()
|
||||
if not T then return end
|
||||
local nodes, routes = 0, 0
|
||||
for _ in pairs(T.nodes) do nodes = nodes + 1 end
|
||||
for _ in pairs(T.routes) do routes = routes + 1 end
|
||||
if arg == "diag" then
|
||||
FW:Print(string.format("Taxi: TakeTaxiNode=%s TaxiNodePosition=%s TaxiNodeCost=%s TaxiNodeGetType=%s UnitOnTaxi=%s Hook=%s",
|
||||
tostring(_G.TakeTaxiNode ~= nil), tostring(_G.TaxiNodePosition ~= nil), tostring(_G.TaxiNodeCost ~= nil),
|
||||
tostring(_G.TaxiNodeGetType ~= nil), tostring(_G.UnitOnTaxi ~= nil), tostring(Taxi.hooked)))
|
||||
FW:Print(string.format("Taxi: aktueller Punkt=%s, Plaetze=%d, Flug laeuft=%s", tostring(state.cur), state.slot and #state.slot or 0,
|
||||
tostring(state.pending and state.pending.started or false)))
|
||||
return
|
||||
end
|
||||
local c = Taxi.CharStats()
|
||||
FW:Print(string.format("Taxi: %d Flugpunkte, %d Routen gespeichert.", nodes, routes))
|
||||
if c then
|
||||
FW:Print(string.format("Taxi: dieser Charakter kennt %d Flugpunkte, %d Fluege, %s ausgegeben, %s in der Luft.", c.known, c.trips, Taxi.Money(c.spent), Taxi.Duration(c.secs)))
|
||||
end
|
||||
local list = {}
|
||||
for _, r in pairs(T.routes) do list[#list + 1] = r end
|
||||
table.sort(list, function(x, y) return (x.upd or 0) > (y.upd or 0) end)
|
||||
for i = 1, math.min(#list, 5) do
|
||||
local r = list[i]
|
||||
local na, nb = T.nodes[r.a], T.nodes[r.b]
|
||||
FW:Print(string.format(" %s -> %s: %s, %s, %d Punkte, %dx", na and na.n or r.a, nb and nb.n or r.b, Taxi.Duration(r.t), Taxi.Money(r.c),
|
||||
math.floor(#(r.p or {}) / 2), r.n or 1))
|
||||
end
|
||||
end
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
-- Freewood-OctoWoW-Codex: Flugfenster
|
||||
--
|
||||
-- Waehrend eines Fluges mit dem Flugmeister: kleines Fenster mit Zielname, verbleibender Zeit und einem ablaufenden Balken.
|
||||
-- Ist die Flugdauer der Route noch unbekannt (noch nie geflogen oder empfangen), zaehlt es die vergangene Zeit hoch.
|
||||
|
||||
local ADDON, NS = ...
|
||||
|
||||
local FW = _G.Freewood
|
||||
local Codex = NS and NS.Codex
|
||||
local Taxi = NS and NS.Taxi
|
||||
if not FW or not Codex or not Taxi or not FW.UI then
|
||||
return
|
||||
end
|
||||
local UI = FW.UI
|
||||
|
||||
local WIDTH, HEIGHT, BAR_H = 250, 50, 8
|
||||
local win
|
||||
|
||||
local function Settings()
|
||||
local db = Codex.db
|
||||
if not db then return {} end
|
||||
db.settings = db.settings or {}
|
||||
return db.settings
|
||||
end
|
||||
|
||||
function Taxi.WindowOn()
|
||||
return Settings().taxiWindow ~= false
|
||||
end
|
||||
|
||||
local function SavePos()
|
||||
if not win then return end
|
||||
local point, _, rel, x, y = win:GetPoint(1)
|
||||
if point then Settings().taxiWinPos = { point = point, rel = rel, x = x, y = y } end
|
||||
end
|
||||
|
||||
local function ApplyPos()
|
||||
win:ClearAllPoints()
|
||||
local p = Settings().taxiWinPos
|
||||
if type(p) == "table" and p.point and p.x and p.y then
|
||||
win:SetPoint(p.point, UIParent, p.rel or p.point, p.x, p.y)
|
||||
else
|
||||
win:SetPoint("TOP", UIParent, "TOP", 0, -150)
|
||||
end
|
||||
end
|
||||
|
||||
local function Build()
|
||||
local f = UI.CreatePanel(UIParent, "bg", "gold")
|
||||
f:SetSize(WIDTH, HEIGHT)
|
||||
f:SetFrameStrata("MEDIUM")
|
||||
f:SetClampedToScreen(true)
|
||||
f:SetMovable(true)
|
||||
f:EnableMouse(true)
|
||||
f:RegisterForDrag("LeftButton")
|
||||
f:SetScript("OnDragStart", function(self) self:StartMoving() end)
|
||||
f:SetScript("OnDragStop", function(self) self:StopMovingOrSizing(); SavePos() end)
|
||||
win = f
|
||||
ApplyPos()
|
||||
|
||||
f.title = UI.CreateLabel(f, "", { size = 12, color = "goldLight" })
|
||||
f.title:SetPoint("TOPLEFT", f, "TOPLEFT", 10, -7)
|
||||
f.title:SetPoint("TOPRIGHT", f, "TOPRIGHT", -10, -7)
|
||||
f.title:SetHeight(14)
|
||||
|
||||
f.time = UI.CreateLabel(f, "", { size = 11, color = "text" })
|
||||
f.time:SetPoint("TOPLEFT", f.title, "BOTTOMLEFT", 0, -2)
|
||||
f.time:SetPoint("TOPRIGHT", f.title, "BOTTOMRIGHT", 0, -2)
|
||||
f.time:SetHeight(13)
|
||||
|
||||
f.barBg = f:CreateTexture(nil, "ARTWORK")
|
||||
f.barBg:SetPoint("BOTTOMLEFT", f, "BOTTOMLEFT", 10, 8)
|
||||
f.barBg:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -10, 8)
|
||||
f.barBg:SetHeight(BAR_H)
|
||||
f.barBg:SetColorTexture(UI.Color("card"))
|
||||
|
||||
f.fill = f:CreateTexture(nil, "OVERLAY")
|
||||
f.fill:SetPoint("BOTTOMLEFT", f.barBg, "BOTTOMLEFT", 0, 0)
|
||||
f.fill:SetHeight(BAR_H)
|
||||
f.fill:SetColorTexture(UI.Color("gold"))
|
||||
|
||||
f.acc = 0
|
||||
f:SetScript("OnUpdate", function(self, elapsed)
|
||||
self.acc = self.acc + (elapsed or 0)
|
||||
if self.acc < 0.1 then return end
|
||||
self.acc = 0
|
||||
local fl = Taxi.flight
|
||||
if not fl or not Taxi.WindowOn() then self:Hide() return end
|
||||
local L = FW.L
|
||||
local el = (GetTime and GetTime() or 0) - fl.t0
|
||||
if self.shownName ~= fl.name then
|
||||
self.shownName = fl.name
|
||||
self.title:SetText(string.format(L.CODEX_TAXI_TITLE, fl.name))
|
||||
end
|
||||
local full = self.barBg:GetWidth() or (WIDTH - 20)
|
||||
if fl.exp and fl.exp > 0 then
|
||||
local left = fl.exp - el
|
||||
if left > 0 then
|
||||
self.time:SetText(string.format(L.CODEX_TAXI_LEFT, Taxi.Duration(math.ceil(left))))
|
||||
self.fill:SetColorTexture(UI.Color("gold"))
|
||||
self.fill:SetWidth(math.max(1, full * left / fl.exp))
|
||||
else
|
||||
self.time:SetText(L.CODEX_TAXI_ARRIVING)
|
||||
self.fill:SetColorTexture(UI.Color("success"))
|
||||
self.fill:SetWidth(1)
|
||||
end
|
||||
else
|
||||
self.time:SetText(string.format(L.CODEX_TAXI_UNKNOWN, Taxi.Duration(el)))
|
||||
self.fill:SetColorTexture(UI.Color("bronze"))
|
||||
self.fill:SetWidth(math.max(1, full * ((el % 4) / 4)))
|
||||
end
|
||||
end)
|
||||
f:Hide()
|
||||
return f
|
||||
end
|
||||
|
||||
-- Das Fenster zeigt sich, sobald ein Flug laeuft
|
||||
local watch = CreateFrame("Frame")
|
||||
local acc = 0
|
||||
watch:SetScript("OnUpdate", function(_, elapsed)
|
||||
acc = acc + (elapsed or 0)
|
||||
if acc < 0.25 then return end
|
||||
acc = 0
|
||||
if Taxi.flight and Taxi.WindowOn() then
|
||||
if not win then win = Build() end
|
||||
if not win:IsShown() then win.shownName = nil; win:Show() end
|
||||
end
|
||||
end)
|
||||
|
||||
if UI.RegisterSettingsSection then
|
||||
UI:RegisterSettingsSection("codex-taxi", {
|
||||
order = 108,
|
||||
height = 140,
|
||||
build = function(parent)
|
||||
local L = FW.L
|
||||
local f, stack = UI.CreateSection(parent, L.CODEX_TAXI_HEADER)
|
||||
local note = UI.CreateLabel(f, L.CODEX_TAXI_NOTE, { size = 11, color = "textDim", justifyV = "TOP" })
|
||||
stack:Add(note, { fit = true, gap = 8 })
|
||||
local cb = UI.CreateCheckbox(f, L.CODEX_TAXI_SWITCH, function() return Taxi.WindowOn() end,
|
||||
function(on) Settings().taxiWindow = on and true or false end)
|
||||
stack:Add(cb, { fit = true, gap = 6 })
|
||||
local cp = UI.CreateCheckbox(f, L.CODEX_TAXI_PLAN, function() return Taxi.PlannerOn() end,
|
||||
function(on) Settings().taxiPlan = on and true or false end)
|
||||
stack:Add(cp, { fit = true, gap = 6 })
|
||||
function f.Refresh() cb:Refresh(); cp:Refresh(); stack:Layout() end
|
||||
stack:Layout()
|
||||
return f
|
||||
end,
|
||||
refresh = function(frame) frame.Refresh() end,
|
||||
})
|
||||
end
|
||||
Reference in New Issue
Block a user