Editing
Module
:
InfoboxHero
Give feedback
Jump to navigation
Jump to search
Warning:
You are not logged in. Once you make an edit, a temporary account will be created for you.
Learn more
.
Log in
or
create an account
to continue receiving notifications after this account expires, and to access other features.
Anti-spam check. Do
not
fill this in!
-------------------------------------------------------------------------------- -- Module:InfoboxHero -- -- Renders the hero infobox. Called from [[Template:Infobox hero]], which passes -- the editor's parameters through as the parent frame: -- -- {{#invoke:InfoboxHero|render}} -- -- May also be invoked directly for testing, in which case parameters are read -- from the invoke itself: -- -- {{#invoke:InfoboxHero|render|key=hero_atlas}} -- -- Stat data comes from [[Data:HeroData.json]]; per-hero colors, backgrounds and -- logo sizes from [[Data:Infobox hero.json]]; labels from [[Module:Lang]] and -- [[Module:Dictionary]]. Styling lives in [[Template:Infobox hero/styles.css]] -- and [[Template:Infobox hero/themes.css]]. -- -------------------------------------------------------------------------------- local p = {} local hero_data_all = mw.loadJsonData('Data:HeroData.json') local theme_data = mw.loadJsonData('Data:Infobox_hero.json') local util = require('Module:Utilities') local lang_module = require('Module:Lang') local dict_module = require('Module:Dictionary') local hero_module = require('Module:HeroData') local sb_module = require('Module:StreetBrawlAbilities') -- Shared hero-data lookups, see [[Module:HeroData]]. local get_stat = hero_module.get_stat local get_scalings = hero_module.get_scalings local has_stat = hero_module.has_stat local has_override_or_stat, stat_number -------------------------------------------------------------------------------- -- CONFIG -------------------------------------------------------------------------------- local DEFAULT_HERO_KEY = 'hero_targetdummy' -- used when |key= is missing/unknown local DEFAULT_SIG_FIGS = 3 local FALLBACK_CARD = 'Dummy card.png' -- Row fields (all optional except a label): -- stat Key in Data:HeroData.json. Resolved top-level, then .Weapon, -- then .Weapon.AltFire. Omit for manual-input-only rows. -- param |param= that overrides the value. -- label Key in Data:Lang_<code>.json. -- label_dict Key in Data:Dictionary. Used instead of `label`. -- label_text Literal, untranslated label. Used instead of `label`. -- link Target for the label, as 'Page' or 'Page#Anchor'. The language -- suffix is inserted for us, so write the plain English title. -- icon_link Target for the icon. Defaults to `link`. -- icon File: name of the value icon. Omit for no icon. -- sig_figs Significant figures. Defaults to 3. -- suffix Appended directly after the value, e.g. '%', 's', 'm/s'. -- scaling Set false to suppress the Boon/Spirit scaling span. Scaling is -- shown by default wherever the data provides it. -- optional Hide the row unless the hero has a non-zero value or scaling. -- cell_class Extra class on the value cell. -- show function(ctx) -> boolean. Row omitted when false. local SECTIONS = { { class = 'infobox-h-dropdown-weapon', header = 'CitadelHeroStats_Weapon', link = 'Stats#Weapon', color = 'Brown', rows = { { stat = 'DPS', param = 'dps', label = 'DPS_label', link = 'Damage per second', icon = 'Damage.png', -- DPS is derived from bullet damage and fire rate, both of which -- show their own scaling; repeating it here would double-count. scaling = false, }, { stat = 'BulletDamage', param = 'bullet_damage', label = 'StatDesc_BulletDamage', link = 'Bullet Damage', icon = 'Bullet damage.png', cell_class = 'infobox-h-stat-value-padded', }, { stat = 'BulletsPerShot', param = 'pellets_per_shot', label = 'BonusPelletsPerShot_label', link = 'Damage per second', icon = 'Bullets per sec icon.png', icon_link = 'Fire Rate', show = function(ctx) return ctx.pellets > 1 end, }, { stat = 'BulletsPerBurst', param = 'bullets_per_burst', label_dict = 'BulletsPerBurst', link = 'Damage per second', icon = 'Bullets per sec icon.png', icon_link = 'Fire Rate', show = function(ctx) return ctx.burst > 1 end, }, { stat = 'ClipSize', param = 'ammo', label = 'StatDesc_ClipSizeBonus', link = 'Ammo', icon = 'Clip size icon.png', }, { stat = 'RoundsPerSecond', param = 'bullets_per_sec', label = 'StatDesc_RoundsPerSecond', link = 'Fire Rate', icon = 'Bullets per sec icon.png', }, { stat = 'ReloadTime', param = 'reload_time', label = 'StatDesc_ReloadTime', link = 'Reload Time', icon = 'AttributeIconReloadTime.png', suffix = 's', }, { stat = 'BulletSpeed', param = 'bullet_velocity', label = 'StatDesc_BulletSpeed', link = 'Bullet Velocity', icon = 'AttributeIconBulletSpeed.png', suffix = 'm/s', }, { stat = 'LightMeleeDamage', param = 'light_melee', label = 'StatDesc_LightMeleeDamage', link = 'Melee Damage', icon = 'Melee damage.png', cell_class = 'infobox-h-stat-value-padded', }, { stat = 'HeavyMeleeDamage', param = 'heavy_melee', label = 'StatDesc_HeavyMeleeDamage', link = 'Melee Damage', icon = 'Melee damage.png', cell_class = 'infobox-h-stat-value-padded', }, { param = 'max_range', label_text = 'Max Range', link = 'Falloff Range', show = function(ctx) return ctx.args.max_range ~= '' end, }, { stat = 'CritDamageBonusPercent', label = 'StatDesc_CritDamageBonusScale', link = 'Weapon Damage#Crit_Multiplier', icon = 'Crit reduction.png', suffix = '%', show = function(ctx) return stat_number(ctx, 'CritDamageBonusPercent') ~= 0 end, }, }, }, { class = 'infobox-h-dropdown-vitality', header = 'CitadelHeroStats_Vitality', link = 'Stats#Vitality', color = 'Green', rows = { { stat = 'MaxHealth', param = 'max_health', label = 'StatDesc_HealthAttribute', link = 'Health', icon = 'Health.png', }, { stat = 'BaseHealthRegen', param = 'health_regen', label = 'StatDesc_HealthRegen', link = 'Health Regen', icon = 'Health regen.png', sig_figs = 3, }, { stat = 'CritDamageReceivedPercent', param = 'crit_reduction', label = 'StatDesc_CritDamageReceivedScale', link = 'Damage Resistance#Crit_Reduction', icon = 'Crit reduction.png', suffix = '%', optional = true, }, { stat = 'BulletResist', param = 'bullet_resist', label = 'StatDesc_BulletArmorDamageReduction', link = 'Damage Resistance', icon = 'Bullet Armor.png', icon_link = 'Bullet Resist', suffix = '%', optional = true, }, { stat = 'TechResist', param = 'spirit_resist', label = 'StatDesc_TechArmorDamageReduction', link = 'Damage Resistance#Spirit_Resist', icon = 'Spirit Armor.png', suffix = '%', optional = true, }, { stat = 'MeleeResist', param = 'melee_resist', label = 'StatDesc_MeleeResist', link = 'Damage Resistance#Melee Resist', icon = 'Melee damage.png', suffix = '%', optional = true, }, { stat = 'DebuffResist', param = 'debuff_resist', label = 'StatDesc_DebuffResist', link = 'Debuff Resist', icon = 'Spirit Armor.png', suffix = '%', optional = true, }, { stat = 'BulletLifesteal', param = 'bullet_lifesteal', label = 'StatDesc_BulletLifesteal', link = 'Lifesteal#Bullet Lifesteal', icon = 'AttributeIconBulletLifesteal.png', suffix = '%', optional = true, }, { stat = 'SpiritLifesteal', param = 'spirit_lifesteal', label = 'StatDesc_SpiritLifesteal', link = 'Lifesteal#Spirit Lifesteal', icon = 'AttributeIconBulletLifesteal.png', suffix = '%', optional = true, }, { stat = 'MaxMoveSpeed', param = 'move_speed', label = 'StatDesc_RunSpeed', link = 'Move Speed', icon = 'Move speed.png', sig_figs = 2, suffix = 'm/s', }, { stat = 'SprintSpeed', param = 'sprint_speed', label = 'StatDesc_SprintSpeed', link = 'Move Speed#Sprint', icon = 'Move speed sprint.png', sig_figs = 2, suffix = 'm/s', }, { stat = 'GroundDashSpeed', label = 'DashSpeed_label', link = 'Stamina#Dash_Speed', icon = 'Springy boots icon.png', suffix = 'm/s', }, { stat = 'Stamina', param = 'stamina', label = 'StatDesc_Stamina', link = 'Stamina', icon = 'Springy boots icon.png', sig_figs = 2, }, { stat = 'StaminaCooldown', param = 'stamina_cooldown', label = 'StatDesc_StaminaCooldown', link = 'Stamina#Cooldown', icon = 'AttributeIconTechDuration.png', sig_figs = 2, suffix = 's', }, }, }, { class = 'infobox-h-dropdown-spirit', header = 'CitadelHeroStats_Spirit', link = 'Stats#Spirit', color = 'Purple', rows = { { stat = 'TechPower', param = 'spirit_power', label = 'SpiritPower_label', link = 'Spirit Power', icon = 'Spirit icon noborder.png', sig_figs = 2, }, }, }, } -- The "Information" block. Manual input only; hidden entirely when all are empty. local INFO_ROWS = { { param = 'pronouns', label_dict = 'Pronouns' }, { param = 'voice_actor', label_dict = 'VoiceActor', link = 'Voice actors' }, { param = 'release_date', label_dict = 'ReleaseDate', link = 'Heroes by release' }, { param = 'codenames', label_dict = 'Codenames', tooltip = 'Codenames used for this hero in the game files' }, } -- name: literal, or function(ctx) -> string. when: function(ctx) -> boolean. local CATEGORIES = { { name = function(ctx) return ctx.is_disabled and 'Unused Heroes' or 'Heroes' end, always = true, }, { name = function(ctx) return (ctx.args.name ~= '' and ctx.args.name) or ctx.hero_en end, -- Silver (Transformed) is documented on Silver's page and shares her category. when = function(ctx) return ctx.hero_en ~= 'Silver (Transformed)' end, always = true, }, { name = 'Heroes with spreadshot weapons', when = function(ctx) return ctx.pellets > 1 end, }, { name = 'Heroes with single damage spreadshot weapons', when = function(ctx) return ctx.pellets > 1 and get_stat(ctx.hero, 'HitOnceAcrossAllBullets') == true end, }, { name = 'Heroes with burst weapons', when = function(ctx) return ctx.burst > 1 end, }, { name = 'Heroes with automatic weapons', when = function(ctx) return ctx.pellets <= 1 and ctx.burst <= 1 end, }, { name = 'Heroes with alternate fire weapons', when = function(ctx) return ctx.hero.Weapon ~= nil and ctx.hero.Weapon.AltFire ~= nil end, }, { name = 'Heroes with single reload weapons', when = function(ctx) return get_stat(ctx.hero, 'ReloadSingle') == true end, }, { name = 'Heroes with spin up weapons', when = function(ctx) return has_stat(ctx.hero, 'SpinAcceleration') end, }, { name = 'Heroes with no weapon damage falloff', when = function(ctx) return (get_stat(ctx.hero, 'FalloffStartRange') or 0) == (get_stat(ctx.hero, 'FalloffEndRange') or 0) end, }, { name = "Heroes that don't deal critical damage", when = function(ctx) return get_stat(ctx.hero, 'CanCrit') == false end, }, { name = 'Heroes with modified crit scale', when = function(ctx) return stat_number(ctx, 'CritDamageBonusPercent') ~= 0 end, }, { name = 'Heroes with modified crit resistance', when = function(ctx) return has_override_or_stat(ctx, 'crit_reduction', 'CritDamageReceivedPercent') end, }, { name = 'Heroes with modified bullet resist', when = function(ctx) return has_override_or_stat(ctx, 'bullet_resist', 'BulletResist') end, }, { name = 'Heroes with modified spirit resist', when = function(ctx) return has_override_or_stat(ctx, 'spirit_resist', 'TechResist') end, }, { name = 'Heroes with modified melee resist', when = function(ctx) return has_override_or_stat(ctx, 'melee_resist', 'MeleeResist') end, }, { name = 'Heroes with modified debuff resist', when = function(ctx) return has_override_or_stat(ctx, 'debuff_resist', 'DebuffResist') end, }, { name = 'Heroes with modified bullet lifesteal', when = function(ctx) return has_override_or_stat(ctx, 'bullet_lifesteal', 'BulletLifesteal') end, }, { name = 'Heroes with modified spirit lifesteal', when = function(ctx) return has_override_or_stat(ctx, 'spirit_lifesteal', 'SpiritLifesteal') end, }, { name = 'Heroes with modified gravity scale', when = function(ctx) return has_stat(ctx.hero, 'GravityChange') end, }, { name = function(ctx) return 'Heroes with ' .. ctx.stamina .. ' stamina' end, when = function(ctx) return ctx.stamina ~= '' end, }, { name = function(ctx) if ctx.dash < 14.7 then return 'Heroes with slow dash speed' end if ctx.dash > 14.7 then return 'Heroes with fast dash speed' end return 'Heroes with medium dash speed' end, }, { name = 'Heroes with Street Brawl changes', when = function(ctx) return sb_module.hero_has_changes(ctx.hero_key) end, }, } -------------------------------------------------------------------------------- -- Data access -------------------------------------------------------------------------------- has_override_or_stat = function(ctx, param, stat) if param and ctx.args[param] ~= '' then return true end return has_stat(ctx.hero, stat) end -- Rounded numeric value of a stat, honouring a |param= override. stat_number = function(ctx, stat, param) if param and ctx.args[param] ~= '' then return tonumber(ctx.args[param]) or 0 end return tonumber(util.round_to_sig_fig(get_stat(ctx.hero, stat) or 0, DEFAULT_SIG_FIGS)) or 0 end local function format_value(raw, sig_figs) if raw == nil then return '0' end local rounded = util.round_to_sig_fig(raw, sig_figs or DEFAULT_SIG_FIGS) return tostring(rounded ~= nil and rounded or raw) end -------------------------------------------------------------------------------- -- Rendering helpers -------------------------------------------------------------------------------- local function theme_var(hero_key, name) local hero_theme = theme_data[hero_key] if hero_theme and hero_theme[name] ~= nil then return hero_theme[name] end return theme_data['hero_default'][name] end local function label_of(row) if row.label_text then return row.label_text end if row.label_dict then return dict_module.translate(row.label_dict) end return lang_module.get_string(row.label) end -- 'Damage Resistance#Spirit_Resist' -> 'Damage Resistance/ru#Spirit_Resist' local function page_link(ctx, target) if not target then return '' end local page, anchor = target:match('^([^#]*)(#?.*)$') return page .. ctx.if_lang .. anchor end local function icon_cell(ctx, row, color, value_html) local icon = '' if row.icon then icon = string.format('[[File:%s|20px|link=%s]]', row.icon, page_link(ctx, row.icon_link or row.link)) end return ctx.frame:expandTemplate{ title = 'Icon/' .. color, args = { icon, value_html } } end -- Ammo and reload time are meaningless for heroes whose weapon never reloads. local NO_AMMO_STATS = { ClipSize = true, ReloadTime = true } -- Returns true when the row rendered. local function render_stat_row(ctx, tbl, row, color, is_first) if row.show and not row.show(ctx) then return false end if row.optional and not has_override_or_stat(ctx, row.param, row.stat) then return false end local value, scaling_inner if NO_AMMO_STATS[row.stat] and ctx.args.no_ammo == 'true' then value, scaling_inner = 'N/A', '' else local override = row.param and ctx.args[row.param] or '' if override ~= '' then value = override else value = format_value(get_stat(ctx.hero, row.stat), row.sig_figs) end value = value .. (row.suffix or '') if row.stat and row.scaling ~= false then local parts = {} for _, scaling in ipairs(get_scalings(ctx.hero, row.stat)) do parts[#parts + 1] = hero_module.write_scalar_str(scaling.value, scaling.type) end scaling_inner = table.concat(parts) value = value .. '<span class="infobox-h-spiritboon">' .. scaling_inner .. '</span>' else scaling_inner = '' end end local tr = tbl:tag('tr') local label_cell = tr:tag('td') -- The section's first row needs extra headroom when it carries a scaling icon. if is_first and scaling_inner ~= '' then label_cell:addClass('infobox-h-stat-label-scaled') end if row.link then label_cell:wikitext(string.format('[[%s|<span class="infobox-h-stat-label">%s</span>]]:', page_link(ctx, row.link), label_of(row))) else label_cell:wikitext(label_of(row) .. ':') end local value_cell = tr:tag('td') if row.cell_class then value_cell:addClass(row.cell_class) end value_cell:wikitext(icon_cell(ctx, row, color, value)) return true end local function render_section(ctx, parent, section) local tbl = parent:tag('tr'):tag('th') :attr('colspan', '2') :addClass('infobox-h-subsection infobox-h-dropdown ' .. section.class) :tag('table'):addClass('mw-collapsible infobox-h-subsection-child') local caption = tbl:tag('caption'):addClass('infobox-h-dropdown-header') if ctx.if_lang == '/ru' then caption:addClass('infobox-h-dropdown-header-overflow') end caption:wikitext(string.format( '[[%s|<span class="infotext-h-bold infobox-h-dropdown-title">%s</span>]]', page_link(ctx, section.link), lang_module.get_string(section.header))) local is_first = true for _, row in ipairs(section.rows) do if render_stat_row(ctx, tbl, row, section.color, is_first) then is_first = false end end end local function render_information(ctx, parent) local present = {} for _, row in ipairs(INFO_ROWS) do if ctx.args[row.param] ~= '' then present[#present + 1] = row end end if #present == 0 then return end parent:tag('tr'):tag('th') :attr('colspan', '2') :addClass('infobox-h-text-stat-subtitle') :wikitext('<span class="infotext-h-bold infotext-h-title infotext-h-big">' .. dict_module.translate('Information') .. '</span>') local tbl = parent:tag('tr'):tag('th') :attr('colspan', '2') :addClass('infobox-h-subsection') :tag('table'):addClass('infobox-h-subsection-child') for _, row in ipairs(present) do local tr = tbl:tag('tr') local label = dict_module.translate(row.label_dict) if row.tooltip then label = ctx.frame:expandTemplate{ title = 'Tooltip', args = { label, row.tooltip } } end if row.link then tr:tag('td'):wikitext(string.format('[[%s|<span class="infobox-h-stat-label">%s</span>]]:', page_link(ctx, row.link), label)) else tr:tag('td'):wikitext(label .. ':') end tr:tag('td'):wikitext(ctx.args[row.param]) end end local function render_title(ctx, parent) local th = parent:tag('tr'):tag('th'):attr('colspan', '2'):addClass('infobox-h-title') if ctx.args.name_font_size ~= '' then th:cssText('font-size: ' .. ctx.args.name_font_size .. ';') end local wrap = th:tag('div'):addClass('infobox-h-title-hero') wrap:tag('div'):addClass('infobox-h-title-hero-left') -- Name logo: <Name>_name.svg, or <Name>_name_<lang>.svg on translated pages. -- Heroes without a logo fall back to their name as plain text. local svg = ((ctx.args.name ~= '' and ctx.args.name) or ctx.hero_en) .. '_name' if ctx.if_lang ~= '' then svg = svg .. '_' .. ctx.lang_code end local middle = wrap:tag('div'):addClass('infobox-h-title-hero-middle') if util.page_exists('File:' .. svg .. '.svg') then middle:wikitext(ctx.frame:expandTemplate{ title = 'Icon/White', args = { string.format('[[File:%s.svg|%s|class=notpageimage]]', svg, theme_var(ctx.hero_key, 'logo-height')) } }) else middle:wikitext(ctx.display_name) end local right = wrap:tag('div'):addClass('infobox-h-title-hero-right'):tag('div') if ctx.show_icon then local icon = ((ctx.args.icon ~= '' and ctx.args.icon) or ctx.hero_en) .. '.png' if util.page_exists('File:' .. icon) then right:tag('div'):addClass('infobox-h-title-hero-tornbox') right:tag('div'):addClass('infobox-h-title-hero-icon') :wikitext(string.format('[[File:%s|center|35px|class=notpageimage]]', icon)) end end end local function render_card(ctx, parent) local card = (ctx.args.card ~= '' and ctx.args.card) or (ctx.hero_en .. ' card.png') if not util.page_exists('File:' .. card) then card = FALLBACK_CARD end parent:tag('tr'):tag('th') :attr('colspan', '2') :addClass('infobox-h-image') :tag('div') :wikitext(string.format('[[File:%s|150px|alt=%s]]', card, mw.title.getCurrentTitle().baseText)) end local function render_tags(ctx, parent) if not ctx.show_hero_tags then return end local tags = {} for i = 1, 3 do local tag = hero_module.hero_tag(ctx.hero_en, i) if tag ~= '' then tags[#tags + 1] = tag end end if #tags == 0 then return end local inner = parent:tag('tr'):tag('td') :attr('colspan', '2') :addClass('infobox-h-herotags-row') :tag('div'):addClass('infobox-h-herotags-inner ' .. ctx.hero_key) for _, tag in ipairs(tags) do inner:tag('div'):addClass('infobox-h-herotag-pill'):wikitext(tag) end end local function render_categories(ctx) if mw.ustring.lower(ctx.args.nocat ~= '' and ctx.args.nocat or 'false') ~= 'false' then return '' end local is_disabled = get_stat(ctx.hero, 'Name') == 'TargetDummy' or get_stat(ctx.hero, 'IsDisabled') ctx.is_disabled = is_disabled local out = {} for _, category in ipairs(CATEGORIES) do if is_disabled and not category.always then elseif category.when == nil or category.when(ctx) then local name = category.name if type(name) == 'function' then name = name(ctx) end out[#out + 1] = ctx.frame:expandTemplate{ title = 'lang cat', args = { name } } end end return table.concat(out) end -------------------------------------------------------------------------------- -- Entry point -------------------------------------------------------------------------------- function p.render(frame) local parent = frame:getParent() local invoke_args = frame.args local template_args = parent and parent.args or {} -- Missing parameters read as '' so callers never have to nil-check. local args = setmetatable({}, { __index = function(_, k) local value = invoke_args[k] if value == nil then value = template_args[k] end return value or '' end }) -- |key= accepts a hero_* key or the hero's English name; unknown values fall -- back to the target dummy, the default character in Sandbox mode. local hero_key = hero_module.find_hero_key(args.key) or DEFAULT_HERO_KEY local hero = hero_data_all[hero_key] local ctx = { frame = frame, args = args, hero = hero, hero_key = hero_key, hero_en = hero.Name, lang_code = lang_module.get_lang_code(), if_lang = frame:expandTemplate{ title = 'If lang' }, } ctx.display_name = (args.name ~= '' and args.name) or lang_module.get_string(hero_key) ctx.show_hero_tags = args.show_hero_tags ~= 'false' ctx.show_icon = args.show_icon ~= 'false' -- Needed by both row visibility and the category rules. ctx.pellets = stat_number(ctx, 'BulletsPerShot', 'pellets_per_shot') ctx.burst = stat_number(ctx, 'BulletsPerBurst', 'bullets_per_burst') ctx.dash = stat_number(ctx, 'GroundDashSpeed') ctx.stamina = (args.stamina ~= '' and args.stamina) or format_value(get_stat(hero, 'Stamina'), 2) local root = mw.html.create('div'):addClass('infobox-h ' .. hero_key) root:tag('div'):addClass('infobox-h-background') :wikitext(string.format('[[File:%s|%s|right|link=|class=notpageimage]]', theme_var(hero_key, 'bg-image'), theme_var(hero_key, 'bg-height'))) local tbl = root:tag('table'):addClass('infobox-h-content') render_title(ctx, tbl) render_card(ctx, tbl) render_tags(ctx, tbl) for _, section in ipairs(SECTIONS) do render_section(ctx, tbl, section) end render_information(ctx, tbl) if mw.title.getCurrentTitle().nsText ~= 'Template' then frame:callParserFunction('DISPLAYTITLE', ctx.display_name) end return tostring(root) .. render_categories(ctx) end return p
Summary:
Please note that all contributions to The Deadlock Wiki are considered to be released under the Creative Commons Attribution-NonCommercial-ShareAlike (see
Deadlock:Copyrights
for details). If you do not want your writing to be edited mercilessly and redistributed at will, then do not submit it here.
You are also promising us that you wrote this yourself, or copied it from a public domain or similar free resource.
Do not submit copyrighted work without permission!
Cancel
Editing help
(opens in new window)
Preview page with this template
Page included on this page:
Module:InfoboxHero/doc
(
view source
)
Navigation menu
Personal tools
Create account
Log in
associated-pages
Module
Discussion
English
Views
Read
Edit source
History
Tools
Search
Discord
Discord
Navigation
Main page
About us
Random page
Special pages
Recent changes
Health
2h ago
路
Toad64
Silver
4h ago
路
TNT CARDBOARD
Health
4h ago
路
Toad64
Viscous/Movement
11h ago
路
Justis
Show more...
Interface Admin
Common CSS
Vector CSS
Minerva CSS
Citizen CSS
Brown theme CSS
Gadgets
Gadgets definition
Editing
How to edit
Style guide
All guides
New files
Unresolved feedback
Tools
What links here
Related changes
Page information