-- =========================================================================== -- InvasionDrops (CLIENTE) v1 - Informacion de drop al hacer clic en la -- ventana nativa "Event Time" / "Invasion Time". -- -- Al pinchar un evento, el cliente llama a EventTimeMenuClick(EventIndex). -- Este script abre un panel con tres datos: que dropea, con que probabilidad -- y que monstruos lo componen. Todo es estatico: sin paquetes, sin SQL, sin -- costo para el GameServer. -- -- Instalacion: -- 1) Copiar este archivo a Data\Custom\Script\Scripts\InvasionDrops\InvasionDrops.lua -- 2) Cargarlo en el ScriptMain del cliente. -- 3) Las imagenes son las del ranking (rk_*.ozt, ya instaladas). No hay imagenes nuevas. -- 4) Indices ya mapeados (200 + Id de invasion). Debug = true imprime el indice de cada clic. -- NO cargar a la vez el EventTimeInfo.lua del video (usa el mismo hook). -- Solo ASCII en los textos. -- =========================================================================== local CFG = { Debug = false, -- true: imprime en consola el indice de cada evento clickeado Scene = 5, Title = "DROP DE INVASION", X = 188, -- a la derecha de la ventana Event Time Y = 52, RowsPage = 15, AllowMove = true, -- Orden de dibujo (el cliente no tiene z-index; esto son pruebas, ver notas): DrawHook = 'MainInterfaceProcThread', -- probar tambien 'MainProcThread' HideNames = false, -- true: llama a DisableShowName() mientras el panel esta abierto PosFile = "InvasionDrops_pos.txt", Scale = 0.85, -- tamano del panel: 1.0 = original, menor = mas chico (0.7 a 1.0) } -- --------------------------------------------------------------------------- -- DATOS. Generados desde Data\EventItemBag\Monster\*.txt de Mu Plus One (no editar a mano: -- si cambian los drops, se vuelven a generar desde esos archivos). -- Clave = Id de la invasion (el de InvasionManager.dat / InvasionMap). -- Cada caja = una tirada por kill: { "prob", prob_numerica, "clases", { {item, "% dentro"}, ... } }. -- Dentro de la caja cae 1 item al azar. Clases: SM, BK, ME, MG, DL, SU, RF. (Exc) = Excellent segun ItemOptionRate seccion 4, columna Option4 (el panel lo muestra con *). (1d) = dura 1 dia. -- --------------------------------------------------------------------------- local Events = { [0] = { title = "Skeleton King", dur = 20, maps = 3, mobs = { {55,"Skeleton King"} }, groups = { { heads = { "Skeleton King" }, boxes = { { "100%", 100.0, "", { {"Jewel of Chaos",""}, {"Jewel of Bless",""}, {"Jewel of Soul",""}, {"Jewel of Life",""}, {"Jewel of Creation",""} } }, { "100%", 100.0, "", { {"Pink Chocolate Box",""} } }, { "10%", 10.0, "", { {"Talisman of Wings of Satan",""}, {"Talisman of Wings of Heaven",""}, {"Talisman of Wings of Elf",""} } }, { "10%", 10.0, "", { {"Red Chocolate Box",""} } }, { "10%", 10.0, "", { {"Silver Key",""} } }, { "5%", 5.0, "", { {"Talisman of Wings of Soul",""}, {"Talisman of Wings of Spirits",""}, {"Talisman of Wings of Darkness",""}, {"Talisman of Wings of Dragon",""}, {"Talisman of Cape of Emperor",""} } }, { "5%", 5.0, "", { {"Gold Key",""} } }, } }, }, }, [1] = { title = "Red Dragon", dur = 20, maps = 3, mobs = { {42,"Red Dragon"} }, groups = { { heads = { "Red Dragon" }, boxes = { { "100%", 100.0, "", { {"Box of Luck",""}, {"Pink Chocolate Box",""} } }, { "10%", 10.0, "", { {"Talisman of Wings of Satan",""}, {"Talisman of Wings of Heaven",""}, {"Talisman of Wings of Elf",""} } }, { "10%", 10.0, "", { {"Red Chocolate Box",""} } }, { "10%", 10.0, "", { {"Silver Key",""} } }, { "5%", 5.0, "", { {"Talisman of Wings of Soul",""}, {"Talisman of Wings of Spirits",""}, {"Talisman of Wings of Darkness",""}, {"Talisman of Wings of Dragon",""}, {"Talisman of Cape of Emperor",""} } }, { "5%", 5.0, "", { {"Gold Key",""} } }, } }, }, }, [2] = { title = "Golden Dragon", dur = 40, maps = 11, mobs = { {43,"G. Budge Dragon"}, {78,"G. Goblin"}, {54,"G. Soldier"}, {53,"G. Titan"}, {80,"G. Lizard King"}, {81,"G. Vepar"}, {83,"G. Iron Wheel"}, {493,"G. Dark Knight"}, {494,"G. Devil"}, {495,"G. Stone Golem"}, {496,"G. Crust"}, {497,"G. Satyros"}, {498,"G. Twin Tail"} }, groups = { { heads = { "G. Budge Dragon, G. Goblin, G. Soldier,", "G. Dark Knight" }, boxes = { { "100%", 100.0, "", { {"Box of Kundun +1",""} } }, } }, { heads = { "G. Titan, G. Lizard King, G. Vepar,", "G. Devil" }, boxes = { { "100%", 100.0, "", { {"Box of Kundun +2",""} } }, } }, { heads = { "G. Iron Wheel, G. Stone Golem, G. Crust" }, boxes = { { "100%", 100.0, "", { {"Box of Kundun +3",""} } }, } }, { heads = { "G. Satyros, G. Twin Tail" }, boxes = { { "100%", 100.0, "", { {"Box of Kundun +4",""} } }, } }, }, }, [3] = { title = "White Wizard", dur = 20, maps = 3, mobs = { {135,"White Wizard"} }, groups = { { heads = { "White Wizard" }, boxes = { { "100%", 100.0, "", { {"Jewel of Creation",""} } }, { "100%", 100.0, "", { {"Wizards Ring",""}, {"Pink Chocolate Box",""} } }, { "10%", 10.0, "", { {"Talisman of Wings of Satan",""}, {"Talisman of Wings of Heaven",""}, {"Talisman of Wings of Elf",""} } }, { "10%", 10.0, "", { {"Red Chocolate Box",""} } }, { "10%", 10.0, "", { {"Silver Key",""} } }, { "5%", 5.0, "", { {"Talisman of Wings of Soul",""}, {"Talisman of Wings of Spirits",""}, {"Talisman of Wings of Darkness",""}, {"Talisman of Wings of Dragon",""}, {"Talisman of Cape of Emperor",""} } }, { "5%", 5.0, "", { {"Gold Key",""} } }, } }, }, }, [4] = { title = "New Year", dur = 20, maps = 3, mobs = { {365,"Fortune Pouch"} }, groups = { { heads = { "Fortune Pouch" }, boxes = { { "95%", 95.0, "", { {"Jewel of Bless","21.4%"}, {"Jewel of Soul","21.4%"}, {"Jewel of Chaos","21.4%"}, {"Jewel of Creation","21.4%"}, {"Loch's Feather","7.14%"}, {"Crest of Monarch","7.14%"} } }, { "5%", 5.0, "", { {"Equipo Exc. al azar (89)",""} } }, } }, }, }, [5] = { title = "Rabbits", dur = 10, maps = 3, mobs = { {413,"Lunar Rabbit"} }, groups = { { heads = { "Lunar Rabbit" }, boxes = { { "100%", 100.0, "", { {"Jewel of Chaos",""}, {"Jewel of Bless",""}, {"Jewel of Soul",""}, {"Jewel of Life",""}, {"Jewel of Creation",""} } }, } }, }, }, [6] = { title = "Summer", dur = 20, maps = 4, mobs = { {463,"Flame Ghost"} }, groups = { { heads = { "Flame Ghost" }, boxes = { { "95%", 95.0, "", { {"Jewel of Bless",""}, {"Jewel of Soul",""}, {"Jewel of Chaos",""}, {"Jewel of Creation",""} } }, { "5%", 5.0, "", { {"Equipo Exc. al azar (32)",""} } }, } }, }, }, [12] = { title = "Goblins", dur = 10, maps = 2, mobs = { {792,"Green Goblin"}, {793,"Blue Goblin"}, {794,"Red Goblin"}, {795,"Yellow Goblin"} }, groups = { { heads = { "Green Goblin, Yellow Goblin" }, boxes = { { "100%", 100.0, "", { {"Pink Chocolate Box",""} } }, { "10%", 10.0, "", { {"Silver Key",""} } }, { "5%", 5.0, "", { {"Gold Key",""} } }, } }, { heads = { "Blue Goblin, Red Goblin" }, boxes = { { "100%", 100.0, "", { {"Red Chocolate Box",""} } }, { "10%", 10.0, "", { {"Silver Key",""} } }, { "5%", 5.0, "", { {"Gold Key",""} } }, } }, }, }, [14] = { title = "Mini Bosses", dur = 30, maps = 5, mobs = { {25,"Ice Queen"}, {18,"Gorgon"}, {67,"Metal Balrog"}, {49,"Hydra"}, {59,"Zaikan"} }, groups = { { heads = { "Ice Queen, Gorgon" }, boxes = { { "100%", 100.0, "", { {"Cherry Blossom Play-Box",""} } }, { "70%", 70.0, "", { {"Jewel of Chaos",""}, {"Jewel of Bless",""}, {"Jewel of Soul",""}, {"Jewel of Life",""}, {"Jewel of Creation",""} } }, { "30%", 30.0, "", { {"Demon (1d)",""}, {"Spirit of Guardian (1d)",""} } }, { "10%", 10.0, "", { {"Red Chocolate Box",""}, {"Blue Chocolate Box",""} } }, { "10%", 10.0, "", { {"Silver Key",""} } }, { "5%", 5.0, "", { {"Gold Key",""} } }, } }, { heads = { "Metal Balrog" }, boxes = { { "100%", 100.0, "", { {"Cherry Blossom Play-Box",""} } }, { "60%", 60.0, "", { {"Jewel of Chaos",""}, {"Jewel of Bless",""}, {"Jewel of Soul",""}, {"Jewel of Life",""}, {"Jewel of Creation",""} } }, { "40%", 40.0, "", { {"Devil Square Ticket",""}, {"Blood Castle Ticket",""}, {"Kalima Ticket",""} } }, { "10%", 10.0, "", { {"Red Chocolate Box",""}, {"Blue Chocolate Box",""} } }, { "10%", 10.0, "", { {"Silver Key",""} } }, { "5%", 5.0, "", { {"Gold Key",""} } }, } }, { heads = { "Hydra" }, boxes = { { "100%", 100.0, "", { {"Cherry Blossom Play-Box",""} } }, { "50%", 50.0, "", { {"Bundled Jewel of Bless",""}, {"Bundled Jewel of Soul",""}, {"Bundled Jewel of Life",""}, {"Bundled Jewel of Creation",""}, {"Bundled Jewel of Chaos",""} } }, { "45%", 45.0, "[BK/MG]", { {"Dragon Shield",""}, {"Spiked Shield (Exc)",""}, {"Dragon Slayer Shield (Exc)",""}, {"Tower Shield (Exc)",""} } }, { "45%", 45.0, "[SM]", { {"Grand Soul Shield",""}, {"Skull Shield (Exc)",""} } }, { "45%", 45.0, "[ME]", { {"Elemental Shield",""}, {"Tower Shield (Exc)",""} } }, { "45%", 45.0, "[DL]", { {"Serpent Shield",""}, {"Spiked Shield (Exc)",""}, {"Dragon Slayer Shield (Exc)",""}, {"Tower Shield (Exc)",""} } }, { "5%", 5.0, "", { {"Panda Ring (1d)",""}, {"Pet Panda (1d)",""} } }, { "5%", 5.0, "", { {"Box of Kundun +3",""} } }, } }, { heads = { "Zaikan" }, boxes = { { "100%", 100.0, "", { {"Cherry Blossom Play-Box",""} } }, { "50%", 50.0, "", { {"Bundled Jewel of Bless",""}, {"Bundled Jewel of Soul",""}, {"Bundled Jewel of Life",""}, {"Bundled Jewel of Creation",""}, {"Bundled Jewel of Chaos",""} } }, { "45%", 45.0, "[DL]", { {"Lord Scepter",""}, {"Great Scepter (Exc)",""} } }, { "45%", 45.0, "[ME]", { {"Celestial Bow",""}, {"Chaos Nature Bow (Exc)",""}, {"Saint Crossbow (Exc)",""}, {"Celestial Bow (Exc)",""} } }, { "45%", 45.0, "[MG]", { {"Rune Blade",""}, {"Lighting Sword (Exc)",""}, {"Crystal Morning Star (Exc)",""}, {"Chaos Dragon Axe (Exc)",""}, {"Legendary Staff (Exc)",""}, {"Staff of Resurrection (Exc)",""}, {"Chaos Lighting Staff (Exc)",""}, {"Staff of Destruction (Exc)",""} } }, { "45%", 45.0, "[SM]", { {"Dragon Soul Staff",""}, {"Legendary Staff (Exc)",""}, {"Staff of Resurrection (Exc)",""}, {"Chaos Lighting Staff (Exc)",""}, {"Staff of Destruction (Exc)",""} } }, { "45%", 45.0, "[BK]", { {"Sword of Destruction",""}, {"Blade (Exc)",""}, {"Legendary Sword (Exc)",""}, {"Lighting Sword (Exc)",""}, {"Chaos Dragon Axe (Exc)",""} } }, { "5%", 5.0, "", { {"Talisman of Chaos Assembly",""} } }, { "5%", 5.0, "", { {"Box of Kundun +3",""} } }, } }, }, }, } -- Contenido de las cajas (se ve al pasar el mouse por encima). { prob, grupo, ejemplos } local BoxInfo = { ["Box of Luck"] = { { "100%", "Accesorios 3, Joyas 4", "Guardian Angel, Imp, Horn of Uniria +4 mas" }, }, ["Box of Kundun +1"] = { { "45%", "Armaduras Excellent (15)", "Leather, Vine, Pad" }, { "45%", "Armas Excellent (29)", "Kris, Short Sword, Rapier +26 mas" }, { "6%", "Accesorios Excellent (6)", "Ring of Ice, Ring of Poison +4 mas" }, { "4%", "Box of Luck", "" }, }, ["Box of Kundun +2"] = { { "45%", "Armaduras Excellent (35)", "Bronze, Scale, Silk, Wind +3 sets" }, { "45%", "Armas Excellent (22)", "Gladius, Falchion, Serpent Sword +19 mas" }, { "6%", "Accesorios Excellent (4)", "Pendant of Ice, Pendant of Wind +2 mas" }, { "4%", "Box of Luck", "" }, }, ["Box of Kundun +3"] = { { "47.5%", "Armaduras Excellent (39)", "Legendary, Brass, Plate, Dragon +4 sets" }, { "47.5%", "Armas Excellent (22)", "Blade, Light Saber, Legendary Sword +19 mas" }, { "5%", "Box of Luck", "" }, }, ["Box of Kundun +4"] = { { "47.5%", "Armaduras Excellent (24)", "Dark Phoenix, Divine, Grand Soul, Thunder Hawk +1 sets" }, { "47.5%", "Armas Excellent (14)", "Sword of Destruction, Thunder Blade +12 mas" }, { "5%", "Box of Luck", "" }, }, ["Box of Kundun +5"] = { { "50%", "Armaduras Excellent (24)", "Great Dragon, Dark Soul, Red Spirit, Red Sprit +2 sets" }, { "50%", "Armas Excellent (11)", "Daybreak, Knight Blade +9 mas" }, }, ["Pink Chocolate Box"] = { { "40%", "Armaduras (50)", "Pad, Leather, Vine, Bronze +6 sets" }, { "40%", "Armas (51)", "Kris, Short Sword, Rapier +48 mas" }, { "10%", "Armaduras 50, Accesorios 10 Excellent", "Pad Helm, Pad Armor, Pad Pants +57 mas" }, { "10%", "Armas Excellent (51)", "Kris, Short Sword, Rapier +48 mas" }, }, ["Red Chocolate Box"] = { { "45%", "Armaduras (43)", "Adamantine, Storm Crow, Guardian, Legendary +5 sets" }, { "45%", "Joyas 5, Accesorios 1, Armas 14", "Jewel of Bless, Jewel of Soul +18 mas" }, { "5%", "Armaduras 30, Armas 4, Accesorios 4 Excellent", "Dragon Helm, Dragon Armor +36 mas" }, { "5%", "Armas Excellent (14)", "Crescent Axe, Double Blade +12 mas" }, }, ["Blue Chocolate Box"] = { { "45%", "Joyas (5)", "Jewel of Bless, Jewel of Soul +3 mas" }, { "45%", "Armaduras (24)", "Ashcrow, Eclipse, Iris, Valiant +1 sets" }, { "5%", "Armaduras Excellent (24)", "Ashcrow, Eclipse, Iris, Valiant +1 sets" }, { "5%", "Armas 10, Accesorios 6 Excellent", "Sword of Destruction, Rune Blade +14 mas" }, }, ["Equipo Exc. al azar (89)"] = { { "38%", "Armas Excellent (34)", "Sword of Salamander, Light Saber +32 mas" }, { "62%", "Armaduras Excellent (55)", "Scale, Leather, Bronze, Brass +7 sets" }, }, ["Equipo Exc. al azar (32)"] = { { "53%", "Armas Excellent (17)", "Blade, Lighting Sword +15 mas" }, { "47%", "Armaduras Excellent (15)", "Scale, Wind, Sphinx" }, }, } -- Indice de la ventana Event Time -> Id de invasion de la tabla de arriba. -- Las invasiones llegan como 200 + su Id. Una invasion nueva: Debug = true para ver su indice -- en consola, y agregar aqui una linea [indice] = Id, mas su entrada en Events. local ByIndex = { [200] = 0, -- Skeleton King [201] = 1, -- Red Dragon [202] = 2, -- Golden Dragon [203] = 3, -- White Wizard [204] = 4, -- New Year [205] = 5, -- Rabbits [206] = 6, -- Summer [212] = 12, -- Goblins [214] = 14, -- Mini Bosses } -- Puntos por kill (los mismos del ranking) local Pts = { [18]=4,[25]=4,[42]=2,[43]=6,[49]=8,[53]=7,[54]=6,[55]=2,[59]=10,[67]=4,[78]=6,[80]=7,[81]=7, [83]=19,[135]=2,[365]=2,[413]=1,[463]=2,[493]=6,[494]=7,[495]=19,[496]=19,[497]=28,[498]=28, [792]=1,[793]=1,[794]=1,[795]=1, } local TIERS = { { label = "SIEMPRE", r = 130, g = 255, b = 150 }, { label = "COMUN", r = 225, g = 225, b = 232 }, { label = "RARO", r = 125, g = 175, b = 255 }, { label = "MUY RARO", r = 255, g = 205, b = 80 }, } local W, H = 236, 408 local SC = math.max(0.6, math.min(1.2, CFG.Scale or 1.0)) local OX, OY = 0, 0 -- origen del panel: todo se escala desde su esquina local function CS(n) return math.floor(n * SC + 0.5) end -- letras por linea (el texto no se achica) -- Imagenes: mismos archivos del ranking, con ids propios (40140..) local IMG = { tl=40140, tm=40141, tr=40142, ml=40143, mm=40144, mr=40145, bl=40146, bm=40147, br=40148, head=40149, line=40150, tabOn=40151, tabOff=40152, barGold=40153, barGreen=40154, rowAlt=40155, strip=40156 } local IMG_FILES = { [IMG.tl]="rk_tl", [IMG.tm]="rk_tm", [IMG.tr]="rk_tr", [IMG.ml]="rk_ml", [IMG.mm]="rk_mm", [IMG.mr]="rk_mr", [IMG.bl]="rk_bl", [IMG.bm]="rk_bm", [IMG.br]="rk_br", [IMG.head]="rk_head", [IMG.line]="rk_line", [IMG.tabOn]="rk_tab_on", [IMG.tabOff]="rk_tab_off", [IMG.barGold]="rk_bar_gold", [IMG.barGreen]="rk_bar_green", [IMG.rowAlt]="rk_row_alt", [IMG.strip]="rk_strip", } local State = { open=false, ev=nil, idx=-1, tab=1, page=1, lines=nil, imgLoaded=false, dx=0, dy=0, moving=false, grabX=0, grabY=0 } local Hit = { win={}, head={}, close={}, tab={ {}, {} }, prev={}, next={} } BridgeFunctionAttach('EventTimeMenuClick', 'InvDrop_Click') BridgeFunctionAttach('KeyboardEvent', 'InvDrop_Key') BridgeFunctionAttach(CFG.DrawHook, 'InvDrop_Draw') BridgeFunctionAttach('MainLoader', 'InvDrop_LoadImages') BridgeFunctionAttach('UpdateMouseEvent', 'InvDrop_Mouse') BridgeFunctionAttach('UpdateKeyEvent', 'InvDrop_UpdateKey') -- --------------------------------------------------------------------------- -- Utilidades -- --------------------------------------------------------------------------- -- El mouse viene en pantalla; los rectangulos estan en medidas de diseno (antes de escalar). local function Inside(r, mx, my) if r.x == nil then return false end mx, my = OX + (mx - OX) / SC, OY + (my - OY) / SC return mx >= r.x and mx <= (r.x + r.w) and my >= r.y and my <= (r.y + r.h) end local function Set(r, x, y, w, h) r.x, r.y, r.w, r.h = x, y, w, h end local function Clamp(v, lo, hi) if v < lo then return lo end if v > hi then return hi end return v end local function Cut(s, n) s = tostring(s); if #s > n then return string.sub(s, 1, n) end return s end local function LoadPos() local ok, f = pcall(io.open, CFG.PosFile, "r") if ok and f then local a, b = f:read("*l"), f:read("*l"); f:close() State.dx, State.dy = tonumber(a) or 0, tonumber(b) or 0 end end local function SavePos() local ok, f = pcall(io.open, CFG.PosFile, "w") if ok and f then f:write(tostring(State.dx), "\n", tostring(State.dy), "\n"); f:close() end end LoadPos() local function Text(x, y, s, w, align, r, g, b, a) SetTextColor(r, g, b, a or 255) RenderText5(math.floor(OX + (x - OX) * SC + 0.5), math.floor(OY + (y - OY) * SC + 0.5), s, math.floor(w * SC + 0.5), align) end local function Img(id, x, y, w, h) RenderImage2(id, math.floor(OX + (x - OX) * SC + 0.5), math.floor(OY + (y - OY) * SC + 0.5), math.floor(w * SC + 0.5), math.floor(h * SC + 0.5), 0.0, 0.0, 1.0, 1.0, 1, 1, 1.0) end local function Frame(x, y, w, h) local c = 8 Img(IMG.tl, x, y, c, c); Img(IMG.tm, x + c, y, w - 2*c, c); Img(IMG.tr, x + w - c, y, c, c) Img(IMG.ml, x, y + c, c, h - 2*c); Img(IMG.mm, x + c, y + c, w - 2*c, h - 2*c); Img(IMG.mr, x + w - c, y + c, c, h - 2*c) Img(IMG.bl, x, y + h - c, c, c); Img(IMG.bm, x + c, y + h - c, w - 2*c, c); Img(IMG.br, x + w - c, y + h - c, c, c) end local function LoadImagesOnce() if State.imgLoaded then return end State.imgLoaded = true for id, file in pairs(IMG_FILES) do LoadBitmap("Custom\\ScriptImages\\" .. file .. ".tga", id) end end function InvDrop_LoadImages() LoadImagesOnce() end -- --------------------------------------------------------------------------- -- Lineas de cada pestana (se arman una vez al abrir / cambiar de pestana) -- --------------------------------------------------------------------------- local function RateNum(r) return tonumber(string.match(tostring(r), "[%d%.]+")) or 0 end local function TierOf(r) local v = RateNum(r) if v >= 99.9 then return 1 elseif v >= 10 then return 2 elseif v >= 1 then return 3 end return 4 end -- Une nombres con prefijo comun: "Jewel of Chaos, Bless, Soul" en vez de repetir "Jewel of". local function JoinNames(names) local words, k = {}, nil for i, n in ipairs(names) do local w = {} for t in string.gmatch(n, "%S+") do w[#w + 1] = t end words[i] = w if i == 1 then k = #w - 1 else local c = 0 while c < k and c < #w - 1 and w[c + 1] == words[1][c + 1] do c = c + 1 end k = c end end if k and k >= 2 and #names > 1 then local out = { names[1] } for i = 2, #names do out[#out + 1] = table.concat(words[i], " ", k + 1) end return out end return names end local function WrapList(items, max) local lines, cur = {}, "" for i, it in ipairs(items) do local piece = it .. (i < #items and "," or "") if cur == "" then cur = piece elseif #cur + 1 + #piece > max then lines[#lines + 1] = cur; cur = piece else cur = cur .. " " .. piece end end if cur ~= "" then lines[#lines + 1] = cur end return lines end local function Short(n) return (string.gsub(n, " %(Exc%)", "*")) end local function BuildLines() local ev, L = State.ev, {} State.pg = { {} } if not ev then State.lines = L; return end if State.tab == 1 then L[#L + 1] = { note = "Una tirada por kill. * = Excellent" } for _, g in ipairs(ev.groups or {}) do for _, h in ipairs(g.heads) do L[#L + 1] = { head = h } end -- Tiradas por clase con la misma probabilidad: un solo bloque "segun tu clase" local entries, byChance = {}, {} for _, b in ipairs(g.boxes) do if b[3] ~= "" then local e = byChance[b[1]] if not e then e = { cls = true, chance = b[1], tier = TierOf(b[1]), rows = {} } byChance[b[1]] = e entries[#entries + 1] = e end e.rows[#e.rows + 1] = b else entries[#entries + 1] = { box = b } end end for _, e in ipairs(entries) do if e.cls then L[#L + 1] = { pool = true, classBlock = true, chance = e.chance, tag = "", tier = e.tier } for _, b in ipairs(e.rows) do local names = {} for _, it in ipairs(b[4]) do names[#names + 1] = Short(it[1]) end local list = JoinNames(names) list[1] = b[3] .. ": " .. list[1] for _, t in ipairs(WrapList(list, 44)) do L[#L + 1] = { txt = t } end end else local b = e.box local items, tier = b[4], TierOf(b[1]) local hasBox = false for _, it in ipairs(items) do if BoxInfo[it[1]] then hasBox = true end end if #items == 1 then L[#L + 1] = { single = Short(items[1][1]), chance = b[1], tag = b[3], tier = tier, box = BoxInfo[items[1][1]], boxName = items[1][1] } else local weighted = items[1][2] ~= "" L[#L + 1] = { pool = true, chance = b[1], tag = b[3], n = #items, tier = tier, weighted = weighted } if weighted or hasBox then for _, it in ipairs(items) do L[#L + 1] = { sub = Short(it[1]), pct = it[2], box = BoxInfo[it[1]], boxName = it[1] } end else local names = {} for _, it in ipairs(items) do names[#names + 1] = Short(it[1]) end for _, t in ipairs(WrapList(JoinNames(names), 44)) do L[#L + 1] = { txt = t } end end end end end end else local m = {} for _, mo in ipairs(ev.mobs or {}) do m[#m + 1] = { name = mo[2], pts = Pts[mo[1]] or 1 } end table.sort(m, function(a, b) return a.pts > b.pts end) for _, r in ipairs(m) do L[#L + 1] = { mob = r.name, pts = r.pts } end end State.lines = L -- Paginas por bloques: una tirada (con sus lineas) no se parte entre paginas, y si una pagina -- empieza a mitad de un grupo de monstruos se repite el nombre del grupo. local units, i, heads, pre = {}, 1, {}, {} while i <= #L do local ln = L[i] if ln.note then pre[#pre + 1] = ln; i = i + 1 elseif ln.head then if #pre == 0 or not pre.inHead then pre.inHead = true; heads = {} end heads[#heads + 1] = ln; pre[#pre + 1] = ln; i = i + 1 else local u = { lines = {}, heads = heads, own = #pre > 0 } for _, p in ipairs(pre) do u.lines[#u.lines + 1] = p end pre = {} u.lines[#u.lines + 1] = ln; i = i + 1 while L[i] and (L[i].sub or L[i].txt) do u.lines[#u.lines + 1] = L[i]; i = i + 1 end units[#units + 1] = u end end local pg, cur = {}, nil for _, u in ipairs(units) do local extra = (not u.own and #u.heads > 0) and #u.heads or 0 if not cur or #cur + #u.lines > CFG.RowsPage then cur = {}; pg[#pg + 1] = cur if not u.own then for _, h in ipairs(u.heads) do cur[#cur + 1] = { head = h.head, cont = true } end end end for _, l in ipairs(u.lines) do cur[#cur + 1] = l end end State.pg = (#pg > 0) and pg or { {} } end local function Pages() return State.pg and #State.pg or 1 end -- --------------------------------------------------------------------------- -- Apertura / cierre -- --------------------------------------------------------------------------- local function CloseWin() State.open = false if State.moving then State.moving = false; SavePos() end end function InvDrop_Click(EventIndex) if CFG.Debug then Console(1, "[InvasionDrops] EventIndex = " .. tostring(EventIndex)) end if GetMainScene() ~= CFG.Scene then return end if State.open and State.idx == EventIndex then CloseWin(); return end -- segundo clic: cierra State.open, State.idx, State.tab, State.page = true, EventIndex, 1, 1 State.ev = Events[ByIndex[EventIndex] or -1] BuildLines() end function InvDrop_Key(key) if State.open and key == Keys.Escape then CloseWin() end end function InvDrop_Mouse() if not State.open then return end if State.moving then if CheckReleasedKey(Keys.LButton) == 1 then State.moving = false; SavePos() end return end local mx, my = MousePosX(), MousePosY() if CheckReleasedKey(Keys.RButton) == 1 and CFG.AllowMove and Inside(Hit.head, mx, my) then State.dx, State.dy = 0, 0; SavePos(); return end if CheckReleasedKey(Keys.LButton) ~= 1 then return end if Inside(Hit.close, mx, my) then CloseWin() elseif Inside(Hit.tab[1], mx, my) then State.tab, State.page = 1, 1; BuildLines() elseif Inside(Hit.tab[2], mx, my) then State.tab, State.page = 2, 1; BuildLines() elseif Inside(Hit.prev, mx, my) then State.page = math.max(1, State.page - 1) elseif Inside(Hit.next, mx, my) then State.page = math.min(Pages(), State.page + 1) elseif CFG.AllowMove and Inside(Hit.head, mx, my) then State.moving = true State.grabX, State.grabY = mx - State.dx, my - State.dy end end function InvDrop_UpdateKey() if not State.open then return end if State.moving or Inside(Hit.win, MousePosX(), MousePosY()) then DisableClickClient() end end -- --------------------------------------------------------------------------- -- Dibujo -- --------------------------------------------------------------------------- -- Tooltip con el contenido de una caja (se dibuja encima del panel) function DrawBoxTip(mx, my, px, name, rows) local sc = SC SC = 1 -- el tooltip va a tamano normal (tiene mucho texto) local ok, err = pcall(DrawBoxTipBody, mx, my, px, name, rows, sc) SC = sc end function DrawBoxTipBody(mx, my, px, name, rows, panelSc) local TW = 300 local lines = 0 for _, r in ipairs(rows) do lines = lines + (r[3] ~= "" and 2 or 1) end local TH = 44 + lines * 14 + 6 local tx = px + W * panelSc + 6 -- a la derecha del panel if tx + TW > GetWideX() - 4 then tx = px - TW - 6 end -- o a la izquierda si no cabe if tx < 4 then tx = GetWideX() - TW - 4 end -- si no cabe en ningun lado, pegado al borde (tapa parte del panel) local ty = Clamp(my - 20, 4, 470 - TH) Frame(tx, ty, TW, TH); Frame(tx, ty, TW, TH); Frame(tx, ty, TW, TH) -- varias capas: fondo opaco Text(tx + 10, ty + 9, Cut(name, 32), TW - 20, ALIGN_LEFT, 255, 215, 90) Text(tx + 10, ty + 23, (string.find(name, "^Equipo") and "Cae 1 item de estos grupos:" or "Al abrirla cae 1 item de estos grupos:"), TW - 20, ALIGN_LEFT, 150, 150, 160) Img(IMG.line, tx + 8, ty + 37, TW - 16, 1) local ly = ty + 42 for _, r in ipairs(rows) do Text(tx + 10, ly, r[1], 44, ALIGN_LEFT, 255, 215, 90) Text(tx + 58, ly, Cut(r[2], 50), TW - 68, ALIGN_LEFT, 235, 235, 240) ly = ly + 14 if r[3] ~= "" then Text(tx + 58, ly, Cut(r[3], 54), TW - 68, ALIGN_LEFT, 150, 150, 165) ly = ly + 14 end end end function InvDrop_Draw() if not State.open then return end if CheckWindowOpen(UIInventory) == 1 or CheckWindowOpen(UICharacter) == 1 then CloseWin(); return end LoadImagesOnce() if CFG.HideNames and DisableShowName then pcall(DisableShowName) end if State.moving then State.dx = MousePosX() - State.grabX State.dy = MousePosY() - State.grabY end State.dx = Clamp(State.dx, -CFG.X, GetWideX() - W * SC - CFG.X) State.dy = Clamp(State.dy, -CFG.Y, 470 - H * SC - CFG.Y) local x, y = CFG.X + State.dx, CFG.Y + State.dy OX, OY = x, y local mx, my = MousePosX(), MousePosY() local ev = State.ev SetFontType(1) SetTextBg(0, 0, 0, 0) Frame(x, y, W, H) Img(IMG.head, x + 3, y + 3, W - 6, 32) Img(IMG.line, x + 3, y + 35, W - 6, 1) Text(x, y + 7, CFG.Title, W, ALIGN_CENTER, 255, 215, 90) Text(x, y + 21, ev and ev.title or "Evento sin informacion", W, ALIGN_CENTER, 235, 235, 240) Set(Hit.head, x, y, W - 24, 36) Set(Hit.close, x + W - 22, y + 6, 16, 16) local oc = Inside(Hit.close, mx, my) Text(x + W - 22, y + 8, "x", 16, ALIGN_CENTER, oc and 255 or 190, oc and 120 or 190, oc and 120 or 190) Set(Hit.win, x, y, W, H) -- Resumen Img(IMG.strip, x + 8, y + 40, W - 16, 34) local colW = (W - 16) // 3 local stats = { { "DURACION", ev and (ev.dur .. " min") or "-" }, { "MONSTRUOS", ev and tostring(#ev.mobs) or "-" }, { "MAPAS", ev and tostring(ev.maps) or "-" }, } for i, s in ipairs(stats) do local sx = x + 8 + (i - 1) * colW Text(sx, y + 44, s[1], colW, ALIGN_CENTER, 160, 150, 120) Text(sx, y + 57, Cut(s[2], CS(12)), colW, ALIGN_CENTER, 255, 255, 255) end -- Pestanas local labels = { "Drop", "Monstruos" } local tw = (W - 16 - 4) // 2 for i = 1, 2 do local h = Hit.tab[i] Set(h, x + 8 + (i - 1) * (tw + 4), y + 80, tw, 22) local on = State.tab == i or Inside(h, mx, my) Img(on and IMG.tabOn or IMG.tabOff, h.x, h.y, h.w, h.h) if State.tab == i then Text(h.x, h.y + 6, labels[i], h.w, ALIGN_CENTER, 255, 245, 210) else Text(h.x, h.y + 6, labels[i], h.w, ALIGN_CENTER, 200, 195, 180) end end -- Encabezado de columnas if State.tab == 1 then Text(x + 14, y + 108, "TIRADA / ITEM", 100, ALIGN_LEFT, 160, 150, 120) Text(x + W - 84, y + 108, "PROBABILIDAD", 70, ALIGN_RIGHT, 160, 150, 120) else Text(x + 14, y + 108, "MONSTRUO", 90, ALIGN_LEFT, 160, 150, 120) Text(x + W - 94, y + 108, "PUNTOS POR KILL", 80, ALIGN_RIGHT, 160, 150, 120) end Img(IMG.line, x + 8, y + 120, W - 16, 1) -- Lista paginada local L = State.lines or {} local pageLines = State.pg and State.pg[Clamp(State.page, 1, Pages())] or {} local ry, rows = y + 125, 0 local hover = nil for i, ln in ipairs(pageLines) do if ln.note then Text(x + 14, ry + 2, ln.note, W - 28, ALIGN_LEFT, 150, 150, 160) elseif ln.head then Img(IMG.strip, x + 8, ry - 1, W - 16, 16) Text(x + 14, ry + 2, Cut(ln.head, CS(42)), W - 28, ALIGN_LEFT, 255, 215, 90) elseif ln.pool then local t = TIERS[ln.tier] local head = (ln.tier == 1 and "SIEMPRE" or (ln.chance .. " DE PROB.")) .. (ln.classBlock and " SEGUN TU CLASE:" or (ln.weighted and ": SE REPARTE ASI" or (": 1 AL AZAR DE " .. ln.n))) Img(IMG.rowAlt, x + 8, ry - 1, W - 16, 16) Img(ln.tier == 1 and IMG.barGreen or IMG.barGold, x + 8, ry - 1, 3, 16) Text(x + 16, ry + 2, head, W - 70, ALIGN_LEFT, t.r, t.g, t.b) if ln.tag ~= "" then Text(x + W - 84, ry + 2, ln.tag, 70, ALIGN_RIGHT, 170, 170, 180) end elseif ln.txt then Text(x + 18, ry + 2, ln.txt, W - 30, ALIGN_LEFT, 215, 215, 225) elseif ln.sub then local sn = ln.sub if ln.box then sn = sn .. " (?)" if Inside({ x = x + 8, y = ry - 1, w = W - 16, h = 16 }, mx, my) then hover = ln end end Text(x + 26, ry + 2, Cut(sn, CS(30)), W - 90, ALIGN_LEFT, ln.box and 150 or 225, ln.box and 215 or 225, ln.box and 255 or 232) if ln.pct ~= "" then Text(x + W - 70, ry + 2, ln.pct, 56, ALIGN_RIGHT, 190, 190, 200) end elseif ln.single then local t = TIERS[ln.tier] if i % 2 == 0 then Img(IMG.rowAlt, x + 8, ry - 1, W - 16, 16) end Img(ln.tier == 1 and IMG.barGreen or IMG.barGold, x + 8, ry - 1, 3, 16) local nm = ln.single if ln.tag ~= "" then nm = nm .. " [" .. ln.tag .. "]" end if ln.box then nm = nm .. " (?)" end if ln.box and Inside({ x = x + 8, y = ry - 1, w = W - 16, h = 16 }, mx, my) then hover = ln end Text(x + 16, ry + 2, Cut(nm, CS(30)), W - 80, ALIGN_LEFT, t.r, t.g, t.b) if ln.tier == 1 then Text(x + W - 78, ry + 2, "SIEMPRE", 64, ALIGN_RIGHT, 130, 255, 150) else Text(x + W - 78, ry + 2, ln.chance, 64, ALIGN_RIGHT, 255, 215, 90) end else if i % 2 == 0 then Img(IMG.rowAlt, x + 8, ry - 1, W - 16, 16) end Text(x + 14, ry + 2, Cut(ln.mob, CS(22)), 130, ALIGN_LEFT, 235, 235, 240) Text(x + W - 70, ry + 2, tostring(ln.pts), 56, ALIGN_RIGHT, 255, 215, 90) end ry = ry + 17; rows = rows + 1 end if rows == 0 then local msg = ev and "Sin datos en esta pestana" or "Aun no hay informacion para este evento" Text(x, y + 220, msg, W, ALIGN_CENTER, 170, 170, 180) if CFG.Debug then Text(x, y + 236, "(indice " .. tostring(State.idx) .. ")", W, ALIGN_CENTER, 130, 130, 140) end end -- Pie: paginacion Img(IMG.line, x + 8, y + H - 24, W - 16, 1) local pg = Pages() Text(x + 14, y + H - 17, "Clic en otro evento", 100, ALIGN_LEFT, 120, 120, 130) if pg > 1 then Set(Hit.prev, x + W - 76, y + H - 21, 20, 16) Set(Hit.next, x + W - 32, y + H - 21, 20, 16) Img(Inside(Hit.prev, mx, my) and IMG.tabOn or IMG.tabOff, Hit.prev.x, Hit.prev.y, 20, 16) Img(Inside(Hit.next, mx, my) and IMG.tabOn or IMG.tabOff, Hit.next.x, Hit.next.y, 20, 16) Text(Hit.prev.x, Hit.prev.y + 3, "<", 20, ALIGN_CENTER, 235, 230, 215) Text(Hit.next.x, Hit.next.y + 3, ">", 20, ALIGN_CENTER, 235, 230, 215) Text(x + W - 56, y + H - 18, string.format("%d/%d", State.page, pg), 24, ALIGN_CENTER, 200, 195, 180) else Set(Hit.prev, 0, 0, 0, 0); Set(Hit.next, 0, 0, 0, 0) end if hover and not State.moving then DrawBoxTip(mx, my, x, hover.boxName or hover.single or hover.sub, hover.box) end end Console(1, "[InvasionDrops] v1 cargado - clic en un evento de Event Time")