-- R6: base visual original del usuario. Solo agrega marcos OZT y estado GS. local CONFIG = require("Scripts\\EventPanel\\Config") local okPanelBG500, PanelBG500 = pcall(require, "Scripts\\PanelBG500\\PanelBG500") if okPanelBG500 ~= true or type(PanelBG500) ~= "table" then PanelBG500 = { Draw = function() return false end, DrawConfigured = function() return false end, } end if CONFIG.IvnLuaPreviewEventPanel ~= nil and CONFIG.IvnLuaPreviewEventPanel.Enabled == true and type(require) == "function" then pcall(require, "Scripts\\IvnLuaPreview\\IvnLuaPreview") end EventPanelClient = EventPanelClient or {} EventPanelClient.Opened = false EventPanelClient.View = "Invasions" EventPanelClient.Selected = { Events = 1, Invasions = 1, ZoneEvents = 1 } EventPanelClient.DetailOpened = { Events = false, Invasions = false, ZoneEvents = false } EventPanelClient.Scroll = { Events = 1, Invasions = 1, ZoneEvents = 1 } EventPanelClient.ClickLock = false EventPanelClient.InterfaceLockHeld = false EventPanelClient.WorldNamesHidden = false EventPanelClient.RowHoverState = { Key = nil, StartFrame = 0 } EventPanelClient.MonsterCache = EventPanelClient.MonsterCache or {} EventPanelClient.Frame = 0 EventPanelClient.Dragging = false EventPanelClient.DragDX = 0 EventPanelClient.DragDY = 0 EventPanelClient.WindowX = nil EventPanelClient.WindowY = nil EventPanelClient.KilledState = EventPanelClient.KilledState or {} EventPanelClient.ForceOnline = EventPanelClient.ForceOnline or {} EventPanelClient.LiveCount = EventPanelClient.LiveCount or {} EventPanelClient.CompleteState = EventPanelClient.CompleteState or {} EventPanelClient.WasOnline = EventPanelClient.WasOnline or {} EventPanelClient.IvnPreviewSignature = EventPanelClient.IvnPreviewSignature or "" EventPanelClient.IvnPreviewVisible = EventPanelClient.IvnPreviewVisible or false EventPanelClient.ScrollStep = 1 EventPanelClient.LastWheelFrame = -1 EventPanelClient.ScrollDrag = EventPanelClient.ScrollDrag or { Active = false, Key = "", StartY = 0, StartScroll = 1 } EventPanelClient.ScrollHold = EventPanelClient.ScrollHold or { Dir = 0, Delay = 0 } EventPanelClient.TimeZoneCode = EventPanelClient.TimeZoneCode or tostring((CONFIG.TimeZoneSelector or {}).Default or "PER") EventPanelClient.ServerConfig = EventPanelClient.ServerConfig or { Events = {}, Invasions = {}, ZoneEvents = {} } EventPanelClient.ConfigLoaded = false EventPanelClient.TextureReloadPending = false EventPanelClient.TextureReloadFrame = 0 EventPanelClient.TextureReloadSecondFrame = 0 EventPanelClient.ConfigRequestPending = false EventPanelClient.LastCharacterToken = EventPanelClient.LastCharacterToken or "" -- Guarda exclusivamente el aspecto 3D original. Los paquetes del GameServer -- pueden actualizar datos y horarios, pero nunca mueven ni escalan monstruos. EventPanelClient.OriginalMonsterVisual = {} local function EventPanelCaptureOriginalVisual(category, list) if type(list) ~= "table" then return end for _, ev in ipairs(list) do if type(ev) == "table" and tonumber(ev.Index) ~= nil then local key = tostring(category) .. ":" .. tostring(math.floor(tonumber(ev.Index))) EventPanelClient.OriginalMonsterVisual[key] = { VisualIndex = ev.VisualIndex, PreviewX = ev.PreviewX, PreviewY = ev.PreviewY, PreviewScale = ev.PreviewScale, ForceDirectRender = ev.ForceDirectRender, } end end end EventPanelCaptureOriginalVisual(1, CONFIG.NormalEvents) EventPanelCaptureOriginalVisual(2, CONFIG.Invasions) EventPanelCaptureOriginalVisual(3, CONFIG.ZoneEvents) function EventPanelClient.Console(text) if CONFIG.Debug == true and type(Console) == "function" then Console(2, "[EventPanel] " .. tostring(text)) end end function EventPanelClient.MouseX() if type(MousePosX) == "function" then return MousePosX() end return 0 end function EventPanelClient.MouseY() if type(MousePosY) == "function" then return MousePosY() end return 0 end function EventPanelClient.MouseIn(x, y, w, h) local mx = EventPanelClient.MouseX() local my = EventPanelClient.MouseY() return mx >= x and mx <= x + w and my >= y and my <= y + h end function EventPanelClient.MousePressed() local key = Keys and Keys.LButton or 1 if type(MouseLButton) == "function" then local ok, ret = pcall(MouseLButton) if ok and tonumber(ret) == 1 then return true end end if type(CheckPressedKey) == "function" then local ok, ret = pcall(CheckPressedKey, key) if ok and tonumber(ret) == 1 then return true end end if type(CheckIsRepeatKey) == "function" then local ok, ret = pcall(CheckIsRepeatKey, key) if ok and tonumber(ret) == 1 then return true end end if type(CheckRepeatKey) == "function" then local ok, ret = pcall(CheckRepeatKey, key) if ok and tonumber(ret) == 1 then return true end end if type(CheckClickClient) == "function" then local ok, ret = pcall(CheckClickClient) if ok and tonumber(ret) == 1 then return true end end return false end function EventPanelClient.MouseReleased() local key = Keys and Keys.LButton or 1 if type(CheckReleasedKey) == "function" and CheckReleasedKey(key) == 1 then return true end if EventPanelClient.MousePressed() ~= true then return true end return false end function EventPanelClient.BlockClick() if type(DisableClickClient) == "function" then pcall(DisableClickClient) end if type(DisableCLick) == "function" then pcall(DisableCLick) end if type(DisableClick) == "function" then pcall(DisableClick) end if type(ResetMouseL) == "function" then pcall(ResetMouseL) end if type(ResetMouseR) == "function" then pcall(ResetMouseR) end -- La DLL espera este bloqueo tambien al pulsar la imagen/boton del panel. -- Sin esta llamada el clic puede pasar al mapa y hacer caminar al personaje. if type(CharacterLockAction) == "function" then pcall(CharacterLockAction, 1) end end function EventPanelClient.SetCharacterLock(active) if CONFIG.LockCharacterWhenOpen ~= true then return end if type(CharacterLockAction) ~= "function" then return end if active == true then pcall(CharacterLockAction, 1) else pcall(CharacterLockAction, 0) end end function EventPanelClient.SetWorldNamesHidden(active) local shouldHide = CONFIG.HideWorldNamesOnPanel == true and active == true if shouldHide == EventPanelClient.WorldNamesHidden then return end if type(DisableShowName) == "function" then local ok = pcall(DisableShowName) if ok == true then EventPanelClient.WorldNamesHidden = shouldHide end end end function EventPanelClient.SetPanelInterfaceLock(active) local shouldLock = false if active == true and EventPanelClient.Opened == true then local wx, wy, ww, wh = EventPanelClient.GetWindowRect() shouldLock = EventPanelClient.MouseIn(wx, wy, ww, wh) == true end EventPanelClient.SetWorldNamesHidden(shouldLock) if CONFIG.LockWorldHoverOnPanel ~= true then shouldLock = false end if shouldLock == true then if EventPanelClient.InterfaceLockHeld ~= true and type(SetLockInterfaces) == "function" then local ok = pcall(SetLockInterfaces) if ok == true then EventPanelClient.InterfaceLockHeld = true end end elseif EventPanelClient.InterfaceLockHeld == true then if type(SetUnlockInterfaces) == "function" then pcall(SetUnlockInterfaces) end EventPanelClient.InterfaceLockHeld = false end end function EventPanelClient.GetMiniButtonImageRect() local b = CONFIG.MiniButton or {} local image = CONFIG.MiniButtonImage or {} local x = tonumber(image.X) or tonumber(b.X) or 6 local y = tonumber(image.Y) or tonumber(b.Y) or 50 local w = tonumber(image.W) or tonumber(b.W) or 86 local h = tonumber(image.H) or tonumber(b.H) or 18 if w < 1 then w = 1 end if h < 1 then h = 1 end return x, y, w, h end function EventPanelClient.GetMiniButtonClickRect() local b = CONFIG.MiniButton or {} local image = CONFIG.MiniButtonImage or {} local click = image.ClickArea or {} if image.Enabled == true and click.FollowImage ~= false and image.ClickFollowsImage ~= false then local ix, iy, iw, ih = EventPanelClient.GetMiniButtonImageRect() local x = ix + (tonumber(click.OffsetX) or 0) local y = iy + (tonumber(click.OffsetY) or 0) local w = tonumber(click.W) or 0 local h = tonumber(click.H) or 0 if w <= 0 then w = iw end if h <= 0 then h = ih end return x, y, w, h end return tonumber(b.X) or 6, tonumber(b.Y) or 50, tonumber(b.W) or 86, tonumber(b.H) or 18 end function EventPanelClient.GetMiniButtonGuideRect() local image = CONFIG.MiniButtonImage or {} local guide = image.GuideArea or {} local baseX, baseY, baseW, baseH if guide.FollowClick ~= false then baseX, baseY, baseW, baseH = EventPanelClient.GetMiniButtonClickRect() else baseX, baseY, baseW, baseH = EventPanelClient.GetMiniButtonImageRect() end local x = baseX + (tonumber(guide.OffsetX) or 0) local y = baseY + (tonumber(guide.OffsetY) or 0) local w = tonumber(guide.W) or 0 local h = tonumber(guide.H) or 0 if w <= 0 then w = baseW end if h <= 0 then h = baseH end return x, y, w, h end function EventPanelClient.GetMiniButtonBackgroundRect() local image = CONFIG.MiniButtonImage or {} local background = image.BackgroundArea or {} local baseX, baseY, baseW, baseH if background.FollowImage ~= false then baseX, baseY, baseW, baseH = EventPanelClient.GetMiniButtonImageRect() else baseX, baseY, baseW, baseH = EventPanelClient.GetMiniButtonClickRect() end local x = baseX + (tonumber(background.OffsetX) or 0) local y = baseY + (tonumber(background.OffsetY) or 0) local w = tonumber(background.W) or 0 local h = tonumber(background.H) or 0 if w <= 0 then w = baseW end if h <= 0 then h = baseH end return x, y, w, h end function EventPanelClient.GetMiniButtonRect() return EventPanelClient.GetMiniButtonClickRect() end function EventPanelClient.IsMouseOverAnyPanelArea() if EventPanelClient.Opened == true then local wx, wy, ww, wh = EventPanelClient.GetWindowRect() local pad = tonumber(CONFIG.InputBlockPadding) or 10 if EventPanelClient.MouseIn(wx - pad, wy - pad, ww + (pad * 2), wh + (pad * 2)) == true then return true end end if CONFIG.ShowMiniButton == true then local bx, by, bw, bh = EventPanelClient.GetMiniButtonRect() if EventPanelClient.MouseIn(bx, by, bw, bh) == true then return true end end return false end function EventPanelClient.HardBlockPanelInput() if CONFIG.Active ~= true then return false end if EventPanelClient.IsMouseOverAnyPanelArea() == true then EventPanelClient.BlockClick() return true end return false end function EventPanelClient.ReleaseClick() if EventPanelClient.MouseReleased() == true or EventPanelClient.MousePressed() ~= true then EventPanelClient.ClickLock = false end end function EventPanelClient.Clicked() if EventPanelClient.MousePressed() == true and EventPanelClient.ClickLock ~= true then EventPanelClient.ClickLock = true return true end return false end function EventPanelClient.Text(x, y, text, width, align, r, g, b, a) if type(SetFontType) == "function" then SetFontType(1) end if type(SetTextBg) == "function" then SetTextBg(0, 0, 0, 0) end if type(SetTextColor) == "function" then SetTextColor(r or 255, g or 255, b or 255, a or 255) end if type(RenderText) == "function" then RenderText(x, y, tostring(text or ""), width or 0, align or 0) end end function EventPanelClient.Box(x, y, w, h, r, g, b, a) if type(SetBlend) == "function" then SetBlend(1) end if type(glColor4f) == "function" then glColor4f(r or 0, g or 0, b or 0, a or 0.75) end if type(DrawBar) == "function" then DrawBar(x, y, w, h) end if type(EndDrawBar) == "function" then EndDrawBar() end if type(glColor4f) == "function" then glColor4f(1, 1, 1, 1) end end function EventPanelClient.DrawPremiumBox(x, y, w, h, alpha) local cfg = CONFIG.PremiumBox or CONFIG.PanelBackground or {} local imageID = tonumber(cfg.ImageID) if cfg.Enabled ~= true or imageID == nil or w <= 0 or h <= 0 then return false end local a = tonumber(alpha) or tonumber(cfg.Alpha) or 1.0 if type(RenderImage2) ~= "function" then return PanelBG500.Draw(x, y, w, h, a, imageID) end local uv = tonumber(cfg.CornerUV) or 0.15 if uv < 0.04 then uv = 0.04 end if uv > 0.30 then uv = 0.30 end local corner = tonumber(cfg.CornerScreen) or 7 corner = math.min(corner, math.max(1, math.floor(w / 3)), math.max(1, math.floor(h / 3))) local midW = math.max(0, w - (corner * 2)) local midH = math.max(0, h - (corner * 2)) local uvMid = 1.0 - (uv * 2) if type(SetBlend) == "function" then pcall(SetBlend, 1) end if type(glColor4f) == "function" then pcall(glColor4f, 1.0, 1.0, 1.0, a) end local drawn = false local function part(dx, dy, dw, dh, u, v, uw, vh) if dw <= 0 or dh <= 0 or uw <= 0 or vh <= 0 then return end local ok = pcall(RenderImage2, imageID, dx, dy, dw, dh, u, v, uw, vh, 1, 1, a) if ok == true then drawn = true end end part(x + corner, y + corner, midW, midH, uv, uv, uvMid, uvMid) part(x + corner, y, midW, corner, uv, 0.0, uvMid, uv) part(x + corner, y + h - corner, midW, corner, uv, 1.0 - uv, uvMid, uv) part(x, y + corner, corner, midH, 0.0, uv, uv, uvMid) part(x + w - corner, y + corner, corner, midH, 1.0 - uv, uv, uv, uvMid) part(x, y, corner, corner, 0.0, 0.0, uv, uv) part(x + w - corner, y, corner, corner, 1.0 - uv, 0.0, uv, uv) part(x, y + h - corner, corner, corner, 0.0, 1.0 - uv, uv, uv) part(x + w - corner, y + h - corner, corner, corner, 1.0 - uv, 1.0 - uv, uv, uv) if type(glColor4f) == "function" then pcall(glColor4f, 1.0, 1.0, 1.0, 1.0) end return drawn end function EventPanelClient.DrawPremiumRow(x, y, w, h, alpha) local cfg = CONFIG.RowBackground or {} local imageID = tonumber(cfg.ImageID) if cfg.Enabled ~= true or imageID == nil or w <= 0 or h <= 0 then return false end local a = tonumber(alpha) or tonumber(cfg.Alpha) or 1.0 if type(RenderImage2) ~= "function" then return PanelBG500.Draw(x, y, w, h, a, imageID) end local uvX = tonumber(cfg.CornerUVX) or 0.10 local uvY = tonumber(cfg.CornerUVY) or 0.25 local cornerX = tonumber(cfg.CornerScreenX) or 8 local cornerY = tonumber(cfg.CornerScreenY) or 4 cornerX = math.min(cornerX, math.max(1, math.floor(w / 3))) cornerY = math.min(cornerY, math.max(1, math.floor(h / 3))) local midW = math.max(0, w - (cornerX * 2)) local midH = math.max(0, h - (cornerY * 2)) local uvMidW = 1.0 - (uvX * 2) local uvMidH = 1.0 - (uvY * 2) if type(SetBlend) == "function" then pcall(SetBlend, 1) end if type(glColor4f) == "function" then pcall(glColor4f, 1.0, 1.0, 1.0, a) end local drawn = false local function part(dx, dy, dw, dh, u, v, uw, vh) if dw <= 0 or dh <= 0 or uw <= 0 or vh <= 0 then return end local ok = pcall(RenderImage2, imageID, dx, dy, dw, dh, u, v, uw, vh, 1, 1, a) if ok == true then drawn = true end end part(x + cornerX, y + cornerY, midW, midH, uvX, uvY, uvMidW, uvMidH) part(x + cornerX, y, midW, cornerY, uvX, 0.0, uvMidW, uvY) part(x + cornerX, y + h - cornerY, midW, cornerY, uvX, 1.0 - uvY, uvMidW, uvY) part(x, y + cornerY, cornerX, midH, 0.0, uvY, uvX, uvMidH) part(x + w - cornerX, y + cornerY, cornerX, midH, 1.0 - uvX, uvY, uvX, uvMidH) part(x, y, cornerX, cornerY, 0.0, 0.0, uvX, uvY) part(x + w - cornerX, y, cornerX, cornerY, 1.0 - uvX, 0.0, uvX, uvY) part(x, y + h - cornerY, cornerX, cornerY, 0.0, 1.0 - uvY, uvX, uvY) part(x + w - cornerX, y + h - cornerY, cornerX, cornerY, 1.0 - uvX, 1.0 - uvY, uvX, uvY) if type(glColor4f) == "function" then pcall(glColor4f, 1.0, 1.0, 1.0, 1.0) end return drawn end function EventPanelClient.DrawRowHoverSweep(x, y, w, h, hover, rowKey) local cfg = CONFIG.RowHoverSweep or {} rowKey = tostring(rowKey or "") local state = EventPanelClient.RowHoverState or { Key = nil, StartFrame = 0 } EventPanelClient.RowHoverState = state if hover ~= true or cfg.Enabled ~= true or w <= 4 or h <= 2 then if state.Key == rowKey then state.Key = nil end return end local frame = tonumber(EventPanelClient.Frame) or 0 if cfg.RestartOnHover ~= false and state.Key ~= rowKey then state.Key = rowKey state.StartFrame = frame elseif state.Key == nil then state.Key = rowKey state.StartFrame = frame end local elapsed = math.max(0, frame - (tonumber(state.StartFrame) or frame)) local color = cfg.Color or { 0.12, 0.68, 1.00 } local r = tonumber(color[1]) or 0.12 local g = tonumber(color[2]) or 0.68 local b = tonumber(color[3]) or 1.00 local pad = 2 local travel = math.max(1, w - (pad * 2)) local sweepW = math.max(18, math.min(travel, tonumber(cfg.Width) or 88)) local speed = math.max(0.25, tonumber(cfg.Speed) or 1.10) local segments = math.max(3, math.floor(tonumber(cfg.Segments) or 8)) local lineH = math.max(1, tonumber(cfg.Height) or 1) local baseAlpha = tonumber(cfg.BaseAlpha) or 0.10 local glowAlpha = tonumber(cfg.GlowAlpha) or 0.20 local coreAlpha = tonumber(cfg.CoreAlpha) or 0.70 local cycle = travel + sweepW local head = (elapsed * speed) % cycle local segmentW = sweepW / segments local function drawLine(lineY) EventPanelClient.Box(x + pad, lineY, travel, lineH, r, g, b, baseAlpha) for i = 0, segments - 1 do local rawX = head - sweepW + (i * segmentW) local rawW = segmentW + 0.5 local clippedX = math.max(0, rawX) local clippedEnd = math.min(travel, rawX + rawW) local clippedW = clippedEnd - clippedX if clippedW > 0 then local strength = (i + 1) / segments EventPanelClient.Box(x + pad + clippedX, lineY - 1, clippedW, lineH + 2, r, g, b, glowAlpha * strength) EventPanelClient.Box(x + pad + clippedX, lineY, clippedW, lineH, r, g, b, coreAlpha * strength) end end end if cfg.Top ~= false then drawLine(y + 1) end if cfg.Bottom ~= false then drawLine(y + h - lineH - 1) end end function EventPanelClient.FrameBox(x, y, w, h) if EventPanelClient.DrawPremiumBox(x, y, w, h) ~= true then EventPanelClient.Box(x, y, w, h, 0.01, 0.03, 0.07, 0.90) end end function EventPanelClient.RenderWindowBackground(x, y, w, h) if EventPanelClient.DrawPremiumBox(x, y, w, h, 1.0) ~= true then EventPanelClient.Box(x, y, w, h, 0.01, 0.03, 0.07, 0.95) end end function EventPanelClient.GlowFrame(x, y, w, h, r, g, b, a) -- Los bordes lineales antiguos quedan deshabilitados. FrameBox dibuja el -- marco premium escalable y los estados usan solamente relleno suave. return false end function EventPanelClient.RenderTooltip(text, x, y) text = tostring(text or "") if text == "" then return end local tw = string.len(text) * 4 + 18 if tw < 90 then tw = 90 end if tw > 220 then tw = 220 end local th = 28 local tx = (tonumber(x) or EventPanelClient.MouseX()) + 12 local ty = (tonumber(y) or EventPanelClient.MouseY()) - 34 if tx < 0 then tx = 0 end if ty < 0 then ty = 0 end EventPanelClient.FrameBox(tx, ty, tw, th) EventPanelClient.Text(tx + 6, ty + 5, text, tw - 12, 1, 255, 230, 150, 255) end function EventPanelClient.Button(x, y, w, h, text, active, selected) local hover = EventPanelClient.MouseIn(x, y, w, h) EventPanelClient.FrameBox(x, y, w, h) if active == false then EventPanelClient.Box(x + 4, y + 3, w - 8, h - 6, 0.08, 0.08, 0.08, 0.45) EventPanelClient.Text(x, y + 5, text, w, 3, 120, 120, 120, 255) return hover end if selected == true then EventPanelClient.Box(x + 4, y + 3, w - 8, h - 6, 0.05, 0.30, 0.65, 0.48) if EventPanelClient.GlowFrame ~= nil then EventPanelClient.GlowFrame(x, y, w, h, 0.10, 0.45, 0.95, 0.70) EventPanelClient.GlowFrame(x + 1, y + 1, w - 2, h - 2, 0.25, 0.65, 1.00, 0.35) end elseif hover then EventPanelClient.Box(x + 4, y + 3, w - 8, h - 6, 0.04, 0.22, 0.48, 0.38) if EventPanelClient.GlowFrame ~= nil then EventPanelClient.GlowFrame(x, y, w, h, 0.10, 0.45, 0.95, 0.35) end else -- El centro oscuro ya forma parte del cuadro premium. end if selected == true then EventPanelClient.Text(x, y + 5, text, w, 3, 185, 220, 255, 255) else EventPanelClient.Text(x, y + 5, text, w, 3, 135, 175, 215, 255) end return hover end function EventPanelClient.GetList() if EventPanelClient.View == "Events" then return CONFIG.NormalEvents or {} end if EventPanelClient.View == "ZoneEvents" then return CONFIG.ZoneEvents or {} end return CONFIG.Invasions or CONFIG.Events or {} end function EventPanelClient.GetViewKey() if EventPanelClient.View == "Events" then return "Events" end if EventPanelClient.View == "ZoneEvents" then return "ZoneEvents" end return "Invasions" end function EventPanelClient.GetEventCount() local list = EventPanelClient.GetList() return #list end function EventPanelClient.GetSelectedIndex() local key = EventPanelClient.GetViewKey() return EventPanelClient.Selected[key] or 1 end function EventPanelClient.SetSelectedIndex(index) local key = EventPanelClient.GetViewKey() EventPanelClient.Selected[key] = index end function EventPanelClient.GetScrollIndex() local key = EventPanelClient.GetViewKey() return EventPanelClient.Scroll[key] or 1 end function EventPanelClient.SetScrollIndex(index) local key = EventPanelClient.GetViewKey() EventPanelClient.Scroll[key] = index end function EventPanelClient.GetEvent() local list = EventPanelClient.GetList() return list[EventPanelClient.GetSelectedIndex()] end function EventPanelClient.GetVisibleRows() local l = CONFIG.Layout or {} local rowH = tonumber(l.RowH) or 16 if EventPanelClient.View == "ZoneEvents" then rowH = tonumber(l.ZoneRowH) or 26 end local listH = tonumber(l.ListH) or 260 local visible = math.floor((listH - 28) / rowH) if visible < 1 then visible = 1 end return visible end function EventPanelClient.GetMaxScroll() local count = EventPanelClient.GetEventCount() local visible = EventPanelClient.GetVisibleRows() local maxScroll = count - visible + 1 if maxScroll < 1 then maxScroll = 1 end return maxScroll end function EventPanelClient.KeepSelectedVisible() local visible = EventPanelClient.GetVisibleRows() local selected = EventPanelClient.GetSelectedIndex() local scroll = EventPanelClient.GetScrollIndex() local maxScroll = EventPanelClient.GetMaxScroll() if selected < scroll then scroll = selected end if selected >= scroll + visible then scroll = selected - visible + 1 end if scroll < 1 then scroll = 1 end if scroll > maxScroll then scroll = maxScroll end EventPanelClient.SetScrollIndex(scroll) end function EventPanelClient.ScrollList(delta) local scroll = EventPanelClient.GetScrollIndex() + (tonumber(delta) or 0) local maxScroll = EventPanelClient.GetMaxScroll() if scroll < 1 then scroll = 1 end if scroll > maxScroll then scroll = maxScroll end EventPanelClient.SetScrollIndex(scroll) end function EventPanelClient.SetScrollFromHandle(trackY, trackH, handleH, maxScroll, mouseY) maxScroll = tonumber(maxScroll) or 1 if maxScroll <= 1 then EventPanelClient.SetScrollIndex(1) return end local range = (tonumber(trackH) or 0) - (tonumber(handleH) or 0) if range <= 0 then EventPanelClient.SetScrollIndex(1) return end local pos = (tonumber(mouseY) or 0) - (tonumber(trackY) or 0) - ((tonumber(handleH) or 0) / 2) if pos < 0 then pos = 0 end if pos > range then pos = range end local value = 1 + math.floor((pos / range) * (maxScroll - 1) + 0.5) if value < 1 then value = 1 end if value > maxScroll then value = maxScroll end EventPanelClient.SetScrollIndex(value) end function EventPanelClient.UpdateScrollDrag(trackY, trackH, handleH, maxScroll, viewKey) local drag = EventPanelClient.ScrollDrag or {} if drag.Active ~= true then return false end if drag.Key ~= viewKey then return false end if EventPanelClient.MousePressed() ~= true then drag.Active = false EventPanelClient.ScrollDrag = drag return false end EventPanelClient.SetScrollFromHandle(trackY, trackH, handleH, maxScroll, EventPanelClient.MouseY()) EventPanelClient.BlockClick() return true end function EventPanelClient.GetMouseWheelDelta() local funcs = { "MouseWheel", "GetMouseWheel", "GetWheel", "GetMouseWheelValue", "GetMouseWheelDelta", "MouseWheelDelta", "MouseWheelValue", "GetMouseWheelState", "GetMouseScroll", "MouseScroll", "GetScroll", "WheelValue", "GetWheelDelta", "GetMouseWheelMove", "GetMouseZ", "GetMouseWheelDir", "GetMouseWheelDirection", } for _, fn in ipairs(funcs) do if type(_G[fn]) == "function" then local ok, value = pcall(_G[fn]) if ok == true and value ~= nil then value = tonumber(value) or 0 if value ~= 0 then return value end end end end if type(MouseWheelUp) == "function" then local ok, value = pcall(MouseWheelUp) if ok == true and tonumber(value) == 1 then return 1 end end if type(MouseWheelDown) == "function" then local ok, value = pcall(MouseWheelDown) if ok == true and tonumber(value) == 1 then return -1 end end if type(CheckMouseWheel) == "function" then local ok, value = pcall(CheckMouseWheel) if ok == true and value ~= nil then value = tonumber(value) or 0 if value ~= 0 then return value end end local okUp, up = pcall(CheckMouseWheel, 1) if okUp == true and tonumber(up) == 1 then return 1 end local okDown, down = pcall(CheckMouseWheel, -1) if okDown == true and tonumber(down) == 1 then return -1 end local okDown2, down2 = pcall(CheckMouseWheel, 0) if okDown2 == true and tonumber(down2) == 1 then return -1 end end if type(Keys) == "table" and type(CheckPressedKey) == "function" then local upKeys = { Keys.MouseWheelUp, Keys.WheelUp, Keys.ScrollUp } local downKeys = { Keys.MouseWheelDown, Keys.WheelDown, Keys.ScrollDown } for _, key in ipairs(upKeys) do if key ~= nil and CheckPressedKey(key) == 1 then return 1 end end for _, key in ipairs(downKeys) do if key ~= nil and CheckPressedKey(key) == 1 then return -1 end end end return 0 end function EventPanelClient.GetListRect() local wx, wy = EventPanelClient.GetWindowPosition() local l = CONFIG.Layout or {} local listX = wx + (tonumber(l.ListX) or 16) local listY = wy + (tonumber(l.ListY) or 70) local listW = tonumber(l.ListW) or 184 local listH = tonumber(l.ListH) or 260 return listX, listY, listW, listH end function EventPanelClient.HandleWheelDelta(delta, force) if EventPanelClient.Opened ~= true then return false end local layout = CONFIG.Layout or {} if layout.MouseWheelActive == false then return false end delta = tonumber(delta) or 0 if delta == 0 then return false end local listX, listY, listW, listH = EventPanelClient.GetListRect() if force ~= true and EventPanelClient.MouseIn(listX, listY, listW, listH) ~= true then return false end local frame = tonumber(EventPanelClient.Frame) or 0 if EventPanelClient.LastWheelFrame == frame then EventPanelClient.BlockClick() return true end EventPanelClient.LastWheelFrame = frame local step = tonumber(layout.MouseWheelStep) or 1 if step < 1 then step = 1 end if delta > 0 then EventPanelClient.ScrollList(-step) else EventPanelClient.ScrollList(step) end EventPanelClient.BlockClick() return true end function EventPanelClient.ProcessWheelOnList(listX, listY, listW, listH) if EventPanelClient.Opened ~= true then return false end if EventPanelClient.DetailOpened ~= nil and EventPanelClient.DetailOpened[EventPanelClient.GetViewKey()] == true then return false end local wx, wy = EventPanelClient.GetWindowPosition() local wcfg = CONFIG.Window or {} local panelW = tonumber(wcfg.W) or 330 local panelH = tonumber(wcfg.H) or 360 local overList = EventPanelClient.MouseIn(listX - 8, listY, listW + 18, listH) local overPanel = EventPanelClient.MouseIn(wx, wy, panelW, panelH) if overList ~= true and overPanel ~= true then return false end return EventPanelClient.HandleWheelDelta(EventPanelClient.GetMouseWheelDelta(), true) end function EventPanelClient.NormalizeScrollDelta(scrollData) local v = tonumber(scrollData) or 0 if v == 0 then return 0 end local delta = 0 if v == 1 then delta = 1 elseif v == 2 then delta = -1 elseif v == -1 then delta = -1 elseif v == 120 then delta = 1 elseif v == -120 then delta = -1 else if v > 0 then delta = 1 else delta = -1 end end if (CONFIG.Layout or {}).MouseWheelInvert == true then delta = delta * -1 end return delta end function EventPanelClient.ProcessWheelArgs(...) local args = { ... } local delta = 0 for i = 1, #args do local raw = args[i] if type(raw) == "string" then local s = string.lower(raw) if string.find(s, "up", 1, true) ~= nil then delta = 1 end if string.find(s, "down", 1, true) ~= nil then delta = -1 end else local v = tonumber(raw) or 0 if v ~= 0 then local av = math.abs(v) if av == 120 or av == 240 or av == 360 or av == 1 or av == 2 or av == 3 then delta = EventPanelClient.NormalizeScrollDelta(v) end end end end if delta ~= 0 then return EventPanelClient.HandleWheelDelta(delta, false) end return false end function EventPanelClient.Select(index, showDetail) local count = EventPanelClient.GetEventCount() index = tonumber(index) or 1 if count <= 0 then EventPanelClient.SetSelectedIndex(1) EventPanelClient.SetScrollIndex(1) return end if index < 1 then index = count end if index > count then index = 1 end EventPanelClient.SetSelectedIndex(index) if showDetail == true then EventPanelClient.DetailOpened[EventPanelClient.GetViewKey()] = true end EventPanelClient.KeepSelectedVisible() end function EventPanelClient.SetView(view) if view ~= "Events" and view ~= "ZoneEvents" then view = "Invasions" end EventPanelClient.View = view EventPanelClient.Select(EventPanelClient.GetSelectedIndex(), false) end function EventPanelClient.Next() EventPanelClient.Select(EventPanelClient.GetSelectedIndex() + 1, true) end function EventPanelClient.Prev() EventPanelClient.Select(EventPanelClient.GetSelectedIndex() - 1, true) end function EventPanelClient.IsChatOpen() if type(UIChatWindow) == "function" then local ok, ret = pcall(UIChatWindow) if ok and (ret == true or ret == 1) then return true end end if type(CheckWindowOpen) == "function" then if type(UIChatWindow) ~= "nil" and type(UIChatWindow) ~= "function" then local ok, ret = pcall(CheckWindowOpen, UIChatWindow) if ok and (ret == true or ret == 1) then return true end end local chatIds = CONFIG.ChatWindowIDs or { 70, 71, 72, 73, 74, 75 } for i = 1, #chatIds do local ok, ret = pcall(CheckWindowOpen, chatIds[i]) if ok and (ret == true or ret == 1) then return true end end if CONFIG.UseCheckWindowOpenNoArgForChat == true then local ok, ret = pcall(CheckWindowOpen) if ok and (ret == true or ret == 1) then return true end end end return false end function EventPanelClient.IsBlockedOpenWindow() if CONFIG.BlockOpenWhenWindowOpen ~= true then return false end if type(CheckWindowOpen) ~= "function" then return false end local ids = CONFIG.BlockOpenWindowIDs or {} for i = 1, #ids do local id = tonumber(ids[i]) if id ~= nil then local ok, ret = pcall(CheckWindowOpen, id) if ok and (ret == true or ret == 1) then return true end end end return false end function EventPanelClient.GetCharacterName() local funcs = { "GetPlayerName", "GetCharName", "UserGetName", "GetMainCharacterName", "GetUserName", "GetName" } for i = 1, #funcs do local fn = _G[funcs[i]] if type(fn) == "function" then local ok, ret = pcall(fn) if ok and ret ~= nil and tostring(ret) ~= "" then return tostring(ret) end end end local vars = { "CharacterName", "PlayerName", "UserName", "gCharacterName" } for i = 1, #vars do local v = _G[vars[i]] if v ~= nil and tostring(v) ~= "" then return tostring(v) end end return "Personaje" end function EventPanelClient.GetTitleText() local title = tostring(CONFIG.Title or "EVENT PANEL") if CONFIG.ShowCharacterNameInTitle == true then local fmt = tostring(CONFIG.TitleFormat or "EVENT PANEL - %s") local ok, ret = pcall(string.format, fmt, EventPanelClient.GetCharacterName()) if ok and ret ~= nil then return tostring(ret) end end return title end function EventPanelClient.ResolveWindowID(value) if value == nil then return nil end if type(value) == "number" then return value end if type(value) == "string" then local n = tonumber(value) if n ~= nil then return n end local g = _G[value] if type(g) == "number" then return g end end return nil end function EventPanelClient.CloseNativeMUWindows() if CONFIG.CloseNativeWindowsOnOpen ~= true then return end local closed = false local function closeID(id) id = tonumber(id) if id == nil then return end if id == tonumber(CONFIG.WindowID) then return end if type(CheckCustomWindow) == "function" then local okCheck, ret = pcall(CheckCustomWindow, id) if okCheck and (ret == true or ret == 1) and type(CloseCustomWindow) == "function" then pcall(CloseCustomWindow, id) closed = true end end if type(CloseCustomWindow) == "function" then pcall(CloseCustomWindow, id) end if type(CloseWindow) == "function" then pcall(CloseWindow, id) end if type(CloseWindowUI) == "function" then pcall(CloseWindowUI, id) end if type(CloseInterface) == "function" then pcall(CloseInterface, id) end if type(CloseWindowLua) == "function" then pcall(CloseWindowLua, id) end end closeID(CONFIG.NativeWindowEventTime or _G.WindowEventTime or 2) local names = CONFIG.NativeWindowNames or {} for i = 1, #names do local id = EventPanelClient.ResolveWindowID(names[i]) if id ~= nil then closeID(id) end end local ids = CONFIG.NativeWindowIDs or {} for i = 1, #ids do closeID(ids[i]) end return closed end function EventPanelClient.StartNativeWindowBlock() EventPanelClient.NativeBlockFrames = tonumber(CONFIG.NativeWindowCloseFrames) or 12 EventPanelClient.CloseNativeMUWindows() end function EventPanelClient.Open() if EventPanelClient.IsChatOpen() == true then return end -- El selector de personajes puede descargar texturas personalizadas. Al -- abrir el panel se registran otra vez solo sus cuatro imagenes. EventPanelClient.LoadImages("panel-open") EventPanelClient.StartNativeWindowBlock() if type(EventPanelClient.RequestServerConfig) == "function" then EventPanelClient.RequestServerConfig() end EventPanelClient.Opened = true EventPanelClient.SetCharacterLock(true) UICustomInterface = CONFIG.WindowID EventPanelClient.KeepSelectedVisible() end function EventPanelClient.Close() EventPanelClient.Opened = false EventPanelClient.SetPanelInterfaceLock(false) EventPanelClient.SetCharacterLock(false) if type(EventPanelClient.HideIvnPreview) == "function" then EventPanelClient.HideIvnPreview(true) end EventPanelClient.ResetDragState() if UICustomInterface == CONFIG.WindowID then UICustomInterface = 0 end end function EventPanelClient.Toggle() if EventPanelClient.Opened == true then EventPanelClient.Close() else EventPanelClient.Open() end end function EventPanelClient.GetIvnPreviewConfig() return CONFIG.IvnLuaPreviewEventPanel or {} end function EventPanelClient.IvnPreviewEnabled() local cfg = EventPanelClient.GetIvnPreviewConfig() if cfg.Enabled ~= true then return false end if IvnLuaPreview == nil then return false end if type(IvnLuaPreview.ShowMonsterEx) ~= "function" and type(IvnLuaPreview.ShowMonster) ~= "function" then return false end return true end function EventPanelClient.HideIvnPreview(force) local cfg = EventPanelClient.GetIvnPreviewConfig() if cfg.HideWhenClosed == false and force ~= true then return false end local slot = tonumber(cfg.Slot) or 10 if EventPanelClient.IvnPreviewVisible ~= true and force ~= true then return true end EventPanelClient.IvnPreviewSignature = "" EventPanelClient.IvnPreviewVisible = false if IvnLuaPreview ~= nil and type(IvnLuaPreview.Hide) == "function" then local ok = pcall(IvnLuaPreview.Hide, slot) return ok == true end return false end function EventPanelClient.ShowIvnPreviewMonster(monsterClass, x, y, w, h, scale, dir) local cfg = EventPanelClient.GetIvnPreviewConfig() if EventPanelClient.IvnPreviewEnabled() ~= true then return false end monsterClass = tonumber(monsterClass) or 0 if monsterClass <= 0 then EventPanelClient.HideIvnPreview(true) return false end local slot = tonumber(cfg.Slot) or 10 if slot < 1 then slot = 10 end if slot > 16 then slot = 10 end x = tonumber(x) or 0 y = tonumber(y) or 0 w = tonumber(w) or 0 h = tonumber(h) or 0 scale = tonumber(scale) or 0 if cfg.UsePluginVisual == true then scale = 0 end dir = tonumber(dir) or tonumber(cfg.Dir) or 90 if w <= 0 or h <= 0 then EventPanelClient.HideIvnPreview(true) return false end local objectId = tonumber(cfg.ObjectID) or 0 if objectId <= 0 then objectId = 9900 + slot end local signature = tostring(slot) .. "|" .. tostring(monsterClass) .. "|" .. tostring(math.floor(x)) .. "|" .. tostring(math.floor(y)) .. "|" .. tostring(math.floor(w)) .. "|" .. tostring(math.floor(h)) .. "|" .. tostring(scale) .. "|" .. tostring(dir) if EventPanelClient.IvnPreviewVisible == true and EventPanelClient.IvnPreviewSignature == signature then return true end local ok = false if type(IvnLuaPreview.ShowMonsterEx) == "function" then local safe, ret = pcall(IvnLuaPreview.ShowMonsterEx, slot, monsterClass, objectId, x, y, w, h, scale, dir) ok = (safe == true and ret ~= false) end if ok ~= true and type(IvnLuaPreview.ShowMonster) == "function" then local safe, ret = pcall(IvnLuaPreview.ShowMonster, slot, monsterClass, x, y, w, h, scale, dir, objectId) ok = (safe == true and ret ~= false) end if ok == true then EventPanelClient.IvnPreviewSignature = signature EventPanelClient.IvnPreviewVisible = true return true end return false end function EventPanelClient.GetMonsterObject(monsterClass) monsterClass = tonumber(monsterClass) or -1 if monsterClass <= 0 then return nil end if EventPanelClient.MonsterCache[monsterClass] ~= nil then return EventPanelClient.MonsterCache[monsterClass] end if type(LoadMonster) ~= "function" then return nil end local ok, obj = pcall(LoadMonster, monsterClass) if ok == true and obj ~= nil then EventPanelClient.MonsterCache[monsterClass] = obj return obj end return nil end function EventPanelClient.RenderMonster(monsterClass, x, y, scale) if type(RenderMonster) ~= "function" then return false end monsterClass = tonumber(monsterClass) or -1 if monsterClass <= 0 then return false end local obj = EventPanelClient.GetMonsterObject(monsterClass) if obj ~= nil then local ok = pcall(RenderMonster, obj, x, y, scale or 0.58) if ok == true then return true end end local ok2 = pcall(RenderMonster, monsterClass, x, y, scale or 0.58) return ok2 == true end function EventPanelClient.RenderMonsterDirect(monsterClass, x, y, scale) if type(RenderMonster) ~= "function" then return false end monsterClass = tonumber(monsterClass) or -1 if monsterClass <= 0 then return false end local ok = pcall(RenderMonster, monsterClass, x, y, scale or 0.58) if ok == true then return true end local obj = EventPanelClient.GetMonsterObject(monsterClass) if obj ~= nil then local ok2 = pcall(RenderMonster, obj, x, y, scale or 0.58) if ok2 == true then return true end end return false end function EventPanelClient.RenderMonsterHoverRotation(monsterClass, x, y, scale, hover) monsterClass = tonumber(monsterClass) or -1 if monsterClass <= 0 then return false end if hover == true and CONFIG.MonsterHoverRotate == true and type(RenderMonsterRotation) == "function" then local obj = EventPanelClient.GetMonsterObject(monsterClass) if obj ~= nil then local okObj = pcall(RenderMonsterRotation, obj, x, y, scale or 0.58) if okObj == true then return true end end local okClass = pcall(RenderMonsterRotation, monsterClass, x, y, scale or 0.58) if okClass == true then return true end end return EventPanelClient.RenderMonster(monsterClass, x, y, scale) end function EventPanelClient.GET_ITEM(typeId, indexId) if type(GET_ITEM) == "function" then return GET_ITEM(typeId, indexId) end return ((tonumber(typeId) or 0) * 512) + (tonumber(indexId) or 0) end function EventPanelClient.GET_ITEM_MODEL(typeId, indexId) if type(GET_ITEM_MODEL) == "function" then return GET_ITEM_MODEL(typeId, indexId) end return (((tonumber(typeId) or 0) * 512) + (tonumber(indexId) or 0)) + 1171 end function EventPanelClient.GetItemRotate(item, hover) if item ~= nil and item.Rotate ~= nil then return tonumber(item.Rotate) or 0 end if hover == true and CONFIG.ItemHoverRotate == true then if CONFIG.ItemHoverRotateUseMapTime == true and type(MapTime) == "function" then local ok, mt = pcall(MapTime) if ok and tonumber(mt) ~= nil then local speed = tonumber(CONFIG.ItemHoverRotateSpeed) or 1 return math.floor((tonumber(mt) * speed) % 360) end end return tonumber(CONFIG.ItemRotate) or 1 end return tonumber(CONFIG.ItemStaticRotate) or 0 end function EventPanelClient.RenderItem(item, x, y, w, h, scale, rotate) if item == nil then return false end local itemId = tonumber(item.ItemIndex) if itemId == nil or itemId <= 0 then local typeId = tonumber(item.Type) local indexId = tonumber(item.Index) if typeId == nil or indexId == nil or typeId < 0 or indexId < 0 then return false end if tonumber(CONFIG.ItemRenderMode) == 2 then itemId = EventPanelClient.GET_ITEM_MODEL(typeId, indexId) else itemId = EventPanelClient.GET_ITEM(typeId, indexId) end end local level = tonumber(item.Level) or 0 local skill = tonumber(item.Skill) or 0 local luck = tonumber(item.Luck) or 0 local realScale = tonumber(scale) or tonumber(CONFIG.ItemSize) or 0.52 local realRotate = tonumber(rotate) or 0 local ok = false if type(CreateItemSize) == "function" then ok = pcall(CreateItemSize, x, y, w or 32, h or 32, itemId, level, skill, luck, realRotate, realScale) if ok ~= true then ok = pcall(CreateItemSize, x, y, w or 32, h or 32, itemId, level, 0, 0, realRotate, realScale) end end if ok ~= true and type(CreateItem) == "function" then ok = pcall(CreateItem, x, y, w or 32, h or 32, itemId, level, skill, luck, realRotate) if ok ~= true then ok = pcall(CreateItem, x, y, w or 32, h or 32, itemId, level, 0, 0, realRotate) end end return ok == true end function EventPanelClient.GetRealItemIndex(item) if item == nil then return nil end local itemId = tonumber(item.ItemIndex) if itemId ~= nil and itemId > 0 then return itemId end local typeId = tonumber(item.Type) local indexId = tonumber(item.Index) if typeId == nil or indexId == nil or typeId < 0 or indexId < 0 then return nil end return EventPanelClient.GET_ITEM(typeId, indexId) end function EventPanelClient.RenderRealItemTooltip(item, x, y) if item == nil then return false end if type(ShowDescriptionComplete) ~= "function" then return false end local itemIndex = EventPanelClient.GET_ITEM( tonumber(item.Type or item.ItemType or item.Section) or 0, tonumber(item.Index or item.ItemIndex or item.ItemID) or 0 ) local level = tonumber(item.Level or item.ItemLevel) or 0 local dur = tonumber(item.Durability or item.Dur or item.ItemDurability) or 255 local op1 = tonumber(item.Skill or item.Option1 or item.Op1 or item.ItemOption1) or 0 local op2 = tonumber(item.Luck or item.Option2 or item.Op2 or item.ItemOption2) or 0 local op3 = tonumber(item.Option or item.Option3 or item.Op3 or item.ItemOption3 or item.LifeOption) or 0 local exc = tonumber(item.Excellent or item.Exc or item.NewOption or item.ItemNewOption or item.ExtOption) or 0 local ancient = tonumber(item.Ancient or item.SetOption or item.ItemSetOption) or 0 local joh = tonumber(item.JOH or item.Harmony or item.JewelOfHarmonyOption or item.ItemJewelOfHarmonyOption) or 0 local socket = tonumber(item.Socket or item.SocketOption or item.ItemSocketOption) or 0 local sock1 = tonumber(item.Socket1 or item.Sock1 or item.ItemSocketOption1) or 255 local sock2 = tonumber(item.Socket2 or item.Sock2 or item.ItemSocketOption2) or 255 local sock3 = tonumber(item.Socket3 or item.Sock3 or item.ItemSocketOption3) or 255 local sock4 = tonumber(item.Socket4 or item.Sock4 or item.ItemSocketOption4) or 255 local sock5 = tonumber(item.Socket5 or item.Sock5 or item.ItemSocketOption5) or 255 local mx = tonumber(x) or EventPanelClient.MouseX() + 14 local my = tonumber(y) or EventPanelClient.MouseY() + 14 local ok = pcall( ShowDescriptionComplete, mx, my, itemIndex, level, dur, op1, op2, op3, exc, ancient, joh, socket, sock1, sock2, sock3, sock4, sock5 ) return ok == true end function EventPanelClient.GetDropItems(ev) if ev ~= nil and type(ev.DropItems) == "table" then return ev.DropItems end if type(CONFIG.DefaultDropItems) == "table" then return CONFIG.DefaultDropItems end return {} end function EventPanelClient.GetMonsterVisual(ev) local l = CONFIG.Layout or {} local d = CONFIG.MonsterVisualDefault or {} local visual = tonumber(ev and ev.VisualIndex) or tonumber(ev and ev.BossIndex) or 0 local v = nil if CONFIG.MonsterVisual ~= nil then v = CONFIG.MonsterVisual[visual] end if v == nil then v = {} end -- PreviewScale es un divisor sencillo: 1 = normal; cuanto mayor sea, -- mas pequena se dibuja la imagen. La escala base sigue siendo por clase. local baseScale = tonumber(v.Scale) or tonumber(d.Scale) or tonumber(l.MonsterScale) or 0.58 local sizeDivisor = tonumber(ev and ev.PreviewScale) or tonumber(ev and ev.Scale) or 1 if sizeDivisor <= 0 then sizeDivisor = 1 end local scale = baseScale / sizeDivisor -- Correccion limitada al GOLEM de Atlans (zona 104). No toca la escala -- global, el cuadro ni los demas eventos. local zoneVisual = nil if ev ~= nil and ev.ZoneTracked == true and CONFIG.ZoneMonsterVisual ~= nil then zoneVisual = CONFIG.ZoneMonsterVisual[tonumber(ev.Index) or -1] end if type(zoneVisual) == "table" then local copyIndex = tonumber(zoneVisual.CopyScaleFromIndex) if copyIndex ~= nil then for _, referenceEvent in ipairs(CONFIG.ZoneEvents or {}) do if tonumber(referenceEvent.Index) == copyIndex then local referenceVisual = tonumber(referenceEvent.VisualIndex) or tonumber(referenceEvent.BossIndex) or 0 local referenceConfig = (CONFIG.MonsterVisual or {})[referenceVisual] or {} local referenceBase = tonumber(referenceConfig.Scale) or tonumber(d.Scale) or tonumber(l.MonsterScale) or 0.58 local referenceDivisor = tonumber(referenceEvent.PreviewScale) or tonumber(referenceEvent.Scale) or 1 if referenceDivisor <= 0 then referenceDivisor = 1 end scale = referenceBase / referenceDivisor break end end elseif tonumber(zoneVisual.Scale) ~= nil then scale = tonumber(zoneVisual.Scale) end end local addX = tonumber(ev and ev.PreviewX) or tonumber(ev and ev.MonsterAddX) or tonumber(v.AddX) or tonumber(d.AddX) or tonumber(l.MonsterAddX) or 48 local addY = tonumber(ev and ev.PreviewY) or tonumber(ev and ev.MonsterAddY) or tonumber(v.AddY) or tonumber(d.AddY) or tonumber(l.MonsterAddY) or 132 return scale, addX, addY end function EventPanelClient.IsTextureAllowed(image) if type(image) ~= "table" or image.Enabled ~= true then return false end local sourceW = tonumber(image.SourceW) or 0 local sourceH = tonumber(image.SourceH) or 0 local policy = CONFIG.TexturePolicy or {} if sourceW > 0 and sourceH > 0 then if sourceW > (tonumber(policy.MaxWidth) or 512) or sourceH > (tonumber(policy.MaxHeight) or 512) then return false end end return type(image.Path) == "string" and image.Path ~= "" and tonumber(image.ImageID) ~= nil end function EventPanelClient.LoadImages(reason) if type(LoadBitmap) ~= "function" then return false end local assets = { CONFIG.MiniButtonImage, CONFIG.PanelBackground, CONFIG.RowBackground } local loaded = 0 for _, image in ipairs(assets) do if EventPanelClient.IsTextureAllowed(image) == true then local ok = pcall(LoadBitmap, image.Path, image.ImageID) if ok == true then loaded = loaded + 1 end end end EventPanelClient.TextureReloadPending = false return loaded > 0 end function EventPanelClient.QueueTextureReload() local delay = tonumber((CONFIG.TexturePolicy or {}).ReloadDelayFrames) or 60 local secondPass = tonumber((CONFIG.TexturePolicy or {}).ReloadSecondPassFrames) or 90 EventPanelClient.TextureReloadPending = true EventPanelClient.ConfigRequestPending = true EventPanelClient.TextureReloadFrame = (tonumber(EventPanelClient.Frame) or 0) + math.max(1, math.floor(delay)) EventPanelClient.TextureReloadSecondFrame = EventPanelClient.TextureReloadFrame + math.max(1, math.floor(secondPass)) end function EventPanelClient.ProcessTextureReload() local frame = tonumber(EventPanelClient.Frame) or 0 if EventPanelClient.TextureReloadPending == true and frame >= (tonumber(EventPanelClient.TextureReloadFrame) or 0) then EventPanelClient.LoadImages("character-entry") if EventPanelClient.ConfigRequestPending == true and type(EventPanelClient.RequestServerConfig) == "function" then EventPanelClient.ConfigRequestPending = false EventPanelClient.RequestServerConfig() end end if (tonumber(EventPanelClient.TextureReloadSecondFrame) or 0) > 0 and frame >= (tonumber(EventPanelClient.TextureReloadSecondFrame) or 0) then EventPanelClient.TextureReloadSecondFrame = 0 EventPanelClient.LoadImages("character-entry-second-pass") end end function EventPanelClient.DetectCharacterEntry() local token = "" local funcs = { "UserGetName", "GetPlayerName", "GetCharName", "GetMainCharacterName" } for i = 1, #funcs do local fn = _G[funcs[i]] if type(fn) == "function" then local ok, value = pcall(fn) if ok == true and value ~= nil and tostring(value) ~= "" then token = tostring(value) break end end end local previous = tostring(EventPanelClient.LastCharacterToken or "") if token ~= "" and token ~= previous then EventPanelClient.LastCharacterToken = token EventPanelClient.QueueTextureReload("character-detected") elseif token == "" and previous ~= "" then EventPanelClient.LastCharacterToken = "" end end function EventPanelClient.RenderMiniButtonImage(defaultX, defaultY, defaultW, defaultH) local image = CONFIG.MiniButtonImage or {} if image.Enabled ~= true then return false end local imageID = tonumber(image.ImageID) or 40018 local x, y, w, h = EventPanelClient.GetMiniButtonImageRect() x = tonumber(x) or tonumber(defaultX) or 6 y = tonumber(y) or tonumber(defaultY) or 50 w = tonumber(w) or tonumber(defaultW) or 86 h = tonumber(h) or tonumber(defaultH) or 18 local crop = image.TextureCrop or {} local cropX = tonumber(crop.X) or 0.0 local cropY = tonumber(crop.Y) or 0.0 local cropW = tonumber(crop.W) or 1.0 local cropH = tonumber(crop.H) or 1.0 if cropX < 0.0 then cropX = 0.0 end if cropY < 0.0 then cropY = 0.0 end if cropW <= 0.0 then cropW = 1.0 end if cropH <= 0.0 then cropH = 1.0 end if cropX > 1.0 then cropX = 1.0 end if cropY > 1.0 then cropY = 1.0 end if cropX + cropW > 1.0 then cropW = 1.0 - cropX end if cropY + cropH > 1.0 then cropH = 1.0 - cropY end local alpha = tonumber(image.Alpha) or 1.0 if alpha < 0 then alpha = 0 end if alpha > 1 then alpha = 1 end if type(SetBlend) == "function" then SetBlend(1) end if type(glColor4f) == "function" then glColor4f(1.0, 1.0, 1.0, alpha) end local called = false if type(RenderImage2) == "function" then local ok = pcall(RenderImage2, imageID, x, y, w, h, cropX, cropY, cropW, cropH, 1, 1, alpha) if ok == true then called = true end end if called ~= true and type(RenderImageScale) == "function" then local ok = pcall(RenderImageScale, imageID, x, y, w, h, cropX, cropY, cropW, cropH, alpha) if ok == true then called = true end end if called ~= true and image.AllowUncroppedFallback == true and type(RenderImage) == "function" then local ok = pcall(RenderImage, imageID, x, y, w, h) if ok == true then called = true end end if type(glColor4f) == "function" then glColor4f(1.0, 1.0, 1.0, 1.0) end return called end function EventPanelClient.RenderMiniButton() if CONFIG.ShowMiniButton ~= true then return end local b = CONFIG.MiniButton or {} local image = CONFIG.MiniButtonImage or {} local imageX, imageY, imageW, imageH = EventPanelClient.GetMiniButtonImageRect() local clickX, clickY, clickW, clickH = EventPanelClient.GetMiniButtonClickRect() local guideX, guideY, guideW, guideH = EventPanelClient.GetMiniButtonGuideRect() local backgroundX, backgroundY, backgroundW, backgroundH = EventPanelClient.GetMiniButtonBackgroundRect() local hover = EventPanelClient.MouseIn(clickX, clickY, clickW, clickH) if image.Enabled == true then if image.DrawBackground == true then local backgroundAlpha = tonumber(image.BackgroundAlpha) or 0.72 if backgroundAlpha < 0.0 then backgroundAlpha = 0.0 end if backgroundAlpha > 1.0 then backgroundAlpha = 1.0 end if hover == true then EventPanelClient.Box( backgroundX, backgroundY, backgroundW, backgroundH, 0.04, 0.14, 0.30, backgroundAlpha ) else EventPanelClient.Box( backgroundX, backgroundY, backgroundW, backgroundH, 0.03, 0.06, 0.12, backgroundAlpha ) end end EventPanelClient.RenderMiniButtonImage(imageX, imageY, imageW, imageH) if image.ShowGuide == true then EventPanelClient.GlowFrame(guideX, guideY, guideW, guideH, 0.10, 0.45, 0.95, 0.90) if hover == true and guideW > 2 and guideH > 2 then EventPanelClient.GlowFrame(guideX + 1, guideY + 1, guideW - 2, guideH - 2, 0.25, 0.65, 1.00, 0.55) end if image.ShowCenterGuide == true then local centerX = guideX + math.floor(guideW / 2) local centerY = guideY + math.floor(guideH / 2) EventPanelClient.Box(centerX, guideY, 1, guideH, 0.20, 0.75, 1.00, 0.65) EventPanelClient.Box(guideX, centerY, guideW, 1, 0.20, 0.75, 1.00, 0.65) end if image.ShowGuideText == true then local gx = guideX + (tonumber(image.GuideTextX) or 0) local gy = guideY + guideH + (tonumber(image.GuideTextY) or 8) local gw = tonumber(image.GuideTextW) or 260 EventPanelClient.Text( gx, gy, "IMG X=" .. tostring(imageX) .. " Y=" .. tostring(imageY) .. " W=" .. tostring(imageW) .. " H=" .. tostring(imageH), gw, 1, 120, 220, 255, 255 ) EventPanelClient.Text( gx, gy + 10, "CLIC X=" .. tostring(clickX) .. " Y=" .. tostring(clickY) .. " W=" .. tostring(clickW) .. " H=" .. tostring(clickH), gw, 1, 255, 230, 150, 255 ) end end if image.ShowText == true then local textX = imageX + (tonumber(image.TextOffsetX) or 0) local textY = imageY + (tonumber(image.TextOffsetY) or -14) EventPanelClient.Text(textX, textY, tostring(b.Text or "EVENTOS [H]"), imageW, 3, 255, 255, 255, 255) end else hover = EventPanelClient.Button(clickX, clickY, clickW, clickH, tostring(b.Text or "EVENTOS [H]"), true, false) end if hover == true then EventPanelClient.BlockClick() if EventPanelClient.Clicked() == true then EventPanelClient.Open() end end end function EventPanelClient.RenderTabs(panelX, panelY) local l = CONFIG.Layout or {} local tx = panelX + (tonumber(l.TabX) or 16) local ty = panelY + (tonumber(l.TabY) or 43) local tw = tonumber(l.TabW) or 90 local th = tonumber(l.TabH) or 19 local gap = 8 local evHover = EventPanelClient.Button(tx, ty, tw, th, "EVENTOS", true, EventPanelClient.View == "Events") if evHover == true and EventPanelClient.Clicked() == true then EventPanelClient.SetView("Events") end local invHover = EventPanelClient.Button(tx + tw + gap, ty, tw, th, "INVASIONES", true, EventPanelClient.View == "Invasions") if invHover == true and EventPanelClient.Clicked() == true then EventPanelClient.SetView("Invasions") end local zoneHover = EventPanelClient.Button( tx + ((tw + gap) * 2), ty, tw, th, "ZONA EVENTOS", true, EventPanelClient.View == "ZoneEvents" ) if zoneHover == true and EventPanelClient.Clicked() == true then EventPanelClient.SetView("ZoneEvents") end end function EventPanelClient.GetTimeZoneSelector() local cfg = CONFIG.TimeZoneSelector if type(cfg) ~= "table" then return nil end return cfg end function EventPanelClient.GetCountryEntry(code) local cfg = EventPanelClient.GetTimeZoneSelector() if cfg == nil or type(cfg.Countries) ~= "table" then return nil end code = string.upper(tostring(code or "")) for _, entry in ipairs(cfg.Countries) do if type(entry) == "table" and string.upper(tostring(entry.Code or "")) == code then return entry end end return nil end function EventPanelClient.GetSelectedCountryCode() local cfg = EventPanelClient.GetTimeZoneSelector() local fallback = tostring((cfg and cfg.Default) or "PER") local code = string.upper(tostring(EventPanelClient.TimeZoneCode or fallback)) if EventPanelClient.GetCountryEntry(code) == nil then code = string.upper(fallback) end return code end function EventPanelClient.LastSundayOfMonth(year, month) year = tonumber(year) or 0 month = tonumber(month) or 0 if year <= 0 or month < 1 or month > 12 then return 31 end local days = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 } if (year % 400 == 0) or ((year % 4 == 0) and (year % 100 ~= 0)) then days[2] = 29 end local lastDay = days[month] or 31 local okTime, stamp = pcall(os.time, { year = year, month = month, day = lastDay, hour = 12, min = 0, sec = 0 }) if okTime ~= true or stamp == nil then return lastDay end local okDate, data = pcall(os.date, "*t", stamp) if okDate ~= true or type(data) ~= "table" then return lastDay end local wday = tonumber(data.wday) or 1 return lastDay - (wday - 1) end function EventPanelClient.IsSpainSummerTime() local ok, utc = pcall(os.date, "!*t") if ok ~= true or type(utc) ~= "table" then return false end local year = tonumber(utc.year) or 0 local month = tonumber(utc.month) or 0 local day = tonumber(utc.day) or 0 local hour = tonumber(utc.hour) or 0 if month > 3 and month < 10 then return true end if month < 3 or month > 10 then return false end local lastSunday = EventPanelClient.LastSundayOfMonth(year, month) if month == 3 then return day > lastSunday or (day == lastSunday and hour >= 1) end return day < lastSunday or (day == lastSunday and hour < 1) end function EventPanelClient.GetCountryRelativeOffsetMinutes(code) local entry = EventPanelClient.GetCountryEntry(code) if entry == nil then return 0 end local offset = tonumber(entry.RelativeOffsetMinutes) or 0 if string.upper(tostring(entry.Code or "")) == "ES" and entry.AutoSummerTime == true and EventPanelClient.IsSpainSummerTime() == true then offset = tonumber(entry.SummerRelativeOffsetMinutes) or offset end return offset end function EventPanelClient.GetCountryUTCOffsetMinutes(code) local cfg = EventPanelClient.GetTimeZoneSelector() local base = tonumber(cfg and cfg.BaseUTCOffsetMinutes) or -300 return base + EventPanelClient.GetCountryRelativeOffsetMinutes(code) end function EventPanelClient.SetCountryCode(code) code = string.upper(tostring(code or "")) if EventPanelClient.GetCountryEntry(code) == nil then return false end EventPanelClient.TimeZoneCode = code return true end function EventPanelClient.ConvertClockText(text) text = tostring(text or "") if text == "" then return text end local offset = EventPanelClient.GetCountryRelativeOffsetMinutes(EventPanelClient.GetSelectedCountryCode()) return string.gsub(text, "(%d%d):(%d%d)", function(h, m) local hh = tonumber(h) local mm = tonumber(m) if hh == nil or mm == nil or hh < 0 or hh > 23 or mm < 0 or mm > 59 then return tostring(h) .. ":" .. tostring(m) end local total = (hh * 60) + mm + offset total = total % 1440 if total < 0 then total = total + 1440 end return string.format("%02d:%02d", math.floor(total / 60), total % 60) end) end function EventPanelClient.GetScheduleDisplayLines(ev, clocksPerLine) local clocks = {} local seen = {} if type(ev) == "table" and type(ev.Schedule) == "table" then for _, value in ipairs(ev.Schedule) do local raw = tostring(value or "") for h, m in string.gmatch(raw, "(%d%d):(%d%d)") do local clock = tostring(h) .. ":" .. tostring(m) if seen[clock] ~= true then seen[clock] = true clocks[#clocks + 1] = EventPanelClient.ConvertClockText(clock) end end end end local perLine = tonumber(clocksPerLine) or 4 if perLine < 1 then perLine = 1 end local lines = {} for index = 1, #clocks, perLine do local parts = {} for offset = 0, perLine - 1 do local value = clocks[index + offset] if value == nil then break end parts[#parts + 1] = value end lines[#lines + 1] = table.concat(parts, " ") end return lines end function EventPanelClient.RenderCountryButtons(panelX, panelY, panelW, panelH) local cfg = EventPanelClient.GetTimeZoneSelector() if cfg == nil or cfg.Active ~= true or type(cfg.Countries) ~= "table" then return end local count = #cfg.Countries if count <= 0 then return end local bw = tonumber(cfg.ButtonW) or 30 local bh = tonumber(cfg.ButtonH) or 16 local gap = tonumber(cfg.Gap) or 4 local bottom = tonumber(cfg.Bottom) or 22 local totalW = (count * bw) + ((count - 1) * gap) local bx = panelX + math.floor((panelW - totalW) / 2) local by = panelY + panelH - bottom local selected = EventPanelClient.GetSelectedCountryCode() for _, entry in ipairs(cfg.Countries) do local code = string.upper(tostring(entry.Code or "")) local hover = EventPanelClient.Button(bx, by, bw, bh, code, true, code == selected) if hover == true and EventPanelClient.Clicked() == true then EventPanelClient.SetCountryCode(code) selected = code end bx = bx + bw + gap end end function EventPanelClient.NowSeconds() local cfg = EventPanelClient.GetTimeZoneSelector() if cfg ~= nil and cfg.Active == true then local okUTC, utc = pcall(os.date, "!*t") if okUTC == true and type(utc) == "table" then local seconds = ((tonumber(utc.hour) or 0) * 3600) + ((tonumber(utc.min) or 0) * 60) + (tonumber(utc.sec) or 0) + (EventPanelClient.GetCountryUTCOffsetMinutes(EventPanelClient.GetSelectedCountryCode()) * 60) seconds = seconds % 86400 if seconds < 0 then seconds = seconds + 86400 end return seconds end end local ok, t = pcall(os.date, "*t") if ok == true and t ~= nil then return ((tonumber(t.hour) or 0) * 3600) + ((tonumber(t.min) or 0) * 60) + (tonumber(t.sec) or 0) end return 0 end function EventPanelClient.ParseSchedules(ev) if ev == nil then return {} end local code = EventPanelClient.GetSelectedCountryCode() local offset = EventPanelClient.GetCountryRelativeOffsetMinutes(code) local cacheKey = tostring(code) .. ":" .. tostring(offset) ev._ScheduleCacheByZone = ev._ScheduleCacheByZone or {} if ev._ScheduleCacheByZone[cacheKey] ~= nil then return ev._ScheduleCacheByZone[cacheKey] end local list = {} local seen = {} local function addFromText(text) text = tostring(text or "") for h, m in string.gmatch(text, "(%d%d):(%d%d)") do local hh = tonumber(h) or 0 local mm = tonumber(m) or 0 if hh >= 0 and hh <= 23 and mm >= 0 and mm <= 59 then local totalMinutes = ((hh * 60) + mm + offset) % 1440 if totalMinutes < 0 then totalMinutes = totalMinutes + 1440 end if seen[totalMinutes] ~= true then seen[totalMinutes] = true local localHour = math.floor(totalMinutes / 60) local localMinute = totalMinutes % 60 table.insert(list, { Hour = localHour, Minute = localMinute, Seconds = (localHour * 3600) + (localMinute * 60), Text = string.format("%02d:%02d", localHour, localMinute), }) end end end end local hasSchedule = false if type(ev.Schedule) == "table" then for _, value in ipairs(ev.Schedule) do addFromText(value) end hasSchedule = (#list > 0) end if hasSchedule ~= true then addFromText(ev.Time1) addFromText(ev.Time2) addFromText(ev.Time3) end table.sort(list, function(a, b) return (a.Seconds or 0) < (b.Seconds or 0) end) ev._ScheduleCacheByZone[cacheKey] = list return list end function EventPanelClient.FormatTime(seconds) seconds = tonumber(seconds) or 0 if seconds < 0 then seconds = 0 end local h = math.floor(seconds / 3600) local m = math.floor((seconds % 3600) / 60) local s = math.floor(seconds % 60) if CONFIG.Timer ~= nil and CONFIG.Timer.ShowSeconds == false then return string.format("%02d:%02d", h, m) end return string.format("%02d:%02d:%02d", h, m, s) end function EventPanelClient.GetTimerInfo(ev) if ev == nil or CONFIG.Timer == nil or CONFIG.Timer.Active ~= true then return { Status = tostring(ev and ev.Status or CONFIG.DefaultStatus or "Configurar"), Online = false, Seconds = 0, NextText = "--:--", ActiveStart = -1, ActiveEnd = -1, Killed = false, } end local schedules = EventPanelClient.ParseSchedules(ev) if #schedules <= 0 then return { Status = tostring(ev.Status or CONFIG.DefaultStatus or "Configurar"), Online = false, Seconds = 0, NextText = "configurar", ActiveStart = -1, ActiveEnd = -1, Killed = false, } end local now = EventPanelClient.NowSeconds() local duration = tonumber(ev.DurationMinutes or CONFIG.Timer.DefaultDurationMinutes or 15) or 15 if duration <= 0 then duration = 15 end local durationSeconds = duration * 60 local activeItem = nil local activeAge = nil local nextItem = nil local nextDiff = nil for _, item in ipairs(schedules) do local startSeconds = tonumber(item.Seconds) or 0 local age = now - startSeconds if age < 0 then age = age + 86400 end if age < durationSeconds and (activeAge == nil or age < activeAge) then activeItem = item activeAge = age end local diff = startSeconds - now if diff <= 0 then diff = diff + 86400 end if nextDiff == nil or diff < nextDiff then nextDiff = diff nextItem = item end end nextDiff = tonumber(nextDiff) or 0 local nextText = tostring(nextItem and nextItem.Text or "--:--") local eventIndex = tonumber(ev.Index) or -1 local normalEvent = (ev.NoMonster == true) if normalEvent == true then if activeItem ~= nil then local remaining = durationSeconds - (tonumber(activeAge) or 0) if remaining < 0 then remaining = 0 end return { Status = EventPanelClient.FormatTime(remaining), Online = true, ActiveCountdown = true, Seconds = remaining, NextText = nextText, ActiveStart = tonumber(activeItem.Seconds) or -1, ActiveEnd = ((tonumber(activeItem.Seconds) or 0) + durationSeconds) % 86400, Killed = false, ScheduleDetected = true, } end return { Status = EventPanelClient.FormatTime(nextDiff), Online = false, Seconds = nextDiff, NextText = nextText, ActiveStart = -1, ActiveEnd = -1, Killed = false, ScheduleDetected = true, ActiveCountdown = false, } end local countData = eventIndex >= 0 and EventPanelClient.LiveCount[eventIndex] or nil local realLive = countData and (tonumber(countData.Live) or 0) or 0 if activeItem ~= nil then local remaining = durationSeconds - (tonumber(activeAge) or 0) if remaining < 0 then remaining = 0 end return { Status = EventPanelClient.FormatTime(remaining), Online = true, ActiveCountdown = true, Seconds = remaining, NextText = nextText, ActiveStart = tonumber(activeItem.Seconds) or -1, ActiveEnd = ((tonumber(activeItem.Seconds) or 0) + durationSeconds) % 86400, Killed = false, LiveDetected = realLive > 0, ScheduleDetected = true, } end return { Status = EventPanelClient.FormatTime(nextDiff), Online = false, ActiveCountdown = false, Seconds = nextDiff, NextText = nextText, ActiveStart = -1, ActiveEnd = -1, Killed = false, LiveDetected = realLive > 0, ScheduleDetected = true, } end function EventPanelClient.MarkKilled(eventIndex) eventIndex = tonumber(eventIndex) or -1 EventPanelClient.ForceOnline[eventIndex] = nil local list = CONFIG.Invasions or CONFIG.Events or {} for _, ev in ipairs(list) do if tonumber(ev.Index) == eventIndex then local info = EventPanelClient.GetTimerInfo(ev) EventPanelClient.KilledState[eventIndex] = tonumber(info.ActiveStart) or EventPanelClient.NowSeconds() return end end end function EventPanelClient.SetLive(eventIndex, active) eventIndex = tonumber(eventIndex) or -1 if eventIndex < 0 then return end if active ~= true then EventPanelClient.ForceOnline[eventIndex] = nil local current = EventPanelClient.LiveCount[eventIndex] if current ~= nil then EventPanelClient.LiveCount[eventIndex] = { Live = 0, Total = tonumber(current.Total) or 0 } end end end function EventPanelClient.SetLiveCount(eventIndex, liveCount, totalCount) eventIndex = tonumber(eventIndex) or -1 if eventIndex < 0 then return end liveCount = tonumber(liveCount) or 0 totalCount = tonumber(totalCount) or 0 if liveCount < 0 then liveCount = 0 end if totalCount < 0 then totalCount = 0 end EventPanelClient.LiveCount[eventIndex] = { Live = liveCount, Total = totalCount } if liveCount > 0 then EventPanelClient.KilledState[eventIndex] = nil EventPanelClient.CompleteState[eventIndex] = nil EventPanelClient.ForceOnline[eventIndex] = true EventPanelClient.WasOnline[eventIndex] = true else EventPanelClient.ForceOnline[eventIndex] = nil EventPanelClient.CompleteState[eventIndex] = nil end end function EventPanelClient.GetMobTotal(ev) if ev == nil then return 0 end local total = tonumber(ev.MobTotal or ev.MonsterTotal or ev.MobCount or 0) or 0 if total <= 0 and CONFIG.MobCounter ~= nil then if tostring(ev.Type or "") == "Boss" then total = tonumber(CONFIG.MobCounter.DefaultBossTotal) or 1 else total = tonumber(CONFIG.MobCounter.DefaultInvasionTotal) or 20 end end return total end function EventPanelClient.GetMobCounterData(ev) if CONFIG.MobCounter == nil or CONFIG.MobCounter.Active ~= true then return nil end if ev == nil then return nil end local eventIndex = tonumber(ev.Index) or -1 if eventIndex < 0 then return nil end local total = EventPanelClient.GetMobTotal(ev) local live = nil local data = EventPanelClient.LiveCount[eventIndex] if data ~= nil then local serverTotal = tonumber(data.Total) or 0 if serverTotal > 0 then total = serverTotal end live = tonumber(data.Live) end if total <= 0 then return nil end local info = EventPanelClient.GetTimerInfo(ev) local prefix = "Inv" if tostring(ev.Type or "") == "Boss" then prefix = "Boss" end local online = (info ~= nil and info.Online == true) if live == nil then live = total end live = tonumber(live) or 0 if live < 0 then live = 0 end if live > total then live = total end local dead = total - live if dead < 0 then dead = 0 end if dead > total then dead = total end return { Prefix = prefix, Online = online, Live = live, Dead = dead, Total = total } end function EventPanelClient.GetMobCounterText(ev) local data = EventPanelClient.GetMobCounterData(ev) if data == nil then return "" end local killed = tonumber(data.Dead) or 0 if killed < 0 then killed = 0 end if killed > (tonumber(data.Total) or 0) then killed = tonumber(data.Total) or 0 end if data.Online ~= true then return "" end return data.Prefix .. " " .. tostring(killed) .. "/" .. tostring(data.Total) end function EventPanelClient.RenderMobProgressBar(ev, x, y, w, h) local data = EventPanelClient.GetMobCounterData(ev) if data == nil then return end local total = tonumber(data.Total) or 0 local live = tonumber(data.Live) or 0 if total <= 0 then return end if live < 0 then live = 0 end if live > total then live = total end local pct = live / total local fillW = math.floor((tonumber(w) or 0) * pct) if fillW < 0 then fillW = 0 end EventPanelClient.Box(x, y, w, h, 0.08, 0.08, 0.08, 0.95) if data.Online == true and fillW > 0 then EventPanelClient.Box(x, y, fillW, h, 0.15, 0.95, 0.18, 0.95) elseif data.Online ~= true then EventPanelClient.Box(x, y, w, h, 0.60, 0.05, 0.05, 0.80) end end function EventPanelClient.GetMobCounterColor(ev) if ev == nil then return 255, 80, 80, 255 end local info = EventPanelClient.GetTimerInfo(ev) if info ~= nil and info.Online == true then return 80, 255, 80, 255 end return 255, 80, 80, 255 end function EventPanelClient.RenderList(x, y, w, h) EventPanelClient.FrameBox(x, y, w, h) local title = "INVASIONES" if EventPanelClient.View == "Events" then title = "EVENTOS" elseif EventPanelClient.View == "ZoneEvents" then title = "ZONA EVENTOS" end EventPanelClient.Text(x, y + 8, title, w, 3, 255, 220, 130, 255) local list = EventPanelClient.GetList() local rowH = tonumber((CONFIG.Layout or {}).RowH) or 16 if EventPanelClient.View == "ZoneEvents" then rowH = tonumber((CONFIG.Layout or {}).ZoneRowH) or 26 end local visible = EventPanelClient.GetVisibleRows() local startIndex = EventPanelClient.GetScrollIndex() local selected = EventPanelClient.GetSelectedIndex() local count = #list local maxScroll = EventPanelClient.GetMaxScroll() if startIndex > maxScroll then startIndex = maxScroll EventPanelClient.SetScrollIndex(startIndex) end if startIndex < 1 then startIndex = 1 EventPanelClient.SetScrollIndex(startIndex) end local hasScroll = count > visible local scrollW = 0 if hasScroll == true then scrollW = 13 end for row = 0, visible - 1 do local index = startIndex + row if index > count then break end local ev = list[index] local ry = y + 28 + (row * rowH) local rowW = w - 12 - scrollW local hover = EventPanelClient.MouseIn(x + 4, ry - 1, rowW + 4, rowH + 1) local rowAlpha = tonumber((CONFIG.RowBackground or {}).Alpha) or 0.96 if index == selected then rowAlpha = 1.0 end -- La propia textura premium representa la fila completa. No se superpone -- el antiguo rectangulo azul de seleccion. EventPanelClient.DrawPremiumRow(x + 4, ry - 1, rowW + 4, rowH + 1, rowAlpha) local hoverKey = EventPanelClient.GetViewKey() .. ":" .. tostring(index) EventPanelClient.DrawRowHoverSweep(x + 6, ry, rowW, rowH - 1, hover, hoverKey) local name = tostring(ev.Name or "Evento") local info = EventPanelClient.GetTimerInfo(ev) local lcfg = CONFIG.Layout or {} local leftWidth = tonumber(lcfg.ListNameW) or (rowW - 72) if leftWidth < 70 then leftWidth = 70 end if ev.ZoneTracked == true then leftWidth = 172 local nr, ng, nb = 120, 255, 120 if info.Online ~= true then nr, ng, nb = 255, 150, 40 end EventPanelClient.Text(x + 10, ry + 3, name, leftWidth, 1, nr, ng, nb, 255) else EventPanelClient.Text(x + 10, ry + 3, name, leftWidth, 1, 120, 255, 120, 255) end local normalEvent = (ev.NoMonster == true) local cr, cg, cb, ca = 255, 150, 40, 255 local statusToShow = tostring(info.Status or "--:--") if info.Online == true then cr, cg, cb, ca = 80, 255, 80, 255 statusToShow = tostring(info.Status or "00:00:00") elseif info.Killed == true then cr, cg, cb, ca = 255, 150, 40, 255 end local timeX = x + (tonumber(lcfg.ListTimeX) or (rowW - 46)) local timeW = tonumber(lcfg.ListTimeW) or 58 if ev.ZoneTracked == true then timeX, timeW = x + 178, 108 if info.Online == true then statusToShow = "ONLINE" else statusToShow = "OFFLINE" if (tonumber(info.Seconds) or 0) > 0 then statusToShow = statusToShow .. " " .. EventPanelClient.FormatTime(info.Seconds) end end end EventPanelClient.Text(timeX, ry + 3, statusToShow, timeW, 1, cr, cg, cb, ca) if hover == true and EventPanelClient.Clicked() == true then EventPanelClient.Select(index, true) end end if hasScroll == true then local sbX = x + w - 16 local sbY = y + 28 local sbW = 12 local sbH = h - 34 EventPanelClient.Box(sbX, sbY, sbW, sbH, 0.04, 0.04, 0.04, 0.86) EventPanelClient.FrameBox(sbX, sbY, sbW, sbH) local upY = sbY + 2 local downY = sbY + sbH - 14 EventPanelClient.Text(sbX, upY, "^", sbW, 3, 255, 220, 130, 255) EventPanelClient.Text(sbX, downY, "v", sbW, 3, 255, 220, 130, 255) local hold = EventPanelClient.ScrollHold or { Dir = 0, Delay = 0 } local dir = 0 if EventPanelClient.MouseIn(sbX - 4, upY, sbW + 8, 12) then dir = -1 end if EventPanelClient.MouseIn(sbX - 4, downY, sbW + 8, 12) then dir = 1 end if dir ~= 0 and EventPanelClient.MousePressed() == true then EventPanelClient.BlockClick() if hold.Dir ~= dir then hold.Dir = dir hold.Delay = 0 end if EventPanelClient.Clicked() == true or hold.Delay <= 0 then EventPanelClient.ScrollList(dir) hold.Delay = 1 else hold.Delay = hold.Delay - 1 end EventPanelClient.ScrollHold = hold else EventPanelClient.ScrollHold = { Dir = 0, Delay = 0 } end local trackY = sbY + 16 local trackH = sbH - 34 local handleH = math.floor((visible / count) * trackH) if handleH < 18 then handleH = 18 end if handleH > trackH then handleH = trackH end local denom = maxScroll - 1 local handleY = trackY if denom > 0 then handleY = trackY + math.floor(((startIndex - 1) / denom) * (trackH - handleH)) end EventPanelClient.Box(sbX + 1, handleY, sbW - 2, handleH, 0.08, 0.34, 0.70, 0.95) local viewKey = EventPanelClient.GetViewKey() if (CONFIG.Layout or {}).ScrollDragActive ~= false then if EventPanelClient.MouseIn(sbX - 5, trackY, sbW + 10, trackH) and EventPanelClient.MousePressed() == true then EventPanelClient.ScrollDrag = { Active = true, Key = viewKey, StartY = EventPanelClient.MouseY(), StartScroll = startIndex } EventPanelClient.SetScrollFromHandle(trackY, trackH, handleH, maxScroll, EventPanelClient.MouseY()) EventPanelClient.BlockClick() return end if EventPanelClient.UpdateScrollDrag(trackY, trackH, handleH, maxScroll, viewKey) == true then return end end if EventPanelClient.MouseIn(sbX - 5, trackY, sbW + 10, trackH) and EventPanelClient.Clicked() then EventPanelClient.SetScrollFromHandle(trackY, trackH, handleH, maxScroll, EventPanelClient.MouseY()) EventPanelClient.BlockClick() end end end function EventPanelClient.RenderMonsterPreview(ev, x, y, w, h) if EventPanelClient.View == "Events" then EventPanelClient.HideIvnPreview(false) return end if ev == nil or ev.NoMonster == true then EventPanelClient.HideIvnPreview(false) return end local visualIndex = tonumber(ev.VisualIndex) or tonumber(ev.BossIndex) or 0 if visualIndex <= 0 then EventPanelClient.HideIvnPreview(false) return end local l = CONFIG.Layout or {} local bx = x + (tonumber(l.MonsterBoxX) or 118) local by = y + (tonumber(l.MonsterBoxY) or 76) local bw = tonumber(l.MonsterBoxW) or 96 local bh = tonumber(l.MonsterBoxH) or 150 local hover = EventPanelClient.MouseIn(bx, by, bw, bh) EventPanelClient.FrameBox(bx, by, bw, bh) if hover == true and CONFIG.MonsterHoverRotate == true then EventPanelClient.GlowFrame(bx, by, bw, bh, 0.20, 1.00, 0.25, 0.80) else EventPanelClient.GlowFrame(bx, by, bw, bh, 0.10, 0.45, 0.95, 0.45) end local scale, addX, addY = EventPanelClient.GetMonsterVisual(ev) local rx = bx + addX local ry = by + addY local ivnCfg = EventPanelClient.GetIvnPreviewConfig() local padX = tonumber(ivnCfg.BoxPaddingX) or 2 local padY = tonumber(ivnCfg.BoxPaddingY) or 4 local padBottom = tonumber(ivnCfg.BoxPaddingBottom) or 31 local previewX = bx + padX local previewY = by + padY local previewW = bw - (padX * 2) local previewH = bh - padY - padBottom if previewW < 10 then previewW = bw end if previewH < 10 then previewH = bh end local ok = EventPanelClient.ShowIvnPreviewMonster( visualIndex, previewX, previewY, previewW, previewH, scale, tonumber(ivnCfg.Dir) or 90 ) if ok ~= true and ivnCfg.UseFallbackIfMissing ~= false then ok = EventPanelClient.RenderMonsterHoverRotation(visualIndex, rx, ry, scale, hover) end if ok ~= true and ev.ForceDirectRender == true then local okDirect = false if hover == true and CONFIG.MonsterHoverRotate == true and type(RenderMonsterRotation) == "function" then okDirect = pcall(RenderMonsterRotation, visualIndex, rx, ry, scale) else okDirect = EventPanelClient.RenderMonsterDirect(visualIndex, rx, ry, scale) end if okDirect == true then ok = true end end if ok ~= true and tonumber(ev.BossIndex) ~= nil and tonumber(ev.BossIndex) ~= visualIndex then ok = EventPanelClient.RenderMonsterHoverRotation(tonumber(ev.BossIndex), rx, ry, scale, hover) if ev.ForceDirectRender == true then local okBossDirect = false if hover == true and CONFIG.MonsterHoverRotate == true and type(RenderMonsterRotation) == "function" then okBossDirect = pcall(RenderMonsterRotation, tonumber(ev.BossIndex), rx, ry, scale) else okBossDirect = EventPanelClient.RenderMonsterDirect(tonumber(ev.BossIndex), rx, ry, scale) end if okBossDirect == true then ok = true end end end if hover == true and CONFIG.MonsterHoverTooltip == true then local tooltip = tostring(ev.MonsterTooltip or ev.Name or "Monster") if tooltip ~= "" then EventPanelClient.RenderTooltip(tooltip, EventPanelClient.MouseX(), EventPanelClient.MouseY()) end end local counterText = EventPanelClient.GetMobCounterText(ev) local cr, cg, cb, ca = EventPanelClient.GetMobCounterColor(ev) local labelY = by + bh - 27 if EventPanelClient.DrawPremiumBox(bx + 2, labelY - 1, bw - 4, 26, 0.98) ~= true then EventPanelClient.Box(bx + 3, labelY, bw - 6, 24, 0.00, 0.00, 0.00, 0.78) end if counterText ~= nil and counterText ~= "" then EventPanelClient.Text(bx + 4, labelY + 4, counterText, bw - 8, 3, cr, cg, cb, ca) EventPanelClient.RenderMobProgressBar(ev, bx + 8, labelY + 17, bw - 16, 5) end if ok ~= true then EventPanelClient.Text(bx, by + 64, "no disponible", bw, 3, 255, 90, 90, 255) EventPanelClient.Text(bx, by + 80, "VisualIndex", bw, 3, 180, 180, 180, 255) EventPanelClient.Text(bx, by + 96, tostring(visualIndex), bw, 3, 255, 220, 130, 255) end end function EventPanelClient.RenderDropItems(ev, x, y, w, h) if EventPanelClient.View == "Events" then return end local l = CONFIG.Layout or {} local dl = ev.DropLayout or ev.DropPosition or {} local sourceItems = EventPanelClient.GetDropItems(ev) local items = {} for _, item in ipairs(sourceItems) do if type(item) == "table" and item.Enabled ~= false then items[#items + 1] = item end end if #items <= 0 then return end -- Los cuadros nacen de la cantidad enviada por MuServer. Hasta 16 items -- se acomodan en una o dos filas; al quitar un item desaparece su cuadro. while #items > 16 do table.remove(items) end local columns = math.min(8, #items) local rows = math.ceil(#items / columns) local boxW = tonumber(dl.DropBoxW or l.DropBoxW) or 40 local boxH = tonumber(dl.DropBoxH or l.DropBoxH) or 40 local gap = tonumber(dl.DropBoxGap or l.DropBoxGap) or 6 if columns >= 7 then boxW = math.min(boxW, 33) boxH = math.min(boxH, 36) gap = math.min(gap, 3) elseif columns >= 6 then boxW = math.min(boxW, 36) gap = math.min(gap, 4) end local rowGap = 5 local startY = y + (rows > 1 and 164 or (tonumber(dl.DropBoxY or l.DropBoxY) or (h - 60))) local tooltipText = nil local tooltipItem = nil EventPanelClient.Text(x, startY - 13, "Drops posibles:", w, 3, 255, 220, 130, 255) for i = 1, #items do local item = items[i] if type(item) == "table" then local row = math.floor((i - 1) / columns) local col = (i - 1) % columns local remaining = #items - (row * columns) local rowCount = math.min(columns, remaining) local rowWidth = (rowCount * boxW) + ((rowCount - 1) * gap) local rowStartX = x + math.floor((w - rowWidth) / 2) local bx = rowStartX + (col * (boxW + gap)) local by = startY + (row * (boxH + rowGap)) bx = x + (tonumber(item.BoxX) or tonumber(item.X) or (bx - x)) by = y + (tonumber(item.BoxY) or tonumber(item.Y) or (by - y)) local hover = EventPanelClient.MouseIn(bx, by, boxW, boxH) EventPanelClient.FrameBox(bx, by, boxW, boxH) if hover == true then EventPanelClient.GlowFrame(bx, by, boxW, boxH, 0.20, 1.00, 0.25, 0.80) else EventPanelClient.GlowFrame(bx, by, boxW, boxH, 0.10, 0.45, 0.95, 0.35) end local itemRotate = EventPanelClient.GetItemRotate(item, hover) local itemRendered = EventPanelClient.RenderItem( item, bx + (tonumber(item.ItemX) or tonumber(dl.DropItemX) or tonumber(l.DropItemX) or -10), by + (tonumber(item.ItemY) or tonumber(dl.DropItemY) or tonumber(l.DropItemY) or -15), tonumber(item.ItemW) or tonumber(dl.DropItemW) or tonumber(l.DropItemW) or 48, tonumber(item.ItemH) or tonumber(dl.DropItemH) or tonumber(l.DropItemH) or 48, tonumber(item.Scale) or tonumber(dl.DropItemScale) or tonumber(l.DropItemScale) or 0.52, itemRotate ) if itemRendered ~= true then local c = item.Color or { 120, 200, 255, 255 } local rr = tonumber(c[1]) or 255 local gg = tonumber(c[2]) or 220 local bb = tonumber(c[3]) or 80 local aa = tonumber(c[4]) or 255 local tag = tostring(item.Tag or item.Name or "DROP") local nameText = tostring(item.Name or "Drop") if string.len(tag) > 6 then tag = string.sub(tag, 1, 6) end if string.len(nameText) > 12 then nameText = string.sub(nameText, 1, 12) end EventPanelClient.Text(bx, by + 7, tag, boxW, 3, rr, gg, bb, aa) EventPanelClient.Text(bx - 12, by + boxH + 2, nameText, boxW + 24, 3, 220, 220, 220, 255) end if hover == true and CONFIG.ShowDropTooltips == true then tooltipItem = item tooltipText = tostring(item.Tooltip or item.Description or item.Name or "Item " .. tostring(item.Type) .. "/" .. tostring(item.Index)) end end end if CONFIG.ShowDropTooltips == true and tooltipItem ~= nil then local mx = EventPanelClient.MouseX() + 12 local my = EventPanelClient.MouseY() - 12 if EventPanelClient.RenderRealItemTooltip(tooltipItem, mx, my) == true then return end end if CONFIG.ShowDropTooltips == true and tooltipText ~= nil and tooltipText ~= "" then EventPanelClient.RenderTooltip(tooltipText, EventPanelClient.MouseX(), EventPanelClient.MouseY()) end end function EventPanelClient.RenderDetail(x, y, w, h) local ev = EventPanelClient.GetEvent() EventPanelClient.FrameBox(x, y, w, h) if ev == nil then EventPanelClient.Text(x, y + 28, "Sin datos configurados", w, 3, 255, 80, 80, 255) return end local name = tostring(ev.Name or "Evento") local leftW = 110 if EventPanelClient.View == "Events" then leftW = w - 20 end EventPanelClient.Text(x, y + 10, name, w, 3, 255, 220, 100, 255) local _topInfo = EventPanelClient.GetTimerInfo(ev) local _topStatus = tostring(_topInfo.DetailStatus or _topInfo.Status or "--:--") local _normalEvent = (ev.NoMonster == true) local _tr, _tg, _tb, _ta = 255, 150, 40, 255 if _topInfo.Online == true then _topStatus = tostring(_topInfo.DetailStatus or _topInfo.Status or "00:00:00") _tr, _tg, _tb, _ta = 80, 255, 80, 255 elseif _topInfo.Killed == true then _tr, _tg, _tb, _ta = 255, 150, 40, 255 end EventPanelClient.Text(x, y + 24, "[" .. _topStatus .. "]", w, 3, _tr, _tg, _tb, _ta) local rewardText = tostring(ev.RewardText or ev.WCText or ev.Reward or "") if rewardText ~= "" then local lReward = CONFIG.Layout or {} local rbX = x + (tonumber(lReward.RewardBoxX) or 214) local rbY = y + (tonumber(lReward.RewardBoxY) or 20) local rbW = tonumber(lReward.RewardBoxW) or 70 local rbH = tonumber(lReward.RewardBoxH) or 15 local rtX = x + (tonumber(lReward.RewardTextX) or 214) local rtY = y + (tonumber(lReward.RewardTextY) or 23) local rtW = tonumber(lReward.RewardTextW) or 70 EventPanelClient.FrameBox(rbX, rbY, rbW, rbH) EventPanelClient.Text(rtX, rtY, rewardText, rtW, 3, 120, 200, 255, 255) end if ev.NoSchedule == true then local eventType = tostring(ev.Type or "Boss") local baseY = y + 42 EventPanelClient.Text(x + 10, baseY, "Tipo: " .. eventType, leftW, 1, 120, 220, 255, 255) EventPanelClient.Text( x + 10, baseY + 18, "Respawn: " .. tostring(ev.RespawnText or ev.Status or "Sin hora fija"), leftW, 1, 255, 220, 130, 255 ) local mapBoxX = x + 8 local mapBoxY = baseY + 48 local mapBoxW = leftW + 4 local mapBoxH = 16 EventPanelClient.FrameBox(mapBoxX, mapBoxY, mapBoxW, mapBoxH) EventPanelClient.Text(mapBoxX, mapBoxY + 2, tostring(ev.Map or "Mapa"), mapBoxW, 3, 120, 225, 255, 255) EventPanelClient.Text(x + 10, mapBoxY + 22, tostring(ev.Coords or ""), w - 20, 1, 180, 220, 255, 255) EventPanelClient.RenderMonsterPreview(ev, x, y, w, h) EventPanelClient.RenderDropItems(ev, x, y, w, h) EventPanelClient.Text(x, y + h - 18, "< LISTA para volver", w, 3, 180, 180, 180, 255) return end local info = EventPanelClient.GetTimerInfo(ev) local eventType = tostring(ev.Type or (EventPanelClient.View == "Invasions" and "Invasion" or "Event")) local statusText = "Estado: " .. tostring(info.Status or ev.Status or CONFIG.DefaultStatus or "configurable") local time1Text = EventPanelClient.ConvertClockText(ev.Time1 or ev.Time or "") local time2Text = EventPanelClient.ConvertClockText(ev.Time2 or "") local time3Text = EventPanelClient.ConvertClockText(ev.Time3 or "") local time4Text = EventPanelClient.ConvertClockText(ev.Time4 or "") local visibleScheduleText = table.concat({ time1Text, time2Text, time3Text, time4Text }, " ") local scheduleLines = EventPanelClient.GetScheduleDisplayLines(ev, 3) if #scheduleLines > 0 then time1Text = scheduleLines[1] or "" time2Text = scheduleLines[2] or "" time3Text = scheduleLines[3] or "" time4Text = scheduleLines[4] or "" elseif string.find(visibleScheduleText, "%d%d:%d%d") == nil then time1Text = scheduleLines[1] or "Sin horario configurado" time2Text = scheduleLines[2] or "" time3Text = scheduleLines[3] or "" time4Text = scheduleLines[4] or "" end local mapText = tostring(ev.Map or CONFIG.DefaultMap or "Mapa: configurar") local coordText = tostring(ev.Coords or CONFIG.DefaultCoords or "Coords: configurar") local durationText = "Duracion: " .. tostring(ev.DurationMinutes or (CONFIG.Timer and CONFIG.Timer.DefaultDurationMinutes) or 10) .. " min" local baseY = y + 36 EventPanelClient.Text(x + 10, baseY, "Tipo: " .. eventType, leftW, 1, 120, 220, 255, 255) local sr, sg, sb, sa = 255, 150, 40, 255 if info.Online == true then sr, sg, sb, sa = 80, 255, 80, 255 elseif ev.NoMonster ~= true then sr, sg, sb, sa = 255, 230, 70, 255 end EventPanelClient.Text(x + 10, baseY + 16, statusText, leftW, 1, sr, sg, sb, sa) EventPanelClient.Text( x + 10, baseY + 32, "Proximo: " .. tostring(info.NextText or "--:--"), leftW, 1, 255, 220, 130, 255 ) EventPanelClient.Text(x + 10, baseY + 48, durationText, leftW, 1, 210, 210, 210, 255) local mobCounterText = EventPanelClient.GetMobCounterText(ev) EventPanelClient.Text( x + 10, baseY + 68, "Horarios [" .. EventPanelClient.GetSelectedCountryCode() .. "]:", leftW, 1, 255, 220, 130, 255 ) local lineY = baseY + 83 EventPanelClient.Text(x + 10, lineY, time1Text, leftW, 1, 235, 235, 235, 255) lineY = lineY + 15 if time2Text ~= "" then EventPanelClient.Text(x + 10, lineY, time2Text, leftW, 1, 235, 235, 235, 255) lineY = lineY + 15 end if time3Text ~= "" then EventPanelClient.Text(x + 10, lineY, time3Text, leftW, 1, 235, 235, 235, 255) lineY = lineY + 15 end if time4Text ~= "" then EventPanelClient.Text(x + 10, lineY, time4Text, leftW, 1, 235, 235, 235, 255) lineY = lineY + 15 end lineY = lineY + 5 local mapBoxX = x + 8 local mapBoxY = lineY - 2 local mapBoxW = leftW + 4 local mapBoxH = 16 EventPanelClient.FrameBox(mapBoxX, mapBoxY, mapBoxW, mapBoxH) EventPanelClient.Text(mapBoxX, lineY, mapText, mapBoxW, 3, 120, 225, 255, 255) lineY = lineY + 18 EventPanelClient.Text(x + 10, lineY, coordText, w - 20, 1, 180, 220, 255, 255) EventPanelClient.RenderMonsterPreview(ev, x, y, w, h) EventPanelClient.RenderDropItems(ev, x, y, w, h) EventPanelClient.Text(x, y + h - 18, "< LISTA para volver", w, 3, 180, 180, 180, 255) end function EventPanelClient.RenderArrows(panelX, panelY) local l = CONFIG.Layout or {} local y = panelY + (tonumber(l.ArrowY) or 303) local lx = panelX + (tonumber(l.LeftArrowX) or 214) local rx = panelX + (tonumber(l.RightArrowX) or 422) local leftHover = EventPanelClient.Button(lx, y, 22, 24, "<", true, false) if leftHover == true and EventPanelClient.Clicked() == true then EventPanelClient.Prev() end local rightHover = EventPanelClient.Button(rx, y, 22, 24, ">", true, false) if rightHover == true and EventPanelClient.Clicked() == true then EventPanelClient.Next() end end function EventPanelClient.ClampWindowPosition(x, y, w, h) w = tonumber(w) or 330 h = tonumber(h) or 360 local maxW = 640 if type(GetWideX) == "function" then local ok, v = pcall(GetWideX) if ok == true and tonumber(v) ~= nil and tonumber(v) > 0 then maxW = tonumber(v) end elseif type(GetScreenWidth) == "function" then local ok, v = pcall(GetScreenWidth) if ok == true and tonumber(v) ~= nil and tonumber(v) > 0 then maxW = tonumber(v) end end if x < 0 then x = 0 end if y < 0 then y = 0 end if x + w > maxW then x = maxW - w end if y + h > 470 then y = 470 - h end if x < 0 then x = 0 end if y < 0 then y = 0 end return x, y end function EventPanelClient.GetWindowPosition() local wcfg = CONFIG.Window or {} if EventPanelClient.WindowX == nil then EventPanelClient.WindowX = tonumber(wcfg.X) or 92 end if EventPanelClient.WindowY == nil then EventPanelClient.WindowY = tonumber(wcfg.Y) or 34 end return EventPanelClient.WindowX, EventPanelClient.WindowY end function EventPanelClient.GetWindowRect() local wcfg = CONFIG.Window or {} local x, y = EventPanelClient.GetWindowPosition() local w = tonumber(wcfg.W) or 330 local h = tonumber(wcfg.H) or 360 return x, y, w, h end function EventPanelClient.IsOverCloseButton(x, y, w, h) return EventPanelClient.MouseIn(x + w - 32, y + 4, 30, 28) end function EventPanelClient.IsOverDragArea(x, y, w, h) local wcfg = CONFIG.Window or {} local dragH = tonumber(wcfg.DragH) or 42 if wcfg.DragFullWindow == true then if EventPanelClient.IsOverCloseButton(x, y, w, h) == true then return false end return EventPanelClient.MouseIn(x, y, w, h) end return EventPanelClient.MouseIn(x, y, w - 34, dragH) end function EventPanelClient.ResetDragState() EventPanelClient.Dragging = false EventPanelClient.DragPossible = false EventPanelClient.DragDX = 0 EventPanelClient.DragDY = 0 EventPanelClient.DragStartX = 0 EventPanelClient.DragStartY = 0 end function EventPanelClient.BeginDragCandidate(x, y, w, h) if EventPanelClient.DragPossible == true or EventPanelClient.Dragging == true then return true end if EventPanelClient.IsOverDragArea(x, y, w, h) ~= true then return false end EventPanelClient.DragPossible = true EventPanelClient.DragStartX = EventPanelClient.MouseX() EventPanelClient.DragStartY = EventPanelClient.MouseY() EventPanelClient.DragDX = EventPanelClient.DragStartX - x EventPanelClient.DragDY = EventPanelClient.DragStartY - y return true end function EventPanelClient.UpdateDrag(x, y, w, h) local wcfg = CONFIG.Window or {} if wcfg.Drag ~= true then return x, y end local winH = tonumber(h) or tonumber(wcfg.H) or 360 local mx = EventPanelClient.MouseX() local my = EventPanelClient.MouseY() local mouseDown = EventPanelClient.MousePressed() if mouseDown ~= true then if EventPanelClient.Dragging == true then EventPanelClient.ResetDragState() EventPanelClient.BlockClick() return EventPanelClient.GetWindowPosition() end if EventPanelClient.DragPossible == true then EventPanelClient.DragPossible = false end return x, y end if EventPanelClient.Dragging == true then local nx = mx - (EventPanelClient.DragDX or 0) local ny = my - (EventPanelClient.DragDY or 0) nx, ny = EventPanelClient.ClampWindowPosition(nx, ny, w, winH) EventPanelClient.WindowX = nx EventPanelClient.WindowY = ny EventPanelClient.ClickLock = true EventPanelClient.BlockClick() return nx, ny end if EventPanelClient.DragPossible ~= true then EventPanelClient.BeginDragCandidate(x, y, w, winH) return x, y end local threshold = tonumber(wcfg.DragThreshold) or 2 local dx = math.abs(mx - (EventPanelClient.DragStartX or mx)) local dy = math.abs(my - (EventPanelClient.DragStartY or my)) if dx >= threshold or dy >= threshold then EventPanelClient.Dragging = true EventPanelClient.ClickLock = true local nx = mx - (EventPanelClient.DragDX or 0) local ny = my - (EventPanelClient.DragDY or 0) nx, ny = EventPanelClient.ClampWindowPosition(nx, ny, w, winH) EventPanelClient.WindowX = nx EventPanelClient.WindowY = ny EventPanelClient.BlockClick() return nx, ny end return x, y end function EventPanelClient.RenderWindow() local wcfg = CONFIG.Window or {} local l = CONFIG.Layout or {} local x, y = EventPanelClient.GetWindowPosition() local w = tonumber(wcfg.W) or 330 local h = tonumber(wcfg.H) or 360 x, y = EventPanelClient.UpdateDrag(x, y, w, h) EventPanelClient.RenderWindowBackground(x, y, w, h) if CONFIG.SolidHeader == true then EventPanelClient.DrawPremiumBox(x + 8, y + 5, w - 16, 38, tonumber(CONFIG.SolidHeaderAlpha) or 0.98) end local detailMode = EventPanelClient.DetailOpened[EventPanelClient.GetViewKey()] == true local currentEvent = EventPanelClient.GetEvent() local titleText = EventPanelClient.GetTitleText() local subText = tostring(CONFIG.SubTitle or "EVENTOS / INVASIONES") if detailMode == true and currentEvent ~= nil then subText = tostring(currentEvent.Name or CONFIG.DetailSubTitle or "DETALLE") end EventPanelClient.Text(x, y + 9, titleText, w, 3, 120, 200, 255, 255) EventPanelClient.Text(x, y + 25, subText, w, 3, 235, 235, 235, 255) local closeHover = EventPanelClient.Button(x + w - 28, y + 8, 18, 18, "X", true, false) if closeHover == true and EventPanelClient.Clicked() == true then EventPanelClient.Close() return end if detailMode == true then local backHover = EventPanelClient.Button(x + 14, y + 43, 74, 20, "< LISTA", true, false) if backHover == true and EventPanelClient.Clicked() == true then EventPanelClient.DetailOpened[EventPanelClient.GetViewKey()] = false return end EventPanelClient.RenderDetail( x + (tonumber(l.DetailX) or 16), y + (tonumber(l.DetailY) or 70), tonumber(l.DetailW) or (w - 32), tonumber(l.DetailH) or 260 ) else EventPanelClient.HideIvnPreview(false) EventPanelClient.RenderTabs(x, y) EventPanelClient.RenderList( x + (tonumber(l.ListX) or 16), y + (tonumber(l.ListY) or 70), tonumber(l.ListW) or (w - 32), tonumber(l.ListH) or 260 ) end EventPanelClient.RenderCountryButtons(x, y, w, h) end function EventPanelClient.Render() if CONFIG.Active ~= true then return end EventPanelClient.ReleaseClick() if EventPanelClient.Opened == true then EventPanelClient.SetCharacterLock(true) end EventPanelClient.SetPanelInterfaceLock(EventPanelClient.Opened == true) EventPanelClient.HardBlockPanelInput() EventPanelClient.Frame = (EventPanelClient.Frame or 0) + 1 local ok, err = pcall(function() if EventPanelClient.Opened ~= true then EventPanelClient.HideIvnPreview(false) EventPanelClient.RenderMiniButton() return end EventPanelClient.RenderWindow() end) if ok ~= true and type(Console) == "function" then Console(2, "[EventPanel] Render error: " .. tostring(err)) end end function EventPanelClient.ProcessClicks() if CONFIG.Active ~= true then return false end EventPanelClient.ReleaseClick() if EventPanelClient.Opened == true then local wx, wy = EventPanelClient.GetWindowPosition() local wcfg = CONFIG.Window or {} local l = CONFIG.Layout or {} local ww = tonumber(wcfg.W) or 330 local wh = tonumber(wcfg.H) or 360 wx, wy = EventPanelClient.UpdateDrag(wx, wy, ww, wh) if EventPanelClient.DetailOpened[EventPanelClient.GetViewKey()] ~= true then local listX = wx + (tonumber(l.ListX) or 16) local listY = wy + (tonumber(l.ListY) or 70) local listW = tonumber(l.ListW) or (ww - 32) local listH = tonumber(l.ListH) or 260 if EventPanelClient.ProcessWheelOnList(listX, listY, listW, listH) == true then return true end end if EventPanelClient.MouseIn(wx, wy, ww, wh) then EventPanelClient.BlockClick() return true end if CONFIG.BlockAllMapClickWhenOpen == true then EventPanelClient.BlockClick() return true end end if CONFIG.ShowMiniButton == true then local bx, by, bw, bh = EventPanelClient.GetMiniButtonRect() if EventPanelClient.MouseIn(bx, by, bw, bh) then EventPanelClient.BlockClick() return true end end return false end function EventPanelClient.HandleOpenKey(key) if CONFIG.Active ~= true then return false end local openKey = tonumber(CONFIG.OpenKey) or 0 local openKeyAlt = tonumber(CONFIG.OpenKeyAlt) or 0 if key ~= openKey and (openKeyAlt <= 0 or key ~= openKeyAlt) then return false end if EventPanelClient.OpenKeyDown == true then return true end if EventPanelClient.OpenKeyLockFrame == EventPanelClient.Frame then return true end if CONFIG.BlockOpenWhenChat ~= false and EventPanelClient.IsChatOpen() == true then return false end if EventPanelClient.IsBlockedOpenWindow() == true then return false end EventPanelClient.OpenKeyDown = true EventPanelClient.OpenKeyLockFrame = EventPanelClient.Frame EventPanelClient.StartNativeWindowBlock() EventPanelClient.Toggle() return true end function EventPanelClient.KeyboardEvent(key) if CONFIG.Active ~= true then return false end if EventPanelClient.Opened == true and key == (CONFIG.CloseKey or 27) then EventPanelClient.Close() return true end if CONFIG.OpenWithKey == true and EventPanelClient.HandleOpenKey(key) == true then return true end return false end function EventPanelClient.UpdateKeyEvent() if CONFIG.Active ~= true then return false end if CONFIG.OpenWithKey ~= true or CONFIG.UpdateKeyHookForH ~= true then return false end local openKey = tonumber(CONFIG.OpenKey) or 0 if openKey <= 0 then return false end local pressed = false if type(CheckPressedKey) == "function" and CheckPressedKey(openKey) == 1 then pressed = true end if pressed == true and EventPanelClient.OpenKeyDown ~= true then return EventPanelClient.HandleOpenKey(openKey) end local released = false if type(CheckReleasedKey) == "function" and CheckReleasedKey(openKey) == 1 then released = true end if released == true then EventPanelClient.OpenKeyDown = false end return false end function EventPanelClient.MainProc() if tonumber(EventPanelClient.NativeBlockFrames or 0) > 0 then EventPanelClient.CloseNativeMUWindows() EventPanelClient.NativeBlockFrames = (tonumber(EventPanelClient.NativeBlockFrames) or 0) - 1 end EventPanelClient.Frame = (tonumber(EventPanelClient.Frame) or 0) + 1 EventPanelClient.DetectCharacterEntry() if EventPanelClient.Opened == true then EventPanelClient.SetCharacterLock(true) else EventPanelClient.SetCharacterLock(false) end EventPanelClient.SetPanelInterfaceLock(EventPanelClient.Opened == true) EventPanelClient.HardBlockPanelInput() if CONFIG.MainProcKeyFallbackForH == true and CONFIG.OpenWithKey == true then local openKey = tonumber(CONFIG.OpenKey) or 0 if openKey > 0 and type(CheckPressedKey) == "function" then local okKey, retKey = pcall(CheckPressedKey, openKey) if okKey == true and tonumber(retKey) == 1 and EventPanelClient.OpenKeyDown ~= true then EventPanelClient.HandleOpenKey(openKey) end end if type(CheckReleasedKey) == "function" then local okRel, retRel = pcall(CheckReleasedKey, openKey) if okRel == true and tonumber(retRel) == 1 then EventPanelClient.OpenKeyDown = false end end end if EventPanelClient.Opened == true then local listX, listY, listW, listH = EventPanelClient.GetListRect() EventPanelClient.ProcessWheelOnList(listX, listY, listW, listH) end end local function EventPanelClient_SafeCall(label, fn, ...) if type(fn) ~= "function" then return false end local ok, ret = pcall(fn, ...) if ok ~= true then if type(Console) == "function" then Console(2, "[EventPanel] " .. tostring(label) .. " error: " .. tostring(ret)) end return false end return ret end function EventPanelClient_MainLoader() return EventPanelClient_SafeCall("MainLoader", EventPanelClient.LoadImages, "main-loader") end function EventPanelClient_OnShutScript() return EventPanelClient_SafeCall("OnShutScript", function() EventPanelClient.SetPanelInterfaceLock(false) EventPanelClient.SetCharacterLock(false) return true end) end function EventPanelClient_MainProcThread() EventPanelClient_SafeCall("TextureReload", EventPanelClient.ProcessTextureReload) return EventPanelClient_SafeCall("MainProc", EventPanelClient.MainProc) end function EventPanelClient_MainInterfaceProcThread() return EventPanelClient_SafeCall("Render", EventPanelClient.Render) end function EventPanelClient_UpdateKeyEvent() return EventPanelClient_SafeCall("UpdateKeyEvent", EventPanelClient.UpdateKeyEvent) end function EventPanelClient_EventTimeMenuClick(index) local result = EventPanelClient_SafeCall("EventTimeMenuClick", function() if CONFIG.OverrideNativeEventTimeClick ~= true then return false end if CONFIG.BlockOpenWhenChat ~= false and EventPanelClient.IsChatOpen() == true then return false end EventPanelClient.StartNativeWindowBlock() EventPanelClient.Toggle() return true end) return (result == true or result == 1) and 1 or 0 end function EventPanelClient_LeftClickEvent() local result = EventPanelClient_SafeCall("LeftClickEvent", function() if CONFIG.Active ~= true then return 0 end if EventPanelClient.Opened == true then local x, y, w, h = EventPanelClient.GetWindowRect() if (CONFIG.Window or {}).Drag == true and EventPanelClient.IsOverDragArea(x, y, w, h) == true then EventPanelClient.BeginDragCandidate(x, y, w, h) end EventPanelClient.BlockClick() return 1 end if EventPanelClient.IsMouseOverAnyPanelArea() == true then EventPanelClient.BlockClick() return 1 end return 0 end) return (result == true or result == 1) and 1 or 0 end function EventPanelClient_UpdateMouseEvent(...) local args = { ... } local result = EventPanelClient_SafeCall("UpdateMouseEvent", function() if EventPanelClient.ProcessWheelArgs(unpack(args)) == true then return 1 end if EventPanelClient.ProcessClicks() == true then return 1 end return 0 end) return (result == true or result == 1) and 1 or 0 end function EventPanelClient_ScrollMouseEvent(scrollData) local result = EventPanelClient_SafeCall("ScrollMouseEvent", function() local delta = EventPanelClient.NormalizeScrollDelta(scrollData) if delta ~= 0 then if EventPanelClient.HandleWheelDelta(delta, false) == true then return 1 end end return 0 end) return (result == true or result == 1) and 1 or 0 end function EventPanelClient_MouseWheelEvent(...) if EventPanelClient.ProcessWheelArgs(...) == true then return 1 end local listX, listY, listW, listH = EventPanelClient.GetListRect() if EventPanelClient.ProcessWheelOnList(listX, listY, listW, listH) == true then return 1 end return 0 end function EventPanelClient_OnMouseWheel(...) if EventPanelClient.ProcessWheelArgs(...) == true then return 1 end local listX, listY, listW, listH = EventPanelClient.GetListRect() if EventPanelClient.ProcessWheelOnList(listX, listY, listW, listH) == true then return 1 end return 0 end function EventPanelClient_MouseScrollEvent(...) if EventPanelClient.ProcessWheelArgs(...) == true then return 1 end local listX, listY, listW, listH = EventPanelClient.GetListRect() if EventPanelClient.ProcessWheelOnList(listX, listY, listW, listH) == true then return 1 end return 0 end function EventPanelClient_UpdateMouseWheel(...) if EventPanelClient.ProcessWheelArgs(...) == true then return 1 end local listX, listY, listW, listH = EventPanelClient.GetListRect() if EventPanelClient.ProcessWheelOnList(listX, listY, listW, listH) == true then return 1 end return 0 end function EventPanelClient_KeyboardEvent(key) local result = EventPanelClient_SafeCall("KeyboardEvent", EventPanelClient.KeyboardEvent, key) return (result == true or result == 1) and 1 or 0 end function EventPanelClient.ReadBytePacketSafe(packetName, pos) if type(GetBytePacket) ~= "function" then return 0 end local ok, value = pcall(GetBytePacket, packetName, pos) if ok == true then return tonumber(value) or 0 end return 0 end function EventPanelClient.ReadWordPacketSafe(packetName, pos) if type(GetWordPacket) ~= "function" then return 0 end local ok, value = pcall(GetWordPacket, packetName, pos) if ok == true then return tonumber(value) or 0 end return 0 end function EventPanelClient.ReadStringPacketSafe(packetName) local len = EventPanelClient.ReadBytePacketSafe(packetName, -1) if len <= 0 or type(GetCharPacketLength) ~= "function" then return "" end local ok, value = pcall(GetCharPacketLength, packetName, -1, len) if ok == true then return tostring(value or "") end return "" end function EventPanelClient.RequestServerConfig() if type(CreatePacket) ~= "function" or type(SendPacket) ~= "function" then return end local packetName = tostring(CONFIG.PacketConfigRequest or "EPQ") local ok = pcall(function() CreatePacket(packetName, CONFIG.Packet) SendPacket(packetName) ClearPacket(packetName) end) if ok ~= true then pcall(ClearPacket, packetName) end end function EventPanelClient.DecodeServerEntry(packetName) local category = EventPanelClient.ReadBytePacketSafe(packetName, -1) local order = EventPanelClient.ReadBytePacketSafe(packetName, -1) local ev = { Index = EventPanelClient.ReadWordPacketSafe(packetName, -1), BossIndex = EventPanelClient.ReadWordPacketSafe(packetName, -1), } ev.VisualIndex = ev.BossIndex ev.BossMap = EventPanelClient.ReadWordPacketSafe(packetName, -1) - 1 ev.DurationMinutes = EventPanelClient.ReadWordPacketSafe(packetName, -1) ev.MobTotal = EventPanelClient.ReadWordPacketSafe(packetName, -1) local flags = EventPanelClient.ReadBytePacketSafe(packetName, -1) ev.NoMonster = (flags % 2) >= 1 ev.ZoneTracked = (math.floor(flags / 2) % 2) >= 1 ev.NoSchedule = (math.floor(flags / 4) % 2) >= 1 ev.RewardEnabled = (math.floor(flags / 8) % 2) >= 1 ev.ForceDirectRender = (math.floor(flags / 16) % 2) >= 1 ev.PreviewX = EventPanelClient.ReadWordPacketSafe(packetName, -1) - 32768 ev.PreviewY = EventPanelClient.ReadWordPacketSafe(packetName, -1) - 32768 ev.PreviewScale = EventPanelClient.ReadWordPacketSafe(packetName, -1) / 1000 local requestedBoxes = EventPanelClient.ReadBytePacketSafe(packetName, -1) local itemScale = EventPanelClient.ReadWordPacketSafe(packetName, -1) / 1000 local dropBoxX = EventPanelClient.ReadWordPacketSafe(packetName, -1) - 32768 local dropBoxY = EventPanelClient.ReadWordPacketSafe(packetName, -1) - 32768 local dropBoxW = EventPanelClient.ReadWordPacketSafe(packetName, -1) local dropBoxH = EventPanelClient.ReadWordPacketSafe(packetName, -1) local dropBoxGap = EventPanelClient.ReadWordPacketSafe(packetName, -1) local dropItemX = EventPanelClient.ReadWordPacketSafe(packetName, -1) - 32768 local dropItemY = EventPanelClient.ReadWordPacketSafe(packetName, -1) - 32768 local dropItemW = EventPanelClient.ReadWordPacketSafe(packetName, -1) local dropItemH = EventPanelClient.ReadWordPacketSafe(packetName, -1) ev.Name = EventPanelClient.ReadStringPacketSafe(packetName) ev.Type = EventPanelClient.ReadStringPacketSafe(packetName) ev.Status = EventPanelClient.ReadStringPacketSafe(packetName) ev.Time1 = EventPanelClient.ReadStringPacketSafe(packetName) ev.Time2 = EventPanelClient.ReadStringPacketSafe(packetName) ev.Time3 = EventPanelClient.ReadStringPacketSafe(packetName) ev.Map = EventPanelClient.ReadStringPacketSafe(packetName) ev.Coords = EventPanelClient.ReadStringPacketSafe(packetName) ev.Drop = EventPanelClient.ReadStringPacketSafe(packetName) ev.RewardText = EventPanelClient.ReadStringPacketSafe(packetName) ev.RespawnText = EventPanelClient.ReadStringPacketSafe(packetName) ev.OnlineText = EventPanelClient.ReadStringPacketSafe(packetName) ev.OfflineText = EventPanelClient.ReadStringPacketSafe(packetName) local scheduleText = EventPanelClient.ReadStringPacketSafe(packetName) ev.Schedule = {} for clock in string.gmatch(scheduleText, "[^|]+") do ev.Schedule[#ev.Schedule + 1] = clock end local itemCount = EventPanelClient.ReadBytePacketSafe(packetName, -1) ev.DropItems = {} for i = 1, itemCount do local item = { Type = EventPanelClient.ReadBytePacketSafe(packetName, -1), Index = EventPanelClient.ReadWordPacketSafe(packetName, -1), Level = EventPanelClient.ReadBytePacketSafe(packetName, -1), Durability = EventPanelClient.ReadBytePacketSafe(packetName, -1), Skill = EventPanelClient.ReadBytePacketSafe(packetName, -1), Luck = EventPanelClient.ReadBytePacketSafe(packetName, -1), Option = EventPanelClient.ReadBytePacketSafe(packetName, -1), Excellent = EventPanelClient.ReadBytePacketSafe(packetName, -1), Ancient = EventPanelClient.ReadBytePacketSafe(packetName, -1), JOH = EventPanelClient.ReadBytePacketSafe(packetName, -1), Socket = EventPanelClient.ReadBytePacketSafe(packetName, -1), } for socket = 1, 5 do item["Socket" .. socket] = EventPanelClient.ReadBytePacketSafe(packetName, -1) end ev.DropItems[#ev.DropItems + 1] = item end ev.DropLayout = { DropBoxMax = math.max(tonumber(requestedBoxes) or 0, #ev.DropItems), DropItemScale = itemScale > 0 and itemScale or 0.64, DropBoxX = dropBoxX, DropBoxY = dropBoxY, DropBoxW = dropBoxW, DropBoxH = dropBoxH, DropBoxGap = dropBoxGap, DropItemX = dropItemX, DropItemY = dropItemY, DropItemW = dropItemW, DropItemH = dropItemH, } local visualKey = tostring(category) .. ":" .. tostring(math.floor(tonumber(ev.Index) or -1)) local originalVisual = EventPanelClient.OriginalMonsterVisual[visualKey] if type(originalVisual) == "table" then if originalVisual.VisualIndex ~= nil then ev.VisualIndex = originalVisual.VisualIndex end ev.PreviewX = originalVisual.PreviewX ev.PreviewY = originalVisual.PreviewY ev.PreviewScale = originalVisual.PreviewScale ev.ForceDirectRender = originalVisual.ForceDirectRender end local keys = { [1] = "Events", [2] = "Invasions", [3] = "ZoneEvents" } local key = keys[category] if key ~= nil and order > 0 then EventPanelClient.ServerConfig[key][order] = ev end end function EventPanelClient.ClearPacketSafe(packetName) if type(ClearPacket) == "function" then pcall(ClearPacket, packetName) end end function EventPanelClient.Protocol(head, packetName) if head ~= CONFIG.Packet then return false end if packetName == tostring(CONFIG.PacketConfigReset or "EPR") then local version = EventPanelClient.ReadBytePacketSafe(packetName, -1) EventPanelClient.ClearPacketSafe(packetName) if version == (tonumber(CONFIG.ConfigProtocolVersion) or 1) then EventPanelClient.ServerConfig = { Events = {}, Invasions = {}, ZoneEvents = {} } end return true end if packetName == tostring(CONFIG.PacketConfigEntry or "EPE") then EventPanelClient.DecodeServerEntry(packetName) EventPanelClient.ClearPacketSafe(packetName) return true end if packetName == tostring(CONFIG.PacketConfigEnd or "EPD") then EventPanelClient.ReadBytePacketSafe(packetName, -1) EventPanelClient.ClearPacketSafe(packetName) CONFIG.NormalEvents = EventPanelClient.ServerConfig.Events CONFIG.Invasions = EventPanelClient.ServerConfig.Invasions CONFIG.Events = CONFIG.Invasions CONFIG.ZoneEvents = EventPanelClient.ServerConfig.ZoneEvents EventPanelClient.ConfigLoaded = true EventPanelClient.Selected = { Events = 1, Invasions = 1, ZoneEvents = 1 } EventPanelClient.Scroll = { Events = 1, Invasions = 1, ZoneEvents = 1 } return true end if packetName == tostring(CONFIG.PacketLive or "LIV") then local eventIndex = EventPanelClient.ReadBytePacketSafe(packetName, -1) local active = EventPanelClient.ReadBytePacketSafe(packetName, -1) EventPanelClient.ClearPacketSafe(packetName) EventPanelClient.SetLive(eventIndex, tonumber(active) == 1) return true end if packetName == tostring(CONFIG.PacketCount or "CNT") then local eventIndex = EventPanelClient.ReadBytePacketSafe(packetName, -1) local liveCount = EventPanelClient.ReadBytePacketSafe(packetName, -1) local totalCount = EventPanelClient.ReadBytePacketSafe(packetName, -1) EventPanelClient.ClearPacketSafe(packetName) EventPanelClient.SetLiveCount(eventIndex, liveCount, totalCount) return true end if packetName == tostring(CONFIG.PacketKill or "KIL") then local eventIndex = EventPanelClient.ReadBytePacketSafe(packetName, -1) EventPanelClient.ClearPacketSafe(packetName) EventPanelClient.MarkKilled(eventIndex) return true end return false end function EventPanelClient_ClientProtocol(head, packetName) return EventPanelClient_SafeCall("ClientProtocol", EventPanelClient.Protocol, head, packetName) end function EventPanelClient.Init() if CONFIG.Active ~= true then return end if type(BridgeFunctionAttach) == "function" then BridgeFunctionAttach("MainLoader", "EventPanelClient_MainLoader") BridgeFunctionAttach("OnShutScript", "EventPanelClient_OnShutScript") BridgeFunctionAttach("MainProcThread", "EventPanelClient_MainProcThread") BridgeFunctionAttach("MainInterfaceProcThread", "EventPanelClient_MainInterfaceProcThread") BridgeFunctionAttach("UpdateMouseEvent", "EventPanelClient_UpdateMouseEvent") BridgeFunctionAttach("LeftClickEvent", "EventPanelClient_LeftClickEvent") BridgeFunctionAttach("KeyboardEvent", "EventPanelClient_KeyboardEvent") BridgeFunctionAttach("UpdateKeyEvent", "EventPanelClient_UpdateKeyEvent") BridgeFunctionAttach("EventTimeMenuClick", "EventPanelClient_EventTimeMenuClick") BridgeFunctionAttach("ScrollMouseEvent", "EventPanelClient_ScrollMouseEvent") end if ProtocolFunctions ~= nil and ProtocolFunctions.ClientProtocol ~= nil then ProtocolFunctions.ClientProtocol(EventPanelClient_ClientProtocol) elseif type(BridgeFunctionAttach) == "function" then BridgeFunctionAttach("ClientProtocol", "EventPanelClient_ClientProtocol") end EventPanelClient.Console("Cliente cargado") end EventPanelClient.Init() -- Las imagenes premium se cargan desde EventPanelClient.LoadImages. -- Estado integrado y autoritativo: nombres, configuracion y ejecucion llegan del GameServer. -- Ningun horario local puede declarar un evento ONLINE. local CONFIG = require("Scripts\\EventPanel\\Config") if type(EventPanelClient) ~= "table" then return end CONFIG.PacketRuntime = CONFIG.PacketRuntime or "RUN" CONFIG.PacketImageRecovery = CONFIG.PacketImageRecovery or "IMG" CONFIG.PacketZone = CONFIG.PacketZone or "ZON" EventPanelClient.RuntimeState = EventPanelClient.RuntimeState or {} EventPanelClient.LiveState = EventPanelClient.LiveState or {} EventPanelClient.ZoneState = EventPanelClient.ZoneState or {} local OriginalSetLive = EventPanelClient.SetLive local OriginalMarkKilled = EventPanelClient.MarkKilled local OriginalGetTimerInfo = EventPanelClient.GetTimerInfo local OriginalProtocol = EventPanelClient.Protocol local function elapsedSeconds(startSeconds, nowSeconds) local elapsed = (tonumber(nowSeconds) or 0) - (tonumber(startSeconds) or 0) if elapsed < 0 then elapsed = elapsed + 86400 end return elapsed end -- El reloj original considera "Online" toda la ventana teorica posterior a un -- horario. Si el GameServer no confirma RUN, esa ejecucion no existe y debemos -- saltar al siguiente inicio configurado en EventManager, no quedar mostrando -- "Esperando GS" durante 30 minutos. Este calculo solo informa el proximo -- horario; nunca enciende el estado verde. local function getNextScheduleSeconds(ev) if type(ev) ~= "table" then return nil end local schedules = {} local seen = {} local function addFromText(value) local text = tostring(value or "") for hourText, minuteText in string.gmatch(text, "(%d%d):(%d%d)") do local hour = tonumber(hourText) local minute = tonumber(minuteText) if hour ~= nil and minute ~= nil and hour >= 0 and hour <= 23 and minute >= 0 and minute <= 59 then local at = (hour * 3600) + (minute * 60) if seen[at] ~= true then seen[at] = true schedules[#schedules + 1] = at end end end end if type(ev.Schedule) == "table" then for _, value in pairs(ev.Schedule) do addFromText(value) end end addFromText(ev.Time1) addFromText(ev.Time2) addFromText(ev.Time3) local now = EventPanelClient.NowSeconds() local best = nil for _, at in pairs(schedules) do local remaining = at - now if remaining <= 0 then remaining = remaining + 86400 end if best == nil or remaining < best then best = remaining end end return best and math.floor(best) or nil end function EventPanelClient.ReadDwordPacketSafe(packetName, pos) if type(GetDwordPacket) ~= "function" then return 0 end local ok, value = pcall(GetDwordPacket, packetName, pos) if ok == true then return tonumber(value) or 0 end return 0 end function EventPanelClient.ReloadImagesAfterEntry() -- El paquete IMG llega al entrar al personaje. La recarga se difiere para -- que el selector termine de destruir la escena anterior y su gestor de -- texturas. Solo se vuelven a registrar las cuatro texturas de EventPanel. EventPanelClient.QueueTextureReload("server-entry") return true end function EventPanelClient.SetRuntime(eventIndex, active, remaining, source) eventIndex = tonumber(eventIndex) or -1 if eventIndex < 0 then return end remaining = math.max(0, math.floor(tonumber(remaining) or 0)) if active ~= true or remaining <= 0 then EventPanelClient.RuntimeState[eventIndex] = nil EventPanelClient.LiveState[eventIndex] = false EventPanelClient.ForceOnline[eventIndex] = nil return end EventPanelClient.LiveState[eventIndex] = true EventPanelClient.RuntimeState[eventIndex] = { Active = true, Remaining = remaining, SyncAt = EventPanelClient.NowSeconds(), Source = tonumber(source) or 0, } EventPanelClient.ForceOnline[eventIndex] = true EventPanelClient.KilledState[eventIndex] = nil EventPanelClient.CompleteState[eventIndex] = nil EventPanelClient.WasOnline[eventIndex] = true end function EventPanelClient.SetZoneRuntime(zoneIndex, alive, remaining) zoneIndex = tonumber(zoneIndex) or -1 if zoneIndex < 0 then return end EventPanelClient.ZoneState[zoneIndex] = { Alive = alive == true, Remaining = math.max(0, math.floor(tonumber(remaining) or 0)), SyncAt = os.time(), } end function EventPanelClient.GetZoneTimerInfo(ev) local state = EventPanelClient.ZoneState[tonumber(ev.Index) or -1] local alive = type(state) == "table" and state.Alive == true local remaining = 0 if type(state) == "table" and alive ~= true then remaining = math.max(0, math.floor((tonumber(state.Remaining) or 0) - math.max(0, os.time() - (tonumber(state.SyncAt) or os.time())))) end local status = alive and "ONLINE" or "OFFLINE" local detail = alive and tostring(ev.OnlineText or ("ONLINE " .. tostring(ev.Map or ""))) or tostring(ev.OfflineText or ("OFFLINE " .. tostring(ev.Map or ""))) if alive ~= true and remaining > 0 then status = "OFF " .. EventPanelClient.FormatTime(remaining) detail = detail .. " - " .. EventPanelClient.FormatTime(remaining) end return { Status = status, DetailStatus = detail, Online = alive, Seconds = remaining, NextText = EventPanelClient.FormatTime(remaining), Killed = not alive, ZoneTracked = true, ServerRuntime = true, ActiveCountdown = remaining > 0, LiveDetected = alive, } end function EventPanelClient.GetRuntimeRemaining(eventIndex) eventIndex = tonumber(eventIndex) or -1 local runtime = EventPanelClient.RuntimeState[eventIndex] if type(runtime) ~= "table" or runtime.Active ~= true then return 0 end local remaining = (tonumber(runtime.Remaining) or 0) - elapsedSeconds(runtime.SyncAt, EventPanelClient.NowSeconds()) if remaining <= 0 then EventPanelClient.RuntimeState[eventIndex] = nil EventPanelClient.LiveState[eventIndex] = false EventPanelClient.ForceOnline[eventIndex] = nil return 0 end return math.floor(remaining) end function EventPanelClient.SetLive(eventIndex, active) eventIndex = tonumber(eventIndex) or -1 if eventIndex < 0 then return end -- LIV es una confirmacion del GameServer. No se calcula estado en el cliente. EventPanelClient.LiveState[eventIndex] = active == true if active == true then EventPanelClient.ForceOnline[eventIndex] = true EventPanelClient.WasOnline[eventIndex] = true EventPanelClient.KilledState[eventIndex] = nil EventPanelClient.CompleteState[eventIndex] = nil return end EventPanelClient.RuntimeState[eventIndex] = nil EventPanelClient.ForceOnline[eventIndex] = nil if type(OriginalSetLive) == "function" then OriginalSetLive(eventIndex, false) end end function EventPanelClient.SetLiveCount(eventIndex, liveCount, totalCount) eventIndex = tonumber(eventIndex) or -1 if eventIndex < 0 then return end liveCount = math.max(0, math.floor(tonumber(liveCount) or 0)) totalCount = math.max(0, math.floor(tonumber(totalCount) or 0)) EventPanelClient.LiveCount[eventIndex] = { Live = liveCount, Total = totalCount } EventPanelClient.LiveState[eventIndex] = liveCount > 0 if liveCount > 0 then EventPanelClient.KilledState[eventIndex] = nil EventPanelClient.CompleteState[eventIndex] = nil elseif type(EventPanelClient.RuntimeState[eventIndex]) == "table" then -- Respaldo ante paquetes retrasados: CNT=0 cierra RUN sin importar si -- nacio por SCHEDULE o force. El siguiente tiempo sale de Evento.lua. EventPanelClient.RuntimeState[eventIndex] = nil EventPanelClient.ForceOnline[eventIndex] = nil end end function EventPanelClient.MarkKilled(eventIndex) eventIndex = tonumber(eventIndex) or -1 EventPanelClient.RuntimeState[eventIndex] = nil EventPanelClient.LiveState[eventIndex] = false EventPanelClient.ForceOnline[eventIndex] = nil if type(OriginalMarkKilled) == "function" then OriginalMarkKilled(eventIndex) end end function EventPanelClient.GetTimerInfo(ev) if type(ev) == "table" and ev.ZoneTracked == true then return EventPanelClient.GetZoneTimerInfo(ev) end local base if type(OriginalGetTimerInfo) == "function" then base = OriginalGetTimerInfo(ev) else base = { Status = "--:--", Online = false, Seconds = 0, NextText = "--:--" } end if ev == nil then return base end local eventIndex = tonumber(ev.Index) or -1 local remaining = EventPanelClient.GetRuntimeRemaining(eventIndex) local countData = EventPanelClient.LiveCount[eventIndex] local liveCount = countData and (tonumber(countData.Live) or 0) or 0 if remaining > 0 then base.Status = EventPanelClient.FormatTime(remaining) base.Online = true base.ActiveCountdown = true base.Seconds = remaining base.Killed = false base.LiveDetected = liveCount > 0 base.ServerRuntime = true return base end -- LIV y CNT tambien son datos expresos del GameServer. Si alguno confirma -- monstruos activos se muestra ONLINE, pero no se inventa una duracion. local serverLive = EventPanelClient.LiveState[eventIndex] == true or liveCount > 0 if serverLive == true then local serverText = tostring(ev.OnlineText or "") if serverText == "" then serverText = "ONLINE" end base.Status = serverText base.Online = true base.ActiveCountdown = false base.Seconds = 0 base.Killed = false base.LiveDetected = true base.ServerRuntime = true return base end -- El horario recibido del GameServer solo informa la proxima ejecucion; -- nunca cambia el estado a ONLINE por su cuenta. local nextSeconds = getNextScheduleSeconds(ev) if nextSeconds ~= nil and nextSeconds > 0 then base.Status = EventPanelClient.FormatTime(nextSeconds) base.Seconds = nextSeconds base.NextText = base.Status else base.Status = tostring(ev.Status or CONFIG.DefaultStatus or "") base.Seconds = 0 end base.Online = false base.ActiveCountdown = false base.Killed = false base.LiveDetected = false base.ServerRuntime = false return base end function EventPanelClient.Protocol(head, packetName) if head == CONFIG.Packet and packetName == tostring(CONFIG.PacketZone or "ZON") then local zoneIndex = EventPanelClient.ReadBytePacketSafe(packetName, -1) local alive = EventPanelClient.ReadBytePacketSafe(packetName, -1) local remaining = EventPanelClient.ReadDwordPacketSafe(packetName, -1) EventPanelClient.ClearPacketSafe(packetName) EventPanelClient.SetZoneRuntime(zoneIndex, tonumber(alive) == 1, remaining) return true end if head == CONFIG.Packet and packetName == tostring(CONFIG.PacketImageRecovery or "IMG") then EventPanelClient.ZoneState = {} EventPanelClient.RuntimeState = {} EventPanelClient.LiveState = {} EventPanelClient.LiveCount = {} EventPanelClient.ForceOnline = {} EventPanelClient.ClearPacketSafe(packetName) EventPanelClient.ReloadImagesAfterEntry() return true end if head == CONFIG.Packet and packetName == tostring(CONFIG.PacketRuntime or "RUN") then local eventIndex = EventPanelClient.ReadBytePacketSafe(packetName, -1) local active = EventPanelClient.ReadBytePacketSafe(packetName, -1) local remaining = EventPanelClient.ReadDwordPacketSafe(packetName, -1) local source = EventPanelClient.ReadBytePacketSafe(packetName, -1) EventPanelClient.ClearPacketSafe(packetName) EventPanelClient.SetRuntime(eventIndex, tonumber(active) == 1, remaining, source) return true end if type(OriginalProtocol) == "function" then return OriginalProtocol(head, packetName) end return false end if type(EventPanelClient.Console) == "function" then EventPanelClient.Console("EventPanel general cargado: estado exclusivo del GameServer") end