Aslain's ModsSettings API (Aslain Menu) — the in-garage settings menu for World of Tanks mods, based on izeberg's modsSettingsApi. It keeps the same template format and API, and adds features for navigating long mod lists and a richer settings UI.
It ships under its own package gui.aslainMenu with its own aslainmenu.dat, so it runs independently of izeberg and never touches izeberg's modsettings.dat. Mods import gui.aslainMenu (with a fallback to izeberg) to use this menu and its new features. In the hangar's mods list the menu appears as "Mods settings+" (localized), with a "+" badge on its icon.
- Collapse / expand each mod, plus a Collapse All / Expand All toolbar button, to keep a long settings list tidy.
- A-Z quick-jump bar above the list: click a letter to jump to the first mod with that initial.
- Mod search: a search box in the window header filters the mod list by name as you type — a magnifier inside the field (it hides while you type), an "×" clear button and a "Search mods" placeholder. A hit counter left of the field shows
found X of Y mods. Press Ctrl+F to focus it; matching mods are revealed (auto-expanded), a search narrowing to a single mod briefly highlights it, and clearing the box restores your view. Clicking an A-Z letter while a search is active clears it first, then jumps. With only one mod installed the search hides entirely. - Per-mod reset: a rotate icon below each mod's on/off switch (shown while the mod is expanded) restores that mod's options and hotkeys to their defaults. Dimmed when nothing differs from the defaults (or the mod is off), bright once something changes. Clicking it asks to confirm — a small dialog with the mod's name and Reset / Cancel, drawn on top inside the menu (localized in all 25 languages; skippable via the
resetSkipConfirmuser setting). On confirm the control reset is live (Apply / OK keeps it, Cancel reverts); hotkeys reset immediately; the mod's on/off state is never touched. Works automatically for every mod (no API call needed). - Mods list sorted by the shown name (case-insensitive), ignoring a leading badge or symbol; a registered translation orders the mod under its translated name, and names not in the Latin alphabet (e.g. Cyrillic) sort after the Latin-named ones.
- Grouped sub-options via
templates.createControlsGroup(master, children): tie sub-controls to a master control so they are indented and greyed-out / disabled while the master is off, then enabled when it is on. Passindent=Falseto keep the children un-indented (so a wide group doesn't crowd the second column). - Value-conditional grey-out via
templates.enableWhen(control, masterVarName, value, condition='=='): grey a control unless another control's value satisfies a condition — equality / list membership by default, or a comparison ('!=','>','>=','<','<=', e.g. master>= 0; also available asCONDITION.*aliases). Updates live as the master changes — extends the master/child idea beyond a boolean On/Off. - Hide instead of grey via
templates.visibleWhen(...): the sibling ofenableWhenwith the same arguments, but it hides the control and reflows the mod (rows below close the gap) when the condition fails, instead of greying it in place — for options that make no sense in the current mode. - Multiple conditions (AND / OR) via
templates.enableWhenAll/enableWhenAny(plusvisibleWhenAll/visibleWhenAny): gate a control on more than one master at once —*Allrequires every condition (AND),*Anyrequires at least one (OR). Imagecomponent (loaded via a plainLoader+Bitmap): render an image in the menu body, refresh it live withupdateImage(...), collapse its slot withupdateImage(..., removeImage=True), or start it collapsed viacreateImage(..., collapsed=True). An optional built-inlabel(withlabelAlign) renders above the image, collapses together with it and can be live-updated too. Image sources are plain root-relative paths resolved from the WoT root.- Sprite-sheet animation in an
Imageslot:createImage(atlas={...})plays a looping animation from a single sheet the moment the menu opens, andupdateImageAtlas(...)switches it live — one image blitted frame-by-frame (copyPixelson a timer) instead of hundreds of frame files, light even at a highfps. - Checkbox + color picker on one row via
templates.createCheckboxColor(text, varName, value, color, ...): a tick box with its label on the left and a color swatch on the right, storing a{'enabled': <bool>, 'color': <hex>}pair under one name. A long label wraps onto extra lines under the checkbox without running under the swatch, and it slots intoenableWhen/visibleWhen/createControlsGrouplike any other control. - Personal color palette in the color picker: up to 48 editable preset slots shared by all mods (rows of 12, revealed as you fill them; starts empty). Left-click picks a color, left-click on an empty slot (+) starts defining it, right-click a filled slot opens a context menu (Edit preset / Copy hex code / Clear preset), DEL clears the slot being edited. While editing (gold frame) the slot follows every picked color live and Apply saves it without closing the picker. Saved in
aslainmenu.dat, so it survives restarts and reinstalls. - Color picker & color control QoL: the COLOR box live-previews the color under the cursor while it moves over the spectrum, and right-clicking the color swatch next to an option (main view) offers reset to the mod's default and copy hex code (bare value).
- Mod-defined picker palettes via
createColorChoice(..., presets=[...], presetsOnly=False)(and the same oncreateCheckboxColor): supply up to 24 colors shown as pick-only preset rows — when given, only your colors are offered (the user palette hides in that picker). WithpresetsOnly=Truethe picker reduces to just your swatches and Apply (no spectrum / sliders / hex input), its width auto-fitting the palette — "pick 1 of these 8". - Window QoL: the Apply button counts pending changes (
Apply (3), options differing from their saved state; reverting a value drops it out), jumping via the A-Z bar briefly highlights the target mod's frame and title, and the window remembers its scroll position within the game session. - Live in-menu updates:
registerLiveSettingsChange(...)(per-linkage; passfullsettings=Falseto receive only the changed keys instead of the full dict — the oldmode='changedOnly'form is deprecated) for uncommitted value changes;reloadModTemplate(...)re-renders one mod in place (e.g. instant language switch) without closing the window — only the controls that actually changed are rebuilt, the rest reused in place. - Hotkey label float:
templates.createHotkey(..., float='right')wraps a long hotkey label around the keys (CSS-floatstyle) — the keys float to the right and the label's overflow lines run full row width underneath them, instead of cramming into the narrow column. Default'none'keeps the original layout (plain-text labels only). - Plain-text labels: pass
useHTML=Falseto anycreate*control to render its label verbatim (so a literal</>/&shows instead of being parsed as HTML markup), ortemplates.escape(text)to escape just a fragment of an otherwise-HTML label. DefaultuseHTML=Truekeeps HTML labels (icons,<font>,<b>). - API version for feature-gating:
g_modsSettingsApi.getVersion()(string, e.g.'1.2.0') andgetVersionTuple()(e.g.(1, 2, 0)— compare this, not the string), plus the importableVERSION/VERSION_TUPLEconstants, so a mod can adapt to the running API version (if g_modsSettingsApi.getVersionTuple() >= (1, 2): ...). - Translate another mod's menu via
g_modsSettingsApi.registerModTranslation(linkage, mapping): supply a{original string: replacement string}dict and the API swaps those labels on the copy shown in the window — the target mod's stored template, saved values and code stay untouched (no settings reset). The API ships no translations itself; it is the hook an optional, separate localization mod uses to translate a mod whose menu is offered in only one language.
See CHANGELOG.md for the full list, and docs/LIVE_MENU_UPDATES.md for the live-update and image API.
Import Aslain's API first, with a fallback to izeberg, so your mod also runs where only the original is installed. Importing gui.aslainMenu first means it wins when both are present:
g_modsSettingsApi = None
templates = None
try:
from gui.aslainMenu import g_modsSettingsApi, templates
except ImportError:
pass
if g_modsSettingsApi is None:
try:
from gui.modsSettingsApi import g_modsSettingsApi, templates
except ImportError:
passThe extra features (image previews, instant language switch, controls grouping) work when gui.aslainMenu is the one loaded. Feature-detect new methods with hasattr(g_modsSettingsApi, ...) so your mod still runs on older API builds. See examples/ for full templates.
Compact copy-paste patterns; the sections below explain each in full. CONDITION is imported from gui.aslainMenu; feature-detect Aslain Menu-only calls with hasattr(templates, ...).
# Master checkbox with greyed-out children (returns a flat list to splice into a column)
column += templates.createControlsGroup(
templates.createCheckbox('Enable group', 'grp', True),
[templates.createSlider('Volume', 'vol', 5, 0, 10, 1)])
# Checkbox + color picker on one row; stored value is {'enabled': bool, 'color': hex}
templates.createCheckboxColor('Damage numbers', 'dmg', True, 'F23030')
# read in onModSettingsChanged: settings['dmg']['enabled'], settings['dmg']['color']
# Color picker with your mod's own palette (pick-only rows; hides the user palette)
templates.createColorChoice('Marker color', 'marker', 'FF0000',
presets=['FF0000', '00FF00', 'FF00FF'])
# ...and restricted to those colors only (compact picker: swatches + Apply, nothing else)
templates.createColorChoice('Team color', 'team', 'FF0000',
presets=['FF0000', '00FF00', 'FF00FF'], presetsOnly=True)
# Grey a control unless another control's value passes a test
templates.enableWhen(templates.createSlider('Fine tune', 'fine', 5, 1, 10, 1),
'level', 5, condition=CONDITION.GREATER_EQUAL)
# Hide + reflow instead of greying (shown only when mode == 1)
templates.visibleWhen(templates.createSlider('Advanced', 'adv', 5, 1, 10, 1), 'mode', 1)
# Dynamic spacer: blank vertical space that shows/hides with a master (no image needed)
templates.visibleWhen(templates.createEmpty(40), 'mode', 1)
# Gate on several masters at once: *All = AND, *Any = OR
conds = [{'varName': 'level', 'condition': CONDITION.GREATER_EQUAL, 'value': 5},
{'varName': 'expert', 'value': True}]
templates.enableWhenAll(templates.createCheckbox('Pro option', 'pro', False), conds)
# Plain-text label - literal <, >, & shown verbatim (or templates.escape(text) for a fragment)
templates.createCheckbox('Warn when HP < 25%', 'hp', True, useHTML=False)
# Long hotkey label wrapped around the keys
templates.createHotkey('A long description for this hotkey', 'key', [Keys.KEY_F2], float='right')The API stores each mod's template and the user's saved values between sessions. Add an integer settingsVersion to your template to control when a changed template is picked up:
def buildTemplate():
return {
'modDisplayName': 'My Mod',
'settingsVersion': 2,
'column1': [ ... ],
}Bump settingsVersion for any structural change — adding, removing or retyping a control, changing a dropdown / radio / step-slider's set of options, or changing a slider / stepper's min / max / interval. A structural change is only adopted — and that mod's saved values reset to the new defaults — when settingsVersion goes up; if you don't bump it, the stored template is kept and the change doesn't show. Cosmetic changes (an edited label or tooltip, a changed default value, or reordered controls) no longer need a bump — they refresh in place, with the user's saved values preserved. The rules:
The rule is the same whether or not your template carries a settingsVersion:
- Cosmetic change (structure unchanged — label/tooltip text, a default value, or the order of controls differs) → the new template is adopted so it refreshes, and the user's saved values are kept (values are keyed by control name, so reordering controls is safe). No reset, no warning. This holds for any mod, including one with no
settingsVersionand one adoptingsettingsVersionfor the first time. - Structural change (a control added, removed or retyped, a dropdown / radio / step-slider's options changed, or a slider / stepper's range changed) → it needs a bump. With
settingsVersionpresent and bumped, the new template is adopted and the mod's saved values reset to the new defaults. WithsettingsVersionpresent but not bumped, the stored template and the user's values are kept and the change is ignored (and a warning is logged). A mod with nosettingsVersionadopts a structural change and resets — the legacy behaviour. - First install → the template is adopted and its defaults recorded.
If you make a structural change but keep the same settingsVersion, the API logs a warning to python.log ([ModsSettings API] Template structure for '…' changed but settingsVersion was not bumped …), so a forgotten bump is easy to spot during development. Cosmetic changes never warn — they just refresh.
Tie sub-controls to a master control so they grey out while the master is off:
templates.createControlsGroup(
templates.createCheckbox('Enable feature', 'featureOn', True),
[
templates.createSlider('Intensity', 'intensity', 5, 1, 10, 1),
templates.createCheckbox('Verbose logging', 'verbose', False),
],
)Pass indent=False to keep the children at the master's own indent instead of inset, so a wide group doesn't crowd the second column.
createControlsGroup greys its children while a boolean master is Off. To grey a control unless a master holds a specific value - e.g. mutually-exclusive radio branches - use enableWhen, which updates live as the master changes:
column = [
templates.createRadioButtonGroup('Indicator', 'indicator', ['Static', 'Animated'], 0),
# editable only while 'indicator' is on Static (value 0)
templates.enableWhen(templates.createSlider('Hold time', 'holdTime', 5, 1, 15, 1),
'indicator', 0),
# editable only while 'indicator' is on Animated (value 1)
templates.enableWhen(templates.createDropdown('Animation', 'anim', ANIMS, 0),
'indicator', 1),
templates.createSlider('Count', 'count', 0, 0, 10, 1),
# comparison, not just equality: editable only while 'count' is >= 3
templates.enableWhen(templates.createCheckbox('Extra option', 'extra', False),
'count', 3, condition=CONDITION.GREATER_EQUAL),
]condition compares instead of matching: '==' (default), '!=', '>', '>=', '<', '<='. The CONDITION constants are aliases for these (CONDITION.EQUAL, CONDITION.GREATER_EQUAL, …) — import with from gui.aslainMenu import templates, CONDITION, or just pass the raw string. With '==', value may be a list to enable for several master values. indent=True indents the control like a sub-option. enableWhen is aslainMenu-only - guard it with hasattr(templates, 'enableWhen'); a control without the binding simply stays always-enabled, so older API builds degrade gracefully.
visibleWhen takes the same arguments as enableWhen, but when the condition fails it hides the control and reflows the mod (the rows below move up to close the gap) instead of greying it out in place. Use it for options that make no sense in the current mode:
column = [
templates.createDropdown('Mode', 'mode', ['Simple', 'Advanced'], 0),
# shown only while 'mode' is on Advanced (value 1); hidden otherwise
templates.visibleWhen(templates.createSlider('Advanced tuning', 'tuning', 5, 1, 10, 1),
'mode', 1, indent=True),
]Guard it with hasattr(templates, 'visibleWhen'). On older API builds it falls back to greying (the hide flag is ignored), so the control still gates correctly.
visibleWhen works on any component, including templates.createEmpty(height) — a gated empty is a dynamic spacer (blank vertical space that appears/disappears and reflows the column), with no image needed. createEmpty() defaults to a 20px gap; pass any height.
To gate a control on more than one master at once, use enableWhenAll (every condition must hold — logical AND) or enableWhenAny (at least one — logical OR). Each takes a list of condition dicts {'varName', 'value', 'condition'}; condition defaults to '==', and omitting 'value' tests that a boolean master is On:
conditions = [
{'varName': 'level', 'condition': CONDITION.GREATER_EQUAL, 'value': 5},
{'varName': 'expertMode', 'value': True},
]
# enabled only while level >= 5 AND expertMode is on
templates.enableWhenAll(templates.createSlider('Fine tune', 'fine', 5, 1, 10, 1), conditions)
# enabled while level >= 5 OR expertMode is on
templates.enableWhenAny(templates.createCheckbox('Shortcut', 'shortcut', False), conditions)visibleWhenAll / visibleWhenAny work the same way but hide the control (like visibleWhen) instead of greying it. Feature-detect with hasattr(templates, 'enableWhenAll'); older builds leave the control always-enabled.
createCheckboxColor puts a checkbox and a color picker on one row: the tick box and its label on the left, the color swatch on the right. It stores both halves under one varName as a dict, so a single setting carries an on/off flag and a color together:
templates.createCheckboxColor('Damage numbers', 'dccDmg', True, 'F23030', tooltip=tip)The stored value is {'enabled': <bool>, 'color': <hex str>} (color without the leading #), so read it like this:
def onModSettingsChanged(self, linkage, settings):
on = settings['dccDmg']['enabled']
color = settings['dccDmg']['color']A long label wraps onto extra lines under the checkbox without ever running under the swatch (the box, the first line and the tooltip icon stay on a real checkbox, so clicking toggles with the usual sound). It works as both a gated control and a gating master in enableWhen / visibleWhen (as a master it gates on its enabled flag), and inside createControlsGroup. Feature-detect with hasattr(templates, 'createCheckboxColor').
By default the color picker shows the user's own palette — up to 48 editable slots shared across all mods, saved between sessions, starting empty. A mod can bring its own palette instead:
# up to 24 colors, shown as one or two pick-only rows at the bottom of the picker;
# when a mod supplies presets, ONLY these are offered (the user palette hides here)
templates.createColorChoice('Marker color', 'marker', 'FF0000',
presets=['FF0000', '00FF00', 'FF00FF'])Add presetsOnly=True to restrict the choice to those colors: the picker reduces to just the preset swatches and the Apply button — no spectrum, no RGB sliders, no hex input — and its width auto-fits the palette:
# the user can pick exactly one of these three colors, nothing else
templates.createColorChoice('Team color', 'team', 'FF0000',
presets=['FF0000', '00FF00', 'FF00FF'], presetsOnly=True)Both parameters also exist on createCheckboxColor. presets=None (default) keeps the user palette; presets=[] shows no preset rows at all. Hex codes may come with or without the leading #, any case; invalid entries are dropped. Both are plain keyword arguments, so on older API builds gate them on the version:
if g_modsSettingsApi.getVersionTuple() >= (1, 5):
column2.append(templates.createColorChoice('Team color', 'team', 'FF0000',
presets=TEAM_COLORS, presetsOnly=True))
else:
column2.append(templates.createColorChoice('Team color', 'team', 'FF0000'))A long hotkey label wraps into the narrow column to the left of the keys by default. Pass float='right' to wrap it around the keys instead (CSS-float style) - the keys stay on the right and the overflow lines run the full row width underneath, so a long description reads naturally:
templates.createHotkey(
'A long description for this hotkey that reads better flowing under the keys',
'myKey', [Keys.KEY_BACKSPACE, KEY_CONTROL], float='right')float='none' (default) keeps the narrow-column wrap. Plain-text labels only (a label with HTML markup keeps the narrow layout). Older API builds ignore the arg, so it degrades gracefully.
Menu labels render as HTML, so a literal <, > or & in label text is parsed as markup — a checkbox labelled Warn when HP < 25%, for example, shows up as just Warn when HP , because everything from the < on is read as an (unclosed) tag. The cleanest fix is useHTML=False, accepted by every create* control, which renders that control's whole label as plain text:
templates.createCheckbox('Warn when HP < 25%', 'hpWarn', True, useHTML=False)
templates.createSlider('Spread < 0.10', 'spread', 5, 0, 20, 1, useHTML=False)Default useHTML=True keeps full HTML support (icons, <font color=...>, <b>). It is a keyword argument, so older API builds reject it — gate it on the API version (or wrap the call in try / except TypeError):
kw = {'useHTML': False} if g_modsSettingsApi.getVersionTuple() >= (1, 3) else {}
templates.createCheckbox('Warn when HP < 25%', 'hpWarn', True, **kw)To escape just a fragment of an otherwise-HTML label, templates.escape(text) returns the text with &, <, > escaped (feature-detect with hasattr(templates, 'escape')):
templates.createLabel(templates.escape(userName) + " <font color='#80D639'>online</font>")Both only affect how literal text displays — neither has anything to do with gating. The condition in enableWhen(control, varName, value, condition='<=') is a separate feature.
The import above lets your mod run on either menu, but the features below exist only on gui.aslainMenu. Calling them on plain izeberg raises an error (and can blank the whole settings window), so feature-detect each one with hasattr(...) and skip it when missing. A skipped control is simply left out of the template - no error, and no empty slot: the layout just closes up.
column = [
templates.createCheckbox('Sixth Sense enabled', 'enabled', True),
templates.createDropdown('Icon', 'icon', ICON_NAMES, 0),
]
# createImage is aslainMenu-only -> add the preview only when available,
# otherwise it is simply omitted (no error, no empty gap).
if hasattr(templates, 'createImage'):
column.append(templates.createImage('mods/configs/mymod/icons/%s.png' % ICON_NAMES[0],
containerHeight=96, align='center',
label='Preview', labelAlign='center'))
# createControlsGroup (grey-out sub-options under a master) is aslainMenu-only too.
master = templates.createCheckbox('Enable extras', 'extrasOn', True)
children = [templates.createSlider('Glow size', 'glow', 24, 8, 64, 1)]
if hasattr(templates, 'createControlsGroup'):
column += templates.createControlsGroup(master, children) # grouped + greyed when off
else:
column += [master] + children # flat fallback on izebergGuard the singleton's new methods the same way, e.g. the live image preview:
if hasattr(g_modsSettingsApi, 'updateImage'):
g_modsSettingsApi.registerLiveSettingsChange(MOD_LINKAGE, onLiveChange)
# inside onLiveChange call g_modsSettingsApi.updateImage(MOD_LINKAGE, 'icon', newSource)To gate on a feature level, read the running API version (guard the call — older builds don't provide it). Compare the tuple form, never the string:
getVer = getattr(g_modsSettingsApi, 'getVersionTuple', None)
apiVersion = getVer() if getVer else (0, 0, 0)
if apiVersion >= (1, 2):
g_modsSettingsApi.registerLiveSettingsChange(MOD_LINKAGE, onLiveChange, fullsettings=False)aslainMenu-only - guard before use:
templates:createImage(incl.atlas=),createControlsGroup,createCheckboxColor,enableWhen/visibleWhen,enableWhenAll/enableWhenAny/visibleWhenAll/visibleWhenAny,escape, thepresets/presetsOnlyarguments ofcreateColorChoice/createCheckboxColor(version-gate these two:getVersionTuple() >= (1, 5))g_modsSettingsApi:updateImage,updateImageAtlas,registerLiveSettingsChange/unregisterLiveSettingsChange,notifyLiveSettingsChange,reloadModTemplate,setModCollapsed,getVersion/getVersionTuple,registerModTranslation
- ModsList API by poliroid, opens the settings window.
net.openwg.gameface, renders the menu UI.
See build.py and build.json; build requirements are in requirements_build.txt.
- Original ModsSettings API by izeberg (Renat Iliev), izeberg/modssettingsapi. All original credit and copyright remain with the author.
- ModsList API dependency by poliroid, wot-public-mods/mods-list.
- Aslain's ModsSettings API is maintained by Aslain (aslain.com). It adds the features listed above on top of izeberg's API, and does not remove or replace the original work.
See AUTHORS.md.
Issues and pull requests welcome, especially new settings UI components and compatibility fixes.






