Module:Events/Table
Jump to navigation
Jump to search
Documentation for this module may be created at Module:Events/Table/doc
local EraMod = require( 'Module:Era' )
local GenMod = require( 'Module:General' )
local p = {}
local function active()
local active_state = false
if start_date_beta ~= nil and start_date_beta ~= "" then
active_start = start_date_beta
end
if start_date ~= nil and start_date ~= "" then
active_start = start_date
end
if active_start ~= nil and active_start ~= "" then
local day, month, year = active_start:match("(%d+)-(%d+)-(%d+)")
if day and month and year then
day = tonumber(day)
month = tonumber(month)
year = tonumber(year)
if day >= 1 and day <= 31 and month >= 1 and month <= 12 and year >= 1900 and year <= 2100 then
local inputDate = os.time({year = year, month = month, day = day, hour = 0, min = 0, sec = 0})
local endDate = inputDate + (duration * 24 * 60 * 60) -- Calculate end date in seconds
local today = os.time({year = os.date("*t").year, month = os.date("*t").month, day = os.date("*t").day, hour = 0, min = 0, sec = 0})
if today >= inputDate then
active_state = true
end
end
end
end
return active_state
end
local function day_suffix(day)
local suffix
day = day:gsub('0*', '', 1)
if day == "11" or day == "12" or day == "13" then
suffix = "th"
else
local lastDigit = tonumber(day) % 10
suffix = lastDigit == 1 and "st" or (lastDigit == 2 and "nd" or (lastDigit == 3 and "rd" or "th"))
end
return day .. suffix
end
local function countdown()
local text_string = ""
if start_date_beta ~= nil and start_date_beta ~= "" and start_time_beta ~= nil and start_time_beta ~= "" then
text_string = '<b>Beta:</b> '
local day, month, year = start_date_beta:match("(%d+)-(%d+)-(%d+)")
if day and month and year then
day = tonumber(day)
month = tonumber(month)
year = tonumber(year)
if day >= 1 and day <= 31 and month >= 1 and month <= 12 and year >= 1900 and year <= 2100 then
local inputDate = os.time({year = tonumber(year), month = tonumber(month), day = tonumber(day)})
local endDate = inputDate + (duration * 24 * 60 * 60) -- Calculate end date in seconds
text_string = text_string .. '<span data-options="short-format" data-end="toggle" data-toggle="next" class="countdown" style="display:none;"><span class="countdowndate">' .. os.date("%B %d %Y", endDate) .. ' ' .. start_time_beta .. '</span></span><span style="display:none;">Ended</span>'
else
text_string = text_string .. start_date_beta
end
else
text_string = text_string .. start_date_beta
end
end
if start_date ~= nil and start_date ~= "" then
local day, month, year = start_date:match("(%d+)-(%d+)-(%d+)")
if day and month and year then
day = tonumber(day)
month = tonumber(month)
year = tonumber(year)
if day >= 1 and day <= 31 and month >= 1 and month <= 12 and year >= 1900 and year <= 2100 then
local inputDate = os.time({year = tonumber(year), month = tonumber(month), day = tonumber(day)})
local endDate = inputDate + (duration * 24 * 60 * 60) -- Calculate end date in seconds
if start_time_am ~= nil and start_time_am ~= "" then
if text_string ~= "" then
text_string = text_string .. '<br>'
end
text_string = text_string .. '<b>AM:</b> <span data-options="short-format" data-end="toggle" data-toggle="next" class="countdown" style="display:none;"><span class="countdowndate">' .. os.date("%B %d %Y", endDate) .. ' ' .. start_time_am .. '</span></span><span style="display:none;">Ended</span>'
end
if start_time_eu ~= nil and start_time_eu ~= "" then
if text_string ~= "" then
text_string = text_string .. '<br>'
end
text_string = text_string .. '<b>EU:</b> <span data-options="short-format" data-end="toggle" data-toggle="next" class="countdown" style="display:none;"><span class="countdowndate">' .. os.date("%B %d %Y", endDate) .. ' ' .. start_time_eu .. '</span></span><span style="display:none;">Ended</span>'
end
else
if text_string ~= "" then
text_string = text_string .. '<br>'
end
text_string = text_string .. '<b>AM:</b> ' .. start_date .. '<br><b>EU:</b> ' .. start_date
end
else
if text_string ~= "" then
text_string = text_string .. '<br>'
end
text_string = text_string .. '<b>AM:</b> ' .. start_date .. '<br><b>EU:</b> ' .. start_date
end
end
return text_string
end
local function formatDate(add_days)
if type(add_days) == 'number' then
duration = add_days
end
local text_string = ""
local day, month, year = start_date:match("(%d+)-(%d+)-(%d+)")
if day and month and year then
if tonumber(day) >= 1 and tonumber(day) <= 31 and tonumber(month) >= 1 and tonumber(month) <= 12 and tonumber(year) >= 1900 and tonumber(year) <= 2100 then
start_date_values = os.time({year = tonumber(year), month = tonumber(month), day = tonumber(day)})
local formattedStartDate = day_suffix(day) .. " " .. os.date("%B %Y", start_date_values)
local endDate = start_date_values + (duration * 24 * 60 * 60) -- Calculate end date in seconds
local endDay = day_suffix(os.date("%d",endDate))
local formattedEndDate = endDay .. ' ' .. os.date("%B %Y", endDate) -- Format end date
if add_days then
text_string = formattedEndDate
else
text_string = formattedStartDate .. " - " .. formattedEndDate .. "<br>(" .. duration .. " days)"
end
end
else
text_string = start_date
end
return text_string
end
local function daily_specials()
if status == nil then -- for older event modules that do not have this variable, set as 'live' to show the daily specials.
status = 'live'
end
local text_string = '<div style="overflow: auto;"><h2>Daily Specials</h2>'
if type(status) == "string" and status:lower() == "beta" then
start_date = start_date_beta
end
if daily_special ~= nil and start_date ~= nil and status ~= "" then
local Timestamp = os.time{year=tonumber(start_date:sub(7)), month=tonumber(start_date:sub(4, 5)), day=tonumber(start_date:sub(1, 2))}
for i = 1, #daily_special do
newTimestamp = Timestamp + ((i-1) * 24 * 60 * 60)
local newDateString = os.date("%d %b", newTimestamp)
local padded_day = i
if i < 10 then
padded_day = i .. ' '
end
local description = ""
local reward_string = ""
if type(daily_special[i]) == "string" and daily_special[i] ~= "" then
description = daily_special[i]
local item = string.upper(daily_special[i])
if string.find(item, 'REFILL') or string.find(item, 'TOKEN') or string.find(item, 'GEARS') or string.find(item, 'KIT') or string.find(item, 'CHEST') then
reward_string = '[[File:' .. daily_special[i] .. '.png|link=|100px]]'
else
reward_string = '[[File:' .. daily_special[i] .. '.png|link=|x272px]]'
end
end
if daily_special_desc ~= nil and daily_special_desc[i] ~= nil then
description = daily_special_desc[i]
end
text_string = text_string .. '<tt><div style="white-space: nowrap; display: flex; gap: 10px; justify-content: flex-start;">'
text_string = text_string .. '<div class="mw-customtoggle-' .. i ..' mw-customtoggle-' .. i ..'expand mw-customtoggle-' .. i ..'collapse" style="width: 22px;"><span class="mw-collapsible" id="mw-customcollapsible-' .. i ..'expand" style="text-align:center;">[+]</span><span class="mw-collapsible mw-collapsed" id="mw-customcollapsible-' .. i ..'collapse" style="text-align:center;">[-]</span></div>'
if string.lower(status) == 'live' then
text_string = text_string .. '<div style="font-weight: bold;"><span style="color: rgb(' .. Era_Array[5][3] .. ');">' .. newDateString .. '</span> '
end
text_string = text_string .. '<span style="color: rgb(' .. Era_Array[6][3] .. ');">Day ' .. padded_day .. '</span> ' .. description .. '</div></div></tt>'
text_string = text_string .. '<div id="mw-customcollapsible-' .. i ..'" class="mw-collapsible mw-collapsed">' .. reward_string .. '</div>'
end
end
text_string = text_string .. '</div>'
return text_string
end
local function event_buildings(link)
local text_string = ''
link = link or ''
if evolution_building ~= nil then
for i = 1, #evolution_building do
if i > 1 then
text_string = text_string .. ', '
end
if string.lower(link) == 'link' then
text_string = text_string .. '<span style="white-space: nowrap;">[[Buildings/' .. evolution_building[i] .. '|' .. evolution_building[i] .. ']]</span>'
else
text_string = text_string .. '<span style="white-space: nowrap;">' .. evolution_building[i] .. '</span>'
end
end
end
return text_string
end
local function event_buildings_heading()
local text_string = ''
if evolution_building ~= nil and #evolution_building > 0 then
text_string = '<h2>Event Building'
if #evolution_building > 1 then
text_string = text_string .. 's'
end
text_string = text_string .. '</h2><div style="display: flex; flex-wrap: wrap; gap: 24px;">'
for i = 1, #evolution_building do
text_string = text_string .. '<table class="article-table"><tr class="bg-th"><td>[[File:' .. evolution_building[i] .. '.png|center|100px|link=]]</td></tr><tr><td>' .. GenMod.Icon(evolution_building[i] .. ' Token') .. ' [[Buildings/' .. evolution_building[i] .. '|' .. evolution_building[i] .. ']]</td></tr></table>'
end
text_string = text_string .. '</div>'
end
return text_string
end
function p.main(data)
if type(data.args[1]) ~= 'string' or data.args[1] == '' then
return
else
data.args[1] = data.args[1]:gsub('^%s+', '') --Strip leading spaces
data.args[1] = data.args[1]:gsub('%s+$', '') --Strip trailing spaces
end
local event_module = 'Module:Events/' .. data.args[1]
-- If event module doesn't exist then return
if mw.title.new(event_module).exists == false then
return
end
local strings = {}
require(event_module)
if type(data.args[2]) == 'string' then
data.args[2] = data.args[2]:gsub('^%s+', '') --Strip leading spaces
data.args[2] = data.args[2]:gsub('%s+$', '') --Strip trailing spaces
if string.lower(data.args[2]) == 'video' then return video end
if string.lower(data.args[2]) == 'date' then return formatDate() end
if string.lower(data.args[2]) == 'token' then return token end
if string.lower(data.args[2]) == 'active' then return active() end
if string.lower(data.args[2]) == 'progress' then return progress end
if string.lower(data.args[2]) == 'status' then return status:lower() end
if string.lower(data.args[2]) == 'countdown' then return countdown() end
if string.lower(data.args[2]) == 'start_tokens' then return start_tokens end
if string.lower(data.args[2]) == 'daily_tokens' then return daily_tokens end
if string.lower(data.args[2]) == 'daily_special' then return daily_specials() end
if string.lower(data.args[2]) == 'incident_tokens' then return incident_tokens end
if string.lower(data.args[2]) == 'event_buildings' then return event_buildings() end
if string.lower(data.args[2]) == 'event_buildings_link' then return event_buildings('link') end
if string.lower(data.args[2]) == 'event_buildings_heading' then return event_buildings_heading() end
return
end
if questline ~= nil then
quest_total = 0 --Hold the total of all previous tasks before the current questline that is being contructed. Used to calculate the date
for questlines = 1, #questline do
if questlines > 1 then table.insert(strings, '<br><br>') end
-- Section
table.insert(strings, '<h2>' .. quest_header .. ' ' .. questlines .. '/' .. #questline .. '</h2>')
for quest = 1, #questline[questlines] do
if quest == 1 then
-- Questline table
table.insert(strings, '<div style="overflow: auto;">')
table.insert(strings, '<table class="table mw-collapsible mw-collapsed" style="padding: 0px;">')
table.insert(strings, '<tr><td></td></tr>')
table.insert(strings, '<tr><td>')
table.insert(strings, '<div style="display: flex; gap: 24px; align-items: flex-start;">')
table.insert(strings, '<div style="border: 1px black solid; border-radius: 8px; min-width: 120px; max-width: 120px; background: #f3e4af; text-align: center;">')
table.insert(strings, '<div style=" text-align: center; vertical-align: bottom; background: radial-gradient(white, #359baf); border-radius: 8px 8px 0px 0px;">' .. GenMod.Icon(questline_questgiver[questlines],100) .. '</div>')
table.insert(strings, '<div style="padding: 4px; color: #4f3e0f;">' .. questline_reward[questlines] .. '</div>')
table.insert(strings, '</div>')
end
--Calculate which date the tasks are available.
if data.args[1] == 'Hercules Event (2021)' then
--Hercules Event (2021) starts with 38 then 1 per day
if (quest_total + quest) < 39 then
add_days = 0
else
add_days = math.floor(((quest_total + quest) - 39))+1
end
else
--General formula: Start with 28 then 4 per day
if (quest_total + quest) < 29 then
add_days = 0
else
add_days = math.floor(((quest_total + quest) - 29)/4)+1
end
end
quest_date = formatDate(add_days)
table.insert(strings, '<div style="display: flex; width: 630px; gap: 12px; flex-direction: column;">')
table.insert(strings, '<div style="background: #25778a; border-radius: 8px; overflow: hidden;">')
if left_colour == nil or left_colour == '' then left_colour = '#329CB3' end
table.insert(strings, '<div style="display: flex; justify-content: center; gap: 4px; height: min-content; background: linear-gradient(to right, ' .. left_colour .. ', #329CB3 100%); border-radius: 8px 8px 0 0; padding: 4px; color: white;">')
table.insert(strings, '<div style="text-shadow: 0px 0px 3px black; font-weight: bold; flex: 1; text-align: left;">' .. quest_header .. ' ' .. questlines .. '/' .. #questline .. '</div><div style="text-shadow: 0px 0px 3px black; font-weight: bold; flex: 1; text-align: center;">' .. quest_date .. '</div><div style="text-shadow: 0px 0px 3px black; font-weight: bold; flex: 1; text-align: right;">' .. quest .. '/' .. #questline[questlines] .. '</div>')
table.insert(strings, '</div>')
table.insert(strings, '<div style="min-height: 4px; max-height: 4px; background: linear-gradient(#404040, #25778a);"></div>')
table.insert(strings, '<div style="display: flex; gap: 4px; padding: 4px;">')
table.insert(strings, '<div style="min-width: 80%; max-width: 80%;">')
if type(questline[questlines][quest][1]) ~= 'string' or questline[questlines][quest][1] == '' then questline[questlines][quest][1] = '[task 1]' end -- Error handler if the incorrect type or blank data is used
table.insert(strings, '<div style="display: flex; min-height: 64px; align-items: center; background: #f3e4af; border-radius: 8px; padding: 4px;"><span style="color: #4f3e0f;">' .. highlight(questline[questlines][quest][1]) .. '</span></div>')
if questline[questlines][quest][2] ~= nil then
table.insert(strings, '<div style="position: relative; display: flex; align-items: center; justify-content: center; width: 100%; height: 19px; margin: 4px;">')
table.insert(strings, '<div style="width: 100%; height: 2px; vertical-align: middle; background: linear-gradient(to right, transparent, #6bbcc9, #6bbcc9, #6bbcc9, transparent);"></div>')
table.insert(strings, '<div style="position: absolute; top: 0px; left: 0px; display: flex; align-items: center; justify-content: center; width: 100%; height: 100%;"><div style="width: 60px; height: 19px; background-color: #6bbcc9; border-radius: 90px;"></div></div>')
table.insert(strings, '<div style="position: absolute; top: 0px; left: 0px; display: flex; align-items: center; justify-content: center; width: 100%; height: 100%;"><div style="line-height: 19px; color: #25778a; font-weight: bold;">OR</div></div>')
table.insert(strings, '</div>')
-- Error handler if the incorrect type or blank data is used
if type(questline[questlines][quest][2]) ~= 'string' or questline[questlines][quest][2] == '' then
questline[questlines][quest][2] = '[task 2]'
end
table.insert(strings, '<div style="display: flex; min-height: 64px; align-items: center; background: #f3e4af; border-radius: 8px; padding: 4px;"><span style="color: #4f3e0f;>' .. highlight(questline[questlines][quest][2]) .. '</span></div>')
end
table.insert(strings, '</div>')
table.insert(strings, '<div style="display: flex; width: 20%; align-items: center; justify-content: center; background: #f3e4af; border-radius: 8px; padding: 4px;"><span style="text-align: center; color: #4f3e0f;">')
if questline[questlines][quest][0] ~= nil then
table.insert(strings, questline[questlines][quest][0])
else
table.insert(strings, GenMod.Icon(token) .. ' ' .. quest_reward)
end
table.insert(strings, '</span></div>')
table.insert(strings, '</div>')
table.insert(strings, '</div>')
end
table.insert(strings, '</div>')
table.insert(strings, '</td></tr></table>')
table.insert(strings, '</div>')
quest_total = quest_total + #questline[questlines]
end
end
if #strings > 0 and questline_extra ~= nil and #questline_extra > 0 then
table.insert(strings, '<br><br>')
for questlines_extra = 1, #questline_extra do
if questlines_extra > 1 then table.insert(strings, '<br><br>') end
-- Section
table.insert(strings, '<h2>' .. quest_extra_header .. ' ' .. questlines_extra .. '/' .. #questline_extra .. '</h2>')
for quest = 1, #questline_extra[questlines_extra] do
if quest == 1 then
-- Questline table
table.insert(strings, '<div style="overflow: auto;">')
table.insert(strings, '<table class="table mw-collapsible mw-collapsed" style="padding: 0px;">')
table.insert(strings, '<tr><td></td></tr>')
table.insert(strings, '<tr><td>')
table.insert(strings, '<div style="display: flex; gap: 24px; align-items: flex-start;">')
table.insert(strings, '<div style="border: 1px black solid; border-radius: 8px; min-width: 120px; max-width: 120px; background: #f3e4af; text-align: center;">')
table.insert(strings, '<div style=" text-align: center; vertical-align: bottom; background: radial-gradient(white, #359baf); border-radius: 8px 8px 0px 0px;">' .. GenMod.Icon(questline_extra_questgiver[questlines_extra],100) .. '</div>')
table.insert(strings, '<div style="padding: 4px; color: #4f3e0f;">' .. questline_extra_reward[questlines_extra] .. '</div>')
table.insert(strings, '</div>')
end
table.insert(strings, '<div style="display: flex; width: 630px; gap: 12px; flex-direction: column;">')
table.insert(strings, '<div style="background: #25778a; border-radius: 8px; overflow: hidden;">')
if left_colour == nil or left_colour == '' then left_colour = '#329CB3' end
table.insert(strings, '<div style="display: flex; align-items: center; gap: 4px; width: 100%; height: min-content; background: linear-gradient(to right, ' .. left_colour .. ', #329CB3 100%); border-radius: 8px 8px 0 0; padding: 4px; color: white;">')
table.insert(strings, '<div style="flex: 1; text-shadow: 0px 0px 3px black; font-weight: bold;">' .. quest_extra_header .. '</div><span style="text-shadow: 0px 0px 3px black; font-weight: bold;">' .. quest .. '/' .. #questline_extra[questlines_extra] .. '</span>')
table.insert(strings, '</div>')
table.insert(strings, '<div style="min-height: 4px; max-height: 4px; background: linear-gradient(#404040, #25778a);"></div>')
table.insert(strings, '<div style="display: flex; gap: 4px; padding: 4px;">')
table.insert(strings, '<div style="min-width: 80%; max-width: 80%;">')
if type(questline_extra[questlines_extra][quest][1]) ~= 'string' or questline_extra[questlines_extra][quest][1] == '' then questline_extra[questlines_extra][quest][1] = '[task 1]' end -- Error handler if the incorrect type or blank data is used
table.insert(strings, '<div style="display: flex; min-height: 64px; align-items: center; background: #f3e4af; border-radius: 8px; padding: 4px;"><span style="color: #4f3e0f;">' .. highlight(questline_extra[questlines_extra][quest][1]) .. '</span></div>')
if questline_extra[questlines_extra][quest][2] ~= nil then
table.insert(strings, '<div style="position: relative; display: flex; align-items: center; justify-content: center; width: 100%; height: 19px; margin: 4px;">')
table.insert(strings, '<div style="width: 100%; height: 2px; vertical-align: middle; background: linear-gradient(to right, transparent, #6bbcc9, #6bbcc9, #6bbcc9, transparent);"></div>')
table.insert(strings, '<div style="position: absolute; top: 0px; left: 0px; display: flex; align-items: center; justify-content: center; width: 100%; height: 100%;"><div style="width: 60px; height: 19px; background-color: #6bbcc9; border-radius: 90px;"></div></div>')
table.insert(strings, '<div style="position: absolute; top: 0px; left: 0px; display: flex; align-items: center; justify-content: center; width: 100%; height: 100%;"><div style="line-height: 19px; color: #25778a; font-weight: bold;">OR</div></div>')
table.insert(strings, '</div>')
-- Error handler if the incorrect type or blank data is used
if type(questline_extra[questlines_extra][quest][2]) ~= 'string' or questline_extra[questlines_extra][quest][2] == '' then
questline_extra[questlines_extra][quest][2] = '[task 2]'
end
table.insert(strings, '<div style="display: flex; min-height: 64px; align-items: center; background: #f3e4af; border-radius: 8px; padding: 4px;"><span style="color: #4f3e0f;">' .. highlight(questline_extra[questlines_extra][quest][2]) .. '</span></div>')
end
table.insert(strings, '</div>')
table.insert(strings, '<div style="display: flex; width: 20%; align-items: center; justify-content: center; background: #f3e4af; border-radius: 8px; padding: 4px;"><span style="text-align: center; color: #4f3e0f;>' .. questline_extra[questlines_extra][quest][0] .. '</span></div>')
table.insert(strings, '</div>')
table.insert(strings, '</div>')
end
table.insert(strings, '</div>')
table.insert(strings, '</td></tr></table>')
table.insert(strings, '</div>')
end
end
return table.concat(strings)
end
function highlight(quest)
quest = string.gsub(quest, "Collect (Coins)", "Collect <b>%1</b>")
quest = string.gsub(quest, "Collect (Food)", "Collect <b>%1</b>")
quest = string.gsub(quest, "Collect (%d+) production(s?) in (Farms) from (%[Previous Era%] or %[Current Era%]) in (Capital City)", "Collect <b>%1</b> production%2 in <b>%3</b> from <b>%4</b> in <b>%5</b>")
quest = string.gsub(quest, "Collect (%d+ short) production(s?) in (Farms) from (%[Previous Era%] or %[Current Era%]) in (Capital City)", "Collect <b>%1</b> production%2 in <b>%3</b> from <b>%4</b> in <b>%5</b>")
quest = string.gsub(quest, "Collect (%d+ medium) production(s?) in (Farms) from (%[Previous Era%] or %[Current Era%]) in (Capital City)", "Collect <b>%1</b> production%2 in <b>%3</b> from <b>%4</b> in <b>%5</b>")
quest = string.gsub(quest, "Collect (%d+ long) production(s?) in (Farms) from (%[Previous Era%] or %[Current Era%]) in (Capital City)", "Collect <b>%1</b> production%2 in <b>%3</b> from <b>%4</b> in <b>%5</b>")
quest = string.gsub(quest, "Collect (%d+) production(s?) in (Luxurious Farm) from (%[Previous Era%] or %[Current Era%]) in (Capital City)", "Collect <b>%1</b> production%2 in <b>%3</b> from <b>%4</b> in <b>%5</b>")
quest = string.gsub(quest, "Pay (Coins)", "Pay <b>%1</b>")
quest = string.gsub(quest, "Pay (Food)", "Pay <b>%1</b>")
quest = string.gsub(quest, "Pay (%d+ Research Points?)", "Pay <b>%1</b>")
quest = string.gsub(quest, "Pay (%w+ Goods)", "Pay <b>%1</b>")
quest = string.gsub(quest, "Pay (%d+ Infantry units?)", "Pay <b>%1</b>")
quest = string.gsub(quest, "Pay (%d+ Cavalry units?)", "Pay <b>%1</b>")
quest = string.gsub(quest, "Pay (%d+ Ranged units?)", "Pay <b>%1</b>")
quest = string.gsub(quest, "Pay (%d+ Heavy Infantry units?) %(or era equivalent%)", "Pay <b>%1</b> (or era equivalent)")
quest = string.gsub(quest, "Pay (%d+ Siege units?) %(or era equivalent%)", "Pay <b>%1</b> (or era equivalent)")
quest = string.gsub(quest, "Accept (%d+) trade offer(s?)", "Accept <b>%1</b> trade offer%2")
quest = string.gsub(quest, "Research (%d+) (technolog%w+)", "Research <b>%1</b> %2")
quest = string.gsub(quest, "Solve (%d+) incident(s?)", "Solve <b>%1</b> incident%2")
quest = string.gsub(quest, "Spend (%d+) Research Point(s?) %(e.g. in the Research Tree or Wonders%)", "Spend <b>%1</b> Research Point%2 (e.g. in the Research Tree or Wonders)")
quest = string.gsub(quest, "Win (%d+) battle(s?)", "Win <b>%1</b> battle%2")
quest = string.gsub(quest, "Acquire (%d+) part(s?) of any region", "Acquire <b>%1</b> part%2 of any region")
quest = string.gsub(quest, "Have (1 .*) on level (%d+) in (Capital City)", "Have <b>%1</b> on level <b>%2</b> in <b>%3</b>")
return quest
end
return p