## Interface: 120001
## Title: SimpleD20
## Notes: A clickable D20 that animates a roll.
## Author: Zenithas
## Version: 1.0
SimpleD20.lua
-- 1. Create the Main Frame
local d20 = CreateFrame("Button", "SimpleD20Frame", UIParent, "BackdropTemplate")
d20:SetSize(64, 64)
d20:SetPoint("CENTER", UIParent, "CENTER", 0, 0)
d20:SetMovable(true)
d20:EnableMouse(true)
d20:RegisterForDrag("RightButton")
d20:SetScript("OnDragStart", d20.StartMoving)
d20:SetScript("OnDragStop", d20.StopMovingOrSizing)
-- 2. Visuals
local texture = d20:CreateTexture(nil, "BACKGROUND")
texture:SetTexture("Interface\\Icons\\INV_Misc_Dice_01")
texture:SetAllPoints(d20)
d20.texture = texture
-- Changed to GameFontNormalLarge so two numbers stack nicely inside the frame
d20.text = d20:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
d20.text:SetPoint("CENTER", 0, 0)
d20.text:SetText("")
d20.text:SetTextColor(1, 1, 1, 1)
-- NEW: The Toggle Button
local expectedRolls = 1 -- How many dice are we rolling?
local toggleBtn = CreateFrame("Button", nil, d20, "UIPanelButtonTemplate")
toggleBtn:SetSize(24, 24)
toggleBtn:SetPoint("LEFT", d20, "RIGHT", 2, 0) -- Attach to the right side
toggleBtn:SetText("1x")
toggleBtn:SetScript("OnClick", function()
if expectedRolls == 1 then
expectedRolls = 2
toggleBtn:SetText("2x")
else
expectedRolls = 1
toggleBtn:SetText("1x")
end
PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON)
end)
-- 3. Logic & Cooldown Setup
local isRolling = false
local rollTimer = nil
local timeOutTimer = nil
local rollResults = {} -- Table to store our incoming rolls
-- Cooldown variables
local COOLDOWN_SECONDS = 2.0
local lastRollTime = 0
-- Helper function to color individual text strings
local function GetColorCode(res)
if res == "?" then return "|cFFFFFFFF" end -- White for errors
local num = tonumber(res)
if num == 20 then
return "|cFF00FF00" -- Green
elseif num == 1 then
return "|cFFFF0000" -- Red
else
return "|cFFFFD100" -- Yellow
end
end
-- Function to handle the final result
local function FinishRoll()
-- Stop the flicker timers
if rollTimer then rollTimer:Cancel() end
if timeOutTimer then timeOutTimer:Cancel() end
-- Unregister the listener
d20:UnregisterEvent("CHAT_MSG_SYSTEM")
local finalString = ""
local playVictory = false
local playDefeat = false
-- Loop through our results and build a stacked string with colors
for i, res in ipairs(rollResults) do
local num = tonumber(res)
if num == 20 then playVictory = true end
if num == 1 then playDefeat = true end
-- Append the colored number
finalString = finalString .. GetColorCode(res) .. res .. "|r"
-- Add a line break if this isn't the last number
if i < #rollResults then
finalString = finalString .. "\n"
end
end
-- Sound Effects based on best/worst outcomes
if playVictory then
PlaySound(34089) -- Victory
elseif playDefeat then
PlaySound(34092) -- Defeat
end
-- Display the final stacked string
d20.text:SetText(finalString)
isRolling = false
end
-- 4. Event Handler (Listening for the Server)
d20:SetScript("OnEvent", function(self, event, msg)
if event == "CHAT_MSG_SYSTEM" and isRolling then
local myName = UnitName("player")
if string.find(msg, myName) and string.find(msg, "%(1%-20%)") then
local rollResult = string.match(msg, "rolls (%d+) %(")
if rollResult then
-- Add the caught roll to our table
table.insert(rollResults, rollResult)
-- Check if we have caught all the rolls we asked for
if #rollResults >= expectedRolls then
FinishRoll()
end
end
end
end
end)
-- 5. Click Handler
d20:SetScript("OnClick", function(self, button)
if isRolling or button == "RightButton" then return end
local currentTime = GetTime()
if (currentTime - lastRollTime) < COOLDOWN_SECONDS then
local timeRemaining = math.ceil(COOLDOWN_SECONDS - (currentTime - lastRollTime))
print("|cff00ccff[SimpleD20]|r You must wait " .. timeRemaining .. " more seconds before rolling again.")
return
end
lastRollTime = currentTime
isRolling = true
rollResults = {} -- Clear old results
PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON)
d20:RegisterEvent("CHAT_MSG_SYSTEM")
-- Fire off the rolls based on the toggle state
for i = 1, expectedRolls do
RandomRoll(1, 20)
end
-- Start Visual Flicker (Wait for server)
rollTimer = C_Timer.NewTicker(0.05, function()
if expectedRolls == 1 then
d20.text:SetText("|cFFFFFFFF" .. math.random(1, 20) .. "|r")
else
d20.text:SetText("|cFFFFFFFF" .. math.random(1, 20) .. "\n" .. math.random(1, 20) .. "|r")
end
end)
-- Safety Timeout
timeOutTimer = C_Timer.NewTimer(3.0, function()
if isRolling then
-- Fill in any missing responses with a "?"
while #rollResults < expectedRolls do
table.insert(rollResults, "?")
end
FinishRoll()
print("|cff00ccff[SimpleD20]|r Error: Server took too long to respond.")
end
end)
end)
-- 6. Tooltip
d20:SetScript("OnEnter", function(self)
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
GameTooltip:AddLine("Official D20")
GameTooltip:AddLine("Left Click: Roll Dice", 1, 1, 1)
GameTooltip:AddLine("Right Click: Drag", 1, 1, 1)
GameTooltip:Show()
end)
d20:SetScript("OnLeave", function(self)
GameTooltip:Hide()
end)
-- 7. Minimap Button
local minimapBtn = CreateFrame("Button", "SimpleD20MinimapButton", Minimap)
minimapBtn:SetSize(32, 32)
minimapBtn:SetFrameStrata("MEDIUM")
minimapBtn:SetFrameLevel(8)
minimapBtn:SetMovable(true)
minimapBtn:RegisterForDrag("RightButton")
minimapBtn:RegisterForClicks("LeftButtonUp")
local minimapIcon = minimapBtn:CreateTexture(nil, "BACKGROUND")
minimapIcon:SetTexture("Interface\\Icons\\INV_Misc_Dice_01")
minimapIcon:SetSize(20, 20)
minimapIcon:SetPoint("CENTER", 0, 0)
local border = minimapBtn:CreateTexture(nil, "OVERLAY")
border:SetTexture("Interface\\Minimap\\MiniMap-TrackingBorder")
border:SetSize(54, 54)
border:SetPoint("TOPLEFT", 0, 0)
minimapBtn:SetScript("OnClick", function(self, button)
if button == "LeftButton" then
if d20:IsShown() then
d20:Hide()
else
d20:Show()
end
end
end)
minimapBtn:SetScript("OnEnter", function(self)
GameTooltip:SetOwner(self, "ANCHOR_LEFT")
GameTooltip:AddLine("SimpleD20")
GameTooltip:AddLine("Left Click: Toggle Dice Frame", 1, 1, 1)
GameTooltip:AddLine("Right Click: Drag Icon", 1, 1, 1)
GameTooltip:Show()
end)
minimapBtn:SetScript("OnLeave", function(self)
GameTooltip:Hide()
end)
local function UpdateMinimapButtonPosition(angle)
local radius = 80
local x = math.cos(angle) * radius
local y = math.sin(angle) * radius
minimapBtn:SetPoint("CENTER", Minimap, "CENTER", x, y)
end
minimapBtn:SetScript("OnDragStart", function(self)
self:LockHighlight()
self:SetScript("OnUpdate", function(self)
local mx, my = Minimap:GetCenter()
local px, py = GetCursorPosition()
local scale = Minimap:GetEffectiveScale()
px, py = px / scale, py / scale
local angle = math.atan2(py - my, px - mx)
UpdateMinimapButtonPosition(angle)
end)
end)
minimapBtn:SetScript("OnDragStop", function(self)
self:UnlockHighlight()
self:SetScript("OnUpdate", nil)
end)
UpdateMinimapButtonPosition(math.rad(225))