Index: ps/trunk/binaries/data/config/default.cfg =================================================================== --- ps/trunk/binaries/data/config/default.cfg (revision 15676) +++ ps/trunk/binaries/data/config/default.cfg (revision 15677) @@ -1,376 +1,379 @@ ; Global Configuration Settings ; ; ************************************************************** ; * DO NOT EDIT THIS FILE if you want personal customisations: * ; * create a text file called "local.cfg" instead, and copy * ; * the lines from this file that you want to change. * ; * * ; * On Linux, create: * ; * $XDG_CONFIG_HOME/0ad/config/local.cfg * ; * (Note: $XDG_CONFIG_HOME defaults to ~/.config) * ; * * ; * On OS X, create: * ; * ~/Library/Application\ Support/0ad/config/local.cfg * ; * * ; * On Windows, create: * ; * %appdata%\0ad\config\local.cfg * ; * * ; ************************************************************** ; Enable/disable windowed mode by default. (Use Alt+Enter to toggle in the game.) windowed = false ; Show detailed tooltips (Unit stats) showdetailedtooltips = false ; Enable/disable the splashscreen splashscreendisable = false ; Splashscreen version (date of last modification). By default, 0 to force splashscreen to appear at first launch. splashscreenversion = 0 ; Pause the game on window focus loss (Only applicable to single player mode) pauseonfocusloss = true ; Default player name to use in multiplayer ; playername = "anonymous" ; Default server name or IP to use in multiplayer multiplayerserver = "127.0.0.1" ; Force a particular resolution. (If these are 0, the default is ; to keep the current desktop resolution in fullscreen mode or to ; use 1024x768 in windowed mode.) xres = 0 yres = 0 ; Force a non-standard bit depth (if 0 then use the current desktop bit depth) bpp = 0 ; System settings: ; if false, actors won't be rendered but anything entity will be. renderactors = true waterugly=false; Force usage of the fixed pipeline water. This is faster, but really, really ugly. waterfancyeffects = false waterrealdepth = true waterrefraction = true waterreflection = true shadowsonwater = false shadows = true shadowpcf = true vsync = false particles = true silhouettes = true showsky = false nos3tc = false noautomipmap = true novbo = false noframebufferobject = false ; Disable hardware cursors nohwcursor = false ; Linux only: Set the driconf force_s3tc_enable option at startup, ; for compressed texture support force_s3tc_enable = true ; Specify the render path. This can be one of: ; default Automatically select one of the below, depending on system capabilities ; fixed Only use OpenGL fixed function pipeline ; shader Use vertex/fragment shaders for transform and lighting where possible ; Using 'fixed' instead of 'default' may work around some graphics-related problems, ; but will reduce performance and features when a modern graphics card is available. renderpath = default ;;;;; EXPERIMENTAL ;;;;; ; Prefer GLSL shaders over ARB shaders (not recommended). REQUIRES gentangents=true. preferglsl = false ; Generate tangents for normal and parallax mapping. REQUIRES preferglsl=true. gentangents = false ; Experimental probably-non-working GPU skinning support; requires preferglsl; use at own risk gpuskinning = false ; Use smooth LOS interpolation; REQUIRES preferglsl=true. smoothlos = false ; Use screen-space postprocessing filters (HDR, bloom, DOF, etc). Incompatible with fixed renderpath. postproc = false ; Quality level of shader effects (set to 10 to display effects) materialmgr.quality = 0.0 ; Maximum distance to display parallax effect. Set to 0 to disable parallax. materialmgr.PARALLAX_DIST.max = 150 ; Maximum distance to display high quality parallax effect. materialmgr.PARALLAX_HQ_DIST.max = 75 ; Maximum distance to display very high quality parallax effect. Set to 30 to enable. materialmgr.PARALLAX_VHQ_DIST.max = 0 ;;;;;;;;;;;;;;;;;;;;;;;; ; Replace alpha-blending with alpha-testing, for performance experiments forcealphatest = false ; Opt-in online user reporting system userreport.url = "http://feedback.wildfiregames.com/report/upload/v1/" ; Colour of the sky (in "r g b" format) skycolor = "0 0 0" ; GENERAL PREFERENCES: sound.mastergain = 0.9 sound.musicgain = 0.2 sound.ambientgain = 0.6 sound.actiongain = 0.7 sound.uigain = 0.7 ; Camera control settings view.scroll.speed = 120.0 view.scroll.speed.modifier = 1.05 ; Multiplier for changing scroll speed view.rotate.x.speed = 1.2 view.rotate.x.min = 28.0 view.rotate.x.max = 60.0 view.rotate.x.default = 35.0 view.rotate.y.speed = 2.0 view.rotate.y.speed.wheel = 0.45 view.rotate.y.default = 0.0 view.rotate.speed.modifier = 1.05 ; Multiplier for changing rotation speed view.drag.speed = 0.5 view.zoom.speed = 256.0 view.zoom.speed.wheel = 32.0 view.zoom.min = 50.0 view.zoom.max = 200.0 view.zoom.default = 120.0 view.zoom.speed.modifier = 1.05 ; Multiplier for changing zoom speed view.pos.smoothness = 0.1 view.zoom.smoothness = 0.4 view.rotate.x.smoothness = 0.5 view.rotate.y.smoothness = 0.3 view.near = 2.0 ; Near plane distance view.far = 4096.0 ; Far plane distance view.fov = 45.0 ; Field of view (degrees), lower is narrow, higher is wide view.height.smoothness = 0.5 view.height.min = 16 ; How close do we have to be to the actual location in order to jump back to the previous one? camerajump.threshold = 40 ; HOTKEY MAPPINGS: ; Each one of the specified keys will trigger the action on the left ; for multiple-key combinations, separate keys with '+' and enclose the entire thing ; in doublequotes. ; See keys.txt for the list of key names. ; > SYSTEM SETTINGS hotkey.exit = "Alt+F4", "Ctrl+Break", "Super+Q" ; Exit to desktop hotkey.leave = Escape ; End current game or Exit hotkey.confirm = Return ; Confirm the current command. hotkey.pause = Pause ; Pause/unpause game hotkey.screenshot = F2 ; Take PNG screenshot hotkey.bigscreenshot = "Shift+F2" ; Take large BMP screenshot hotkey.togglefullscreen = "Alt+Return" ; Toggle fullscreen/windowed mode hotkey.screenshot.watermark = "Alt+K" ; Toggle product/company watermark for official screenshots hotkey.wireframe = "Alt+W" ; Toggle wireframe mode hotkey.silhouettes = "Alt+S" ; Toggle unit silhouettes hotkey.showsky = "Alt+Z" ; Toggle sky ; > CAMERA SETTINGS hotkey.camera.reset = "R" ; Reset camera rotation to default. hotkey.camera.follow = "F" ; Follow the first unit in the selection hotkey.camera.zoom.in = Plus, Equals, NumPlus ; Zoom camera in (continuous control) hotkey.camera.zoom.out = Minus, NumMinus ; Zoom camera out (continuous control) hotkey.camera.zoom.wheel.in = WheelUp ; Zoom camera in (stepped control) hotkey.camera.zoom.wheel.out = WheelDown ; Zoom camera out (stepped control) hotkey.camera.rotate.up = "Ctrl+UpArrow", "Ctrl+W" ; Rotate camera to look upwards hotkey.camera.rotate.down = "Ctrl+DownArrow", "Ctrl+S" ; Rotate camera to look downwards hotkey.camera.rotate.cw = "Ctrl+LeftArrow", "Ctrl+A", Q ; Rotate camera clockwise around terrain hotkey.camera.rotate.ccw = "Ctrl+RightArrow", "Ctrl+D", E ; Rotate camera anticlockwise around terrain hotkey.camera.rotate.wheel.cw = "Shift+WheelUp", MouseX1 ; Rotate camera clockwise around terrain (stepped control) hotkey.camera.rotate.wheel.ccw = "Shift+WheelDown", MouseX2 ; Rotate camera anticlockwise around terrain (stepped control) hotkey.camera.pan = MouseMiddle ; Enable scrolling by moving mouse hotkey.camera.left = A, LeftArrow ; Scroll or rotate left hotkey.camera.right = D, RightArrow ; Scroll or rotate right hotkey.camera.up = W, UpArrow ; Scroll or rotate up/forwards hotkey.camera.down = S, DownArrow ; Scroll or rotate down/backwards hotkey.camera.scroll.speed.increase = "Ctrl+Shift+S" ; Increase scroll speed hotkey.camera.scroll.speed.decrease = "Ctrl+Alt+S" ; Decrease scroll speed hotkey.camera.rotate.speed.increase = "Ctrl+Shift+R" ; Increase rotation speed hotkey.camera.rotate.speed.decrease = "Ctrl+Alt+R" ; Decrease rotation speed hotkey.camera.zoom.speed.increase = "Ctrl+Shift+Z" ; Increase zoom speed hotkey.camera.zoom.speed.decrease = "Ctrl+Alt+Z" ; Decrease zoom speed hotkey.camera.jump.1 = F5 ; Jump to position N hotkey.camera.jump.2 = F6 hotkey.camera.jump.3 = F7 hotkey.camera.jump.4 = F8 ;hotkey.camera.jump.5 = ;hotkey.camera.jump.6 = ;hotkey.camera.jump.7 = ;hotkey.camera.jump.8 = ;hotkey.camera.jump.9 = ;hotkey.camera.jump.10 = hotkey.camera.jump.set.1 = "Ctrl+F5" ; Set jump position N hotkey.camera.jump.set.2 = "Ctrl+F6" hotkey.camera.jump.set.3 = "Ctrl+F7" hotkey.camera.jump.set.4 = "Ctrl+F8" ;hotkey.camera.jump.set.5 = ;hotkey.camera.jump.set.6 = ;hotkey.camera.jump.set.7 = ;hotkey.camera.jump.set.8 = ;hotkey.camera.jump.set.9 = ;hotkey.camera.jump.set.10 = ; > CONSOLE SETTINGS hotkey.console.toggle = BackQuote, F9 ; Open/close console ; > CLIPBOARD CONTROLS hotkey.copy = "Ctrl+C" ; Copy to clipboard hotkey.paste = "Ctrl+V" ; Paste from clipboard hotkey.cut = "Ctrl+X" ; Cut selected text and copy to the clipboard ; > ENTITY SELECTION hotkey.selection.add = Shift ; Add units to selection hotkey.selection.milonly = Alt ; Add only military units to selection hotkey.selection.remove = Ctrl ; Remove units from selection hotkey.selection.cancel = Esc ; Un-select all units and cancel building placement hotkey.selection.idleworker = Period ; Select next idle worker hotkey.selection.idlewarrior = ForwardSlash ; Select next idle warrior hotkey.selection.offscreen = Alt ; Include offscreen units in selection hotkey.selection.group.select.0 = 0 hotkey.selection.group.save.0 = "Ctrl+0" hotkey.selection.group.add.0 = "Shift+0" hotkey.selection.group.select.1 = 1 hotkey.selection.group.save.1 = "Ctrl+1" hotkey.selection.group.add.1 = "Shift+1" hotkey.selection.group.select.2 = 2 hotkey.selection.group.save.2 = "Ctrl+2" hotkey.selection.group.add.2 = "Shift+2" hotkey.selection.group.select.3 = 3 hotkey.selection.group.save.3 = "Ctrl+3" hotkey.selection.group.add.3 = "Shift+3" hotkey.selection.group.select.4 = 4 hotkey.selection.group.save.4 = "Ctrl+4" hotkey.selection.group.add.4 = "Shift+4" hotkey.selection.group.select.5 = 5 hotkey.selection.group.save.5 = "Ctrl+5" hotkey.selection.group.add.5 = "Shift+5" hotkey.selection.group.select.6 = 6 hotkey.selection.group.save.6 = "Ctrl+6" hotkey.selection.group.add.6 = "Shift+6" hotkey.selection.group.select.7 = 7 hotkey.selection.group.save.7 = "Ctrl+7" hotkey.selection.group.add.7 = "Shift+7" hotkey.selection.group.select.8 = 8 hotkey.selection.group.save.8 = "Ctrl+8" hotkey.selection.group.add.8 = "Shift+8" hotkey.selection.group.select.9 = 9 hotkey.selection.group.save.9 = "Ctrl+9" hotkey.selection.group.add.9 = "Shift+9" ; > SESSION CONTROLS hotkey.session.kill = Delete ; Destroy selected units hotkey.session.stop = "H" ; Stop the current action hotkey.session.attack = "Ctrl+Alt" ; Modifier to force attack instead of another action hotkey.session.attackmove = Ctrl ; Modifier to attackmove when clicking on a point hotkey.session.attackmoveUnit = "Ctrl+Q" ; Modifier to attackmove targeting only units when clicking on a point hotkey.session.garrison = Ctrl ; Modifier to garrison when clicking on building hotkey.session.autorallypoint = Ctrl ; Modifier to set the rally point on the building itself hotkey.session.guard = "G" ; Modifier to escort/guard when clicking on unit/building hotkey.session.queue = Shift ; Modifier to queue unit orders instead of replacing hotkey.session.batchtrain = Shift ; Modifier to train units in batches hotkey.session.massbarter = Shift ; Modifier to barter bunch of resources hotkey.session.masstribute = Shift ; Modifier to tribute bunch of resources hotkey.session.fulltradeswap = Shift ; Modifier to put the desired trade resource to 100% hotkey.session.unloadtype = Shift ; Modifier to unload all units of type hotkey.session.deselectgroup = Ctrl ; Modifier to deselect units when clicking group icon, instead of selecting hotkey.session.rotate.cw = RightBracket ; Rotate building placement preview clockwise hotkey.session.rotate.ccw = LeftBracket ; Rotate building placement preview anticlockwise hotkey.timewarp.fastforward = Space ; If timewarp mode enabled, speed up the game hotkey.timewarp.rewind = Backspace ; If timewarp mode enabled, go back to earlier point in the game ; > UNIT TRAINING hotkey.session.queueunit.1 = "Z" ; add first unit type to queue hotkey.session.queueunit.2 = "X" ; add second unit type to queue hotkey.session.queueunit.3 = "C" ; add third unit type to queue hotkey.session.queueunit.4 = "V" ; add fourth unit type to queue hotkey.session.queueunit.5 = "B" ; add fivth unit type to queue hotkey.session.queueunit.6 = "N" ; add sixth unit type to queue hotkey.session.queueunit.7 = "M" ; add seventh unit type to queue hotkey.session.queueunit.8 = Comma ; add eighth unit type to queue ; > OVERLAY KEYS hotkey.fps.toggle = "Alt+F" ; Toggle frame counter hotkey.realtime.toggle = "Alt+T" ; Toggle current display of computer time hotkey.session.devcommands.toggle = "Alt+D" ; Toggle developer commands panel hotkey.session.gui.toggle = "Alt+G" ; Toggle visibility of session GUI hotkey.menu.toggle = "F10" ; Toggle in-game menu hotkey.timeelapsedcounter.toggle = "F12" ; Toggle time elapsed counter hotkey.session.showstatusbars = Tab ; Toggle display of status bars hotkey.session.highlightguarding = PgDn ; Toggle highlight of guarding units hotkey.session.highlightguarded = PgUp ; Toggle highlight of guarded units ; > HOTKEYS ONLY hotkey.chat = Return ; Toggle chat window hotkey.teamchat = "T" ; Toggle chat window in team chat mode ; > GUI TEXTBOX HOTKEYS hotkey.text.delete.left = "Ctrl+Backspace" ; Delete word to the left of cursor hotkey.text.delete.right = "Ctrl+Del" ; Delete word to the right of cursor hotkey.text.move.left = "Ctrl+LeftArrow" ; Move cursor to start of word to the left of cursor hotkey.text.move.right = "Ctrl+RightArrow" ; Move cursor to start of word to the right of cursor ; > PROFILER hotkey.profile.toggle = "F11" ; Enable/disable real-time profiler hotkey.profile.save = "Shift+F11" ; Save current profiler data to logs/profile.txt hotkey.profile2.enable = "F11" ; Enable HTTP/GPU modes for new profiler profiler2.http.autoenable = false ; Enable HTTP server output at startup (default off for security/performance) profiler2.script.enable = false ; Enable Javascript profiling. Needs to be set before startup and can't be changed later. (default off for performance) profiler2.gpu.autoenable = false ; Enable GPU timing at startup (default off for performance/compatibility) profiler2.gpu.arb.enable = true ; Allow GL_ARB_timer_query timing mode when available profiler2.gpu.ext.enable = true ; Allow GL_EXT_timer_query timing mode when available profiler2.gpu.intel.enable = true ; Allow GL_INTEL_performance_queries timing mode when available ; Developer options jsdebugger.enable = false ; Enable Javascript debugging. The Debugger is currently broken and can't be enabled until the SpiderMonkey upgrade is done and the debugger is updated for the new API. tinygettext.debug = false ; Enable Tinygettext debugging information. If true, Tinygettext prints error messages each time that a translation for an English string is not found. ; > QUICKSAVE hotkey.quicksave = "Shift+F5" hotkey.quickload = "Shift+F8" ; EXPERIMENTAL: joystick/gamepad settings joystick.enable = false joystick.deadzone = 8192 joystick.camera.pan.x = 0 joystick.camera.pan.y = 1 joystick.camera.rotate.x = 3 joystick.camera.rotate.y = 2 joystick.camera.zoom.in = 5 joystick.camera.zoom.out = 4 ; SESSION GUI SETTINGS gui.session.timeelapsedcounter = false ; Show the game duration in the top right corner gui.session.attacknotificationmessage = true ; Show attack notification messages gui.session.minimap.pingduration = 50.0 ; The duration for which an entity will be pinged after an attack notification gui.session.minimap.blinkduration = 1.7 ; The blink duration while pinging ; GENERAL GUI SETTINGS gui.cursorblinkrate = 0.5 ; Cursor blink rate in seconds (0.0 to disable blinking) ; Multiplayer lobby preferences lobby.server = "lobby.wildfiregames.com" ; Address of lobby server lobby.xpartamupp = "wfgbot17" ; Name of the server-side xmpp client that manage games lobby.chattimestamp = false ; Show time chat message was posted lobby.room = "arena17" ; Default MUC room to join lobby.history = 0 ; Number of past messages to display on join ; Overlay Preferences overlay.fps = "false" ; Show frames per second in top right corner overlay.realtime = "false" ; Show current system time in top right corner + +; MOD SETTINGS +;mod.enabledmods = "mod public" Index: ps/trunk/binaries/data/mods/mod/gui/modmod/modmod.js =================================================================== --- ps/trunk/binaries/data/mods/mod/gui/modmod/modmod.js (nonexistent) +++ ps/trunk/binaries/data/mods/mod/gui/modmod/modmod.js (revision 15677) @@ -0,0 +1,494 @@ +/* +Example contents of g_mods: +{ + "foldername1": { // this is the content of the json file in a specific mod + name: "unique_shortname", // eg "0ad", "rote" + version: "0.0.16", + label: "Nice Mod Name", // eg "0 A.D. - Empires Ascendant" + type: "content|functionality|mixed/mod-pack", + url: "http://wildfregames.com/", + description: "", + dependencies: [] // (name({<,<=,==,>=,>}version)?)+ + }, + "foldername2": { + name: "mod2", + label: "Mod 2", + version: "1.1", + type: "content|functionality|mixed/mod-pack", + url: "http://play0ad.wfg.com/", + description: "", + dependencies: [] + } +} +*/ + + +var g_mods = {}; // Contains all JSONs as explained in the structure above +var g_modsEnabled = []; // folder names +var g_modsAvailable = []; // folder names + +const g_sortByOptions = [translate("Name"), translate("Label"), translate("Folder"), translate("Version")]; +const SORT_BY_NAME = 0; +const SORT_BY_FOLDER = 1; +const SORT_BY_LABEL = 2; +const SORT_BY_VERSION = 3; + +var g_modTypes = [translate("Type: Any")]; + +/** + * Fetches the mod lists in JSON from the Engine. + * Initiates a first creation of the GUI lists. + * Enabled mods are read from the Configuration and checked if still available. + */ +function init() +{ + g_mods = Engine.GetAvailableMods(); + + g_modsEnabled = getExistingModsFromConfig(); + g_modsAvailable = Object.keys(g_mods).filter(function(i) { return g_modsEnabled.indexOf(i) === -1; }); + + Engine.GetGUIObjectByName("negateFilter").checked = false; + Engine.GetGUIObjectByName("modGenericFilter").caption = translate("Filter"); + Engine.GetGUIObjectByName("modTypeFilter").selected = 0; + + var sortBy = Engine.GetGUIObjectByName("sortBy"); + sortBy.list = g_sortByOptions; + sortBy.selected = SORT_BY_NAME; + + // sort ascending by default + Engine.GetGUIObjectByName("isOrderDescending").checked = false; + + generateModsLists(); + + Engine.GetGUIObjectByName("message").caption = translate("Message: Mods Loaded."); +} + +/** + * Recreating both the available and enabled mods lists. + */ +function generateModsLists() +{ + generateModsList('modsAvailableList', g_modsAvailable); + generateModsList('modsEnabledList', g_modsEnabled); +} + +function saveMods() +{ + // always sort mods before saving + sortMods(); + Engine.ConfigDB_CreateValue("user", "mod.enabledmods", ["mod"].concat(g_modsEnabled).join(" ")); + Engine.ConfigDB_WriteFile("user", "config/user.cfg"); +} + +function startMods() +{ + // always sort mods before starting + sortMods(); + Engine.SetMods(["mod"].concat(g_modsEnabled)); + Engine.RestartEngine(); +} + +function getExistingModsFromConfig() +{ + var existingMods = []; + + var mods = []; + var cfgMods = Engine.ConfigDB_GetValue("user", "mod.enabledmods"); + if (cfgMods.length > 0) + mods = cfgMods.split(/\s+/); + + mods.forEach(function(mod) { + if (mod in g_mods) + existingMods.push(mod); + }); + + return existingMods; +} + +/** + * (Re-)Generate List of all mods. + * @param listObjectName The GUI object's name (e.g. "modsEnabledList", "modsAvailableList") + */ +function generateModsList(listObjectName, mods) +{ + var sortBy = Engine.GetGUIObjectByName("sortBy"); + var orderDescending = Engine.GetGUIObjectByName("isOrderDescending"); + var isDescending = orderDescending && orderDescending.checked; + + // TODO: Sorting mods by dependencies would be nice + if (listObjectName != "modsEnabledList") + { + var idx = -1; + if (sortBy) + idx = sortBy.selected; + + switch (idx) + { + default: + warn("generateModsList: invalid index '"+idx+"'"); // fall through + // sort by unique name alphanumerically by default: + case -1: + case SORT_BY_NAME: + mods.sort(function(a, b) + { + var ret = compare(g_mods[a].name.toLowerCase(), g_mods[b].name.toLowerCase()); + return ret * (isDescending ? -1 : 1); + }); + break; + case SORT_BY_FOLDER: + mods.sort(function(a, b) + { + return compare(a.toLowerCase(), b.toLowerCase()) * (isDescending ? -1 : 1); + }); + break; + case SORT_BY_LABEL: + mods.sort(function(a, b) + { + var ret = compare(g_mods[a].label.toLowerCase(), g_mods[b].label.toLowerCase()); + return ret * (isDescending ? -1 : 1); + }); + break; + case SORT_BY_VERSION: + mods.sort(function(a, b) + { + // TODO reuse actual logic + var ret = compare(g_mods[a].version, g_mods[b].version); + return ret * (isDescending ? -1 : 1); + }); + break; + } + } + + var [names, folders, labels, types, urls, versions, dependencies] = [[],[],[],[],[],[],[]]; + mods.forEach(function(foldername) + { + var mod = g_mods[foldername]; + if (mod.type && g_modTypes.indexOf(mod.type) == -1) + g_modTypes.push(mod.type); + + if (filterMod(foldername)) + return; + + names.push(mod.name); + folders.push('[color="45 45 45"](' + foldername + ')[/color]'); + + labels.push(mod.label || ""); + types.push(mod.type || ""); + urls.push(mod.url || ""); + versions.push(mod.version || ""); + dependencies.push((mod.dependencies || []).join(" ")); + }); + + // Update the list + var obj = Engine.GetGUIObjectByName(listObjectName); + obj.list_name = names; + obj.list_modFolderName = folders; + obj.list_modLabel = labels; + obj.list_modType = types; + obj.list_modURL = urls; + obj.list_modVersion = versions; + obj.list_modDependencies = dependencies; + + obj.list = names; + + var modTypeFilter = Engine.GetGUIObjectByName("modTypeFilter"); + modTypeFilter.list = g_modTypes; +} + +function compare(a, b) +{ + return ( (a > b) ? 1 : (b > a) ? -1 : 0 ); +} + +function enableMod() +{ + var obj = Engine.GetGUIObjectByName("modsAvailableList"); + var pos = obj.selected; + if (pos === -1) + return; + + var mod = g_modsAvailable[pos]; + + // Move it to the other table + // check dependencies, warn about not satisfied dependencies and abort if so: + if (!areDependenciesMet(mod)) + return; + + g_modsEnabled.push(g_modsAvailable.splice(pos, 1)[0]); + + if (pos >= g_modsAvailable.length) + pos--; + obj.selected = pos; + + generateModsLists(); +} + +function disableMod() +{ + var obj = Engine.GetGUIObjectByName("modsEnabledList"); + var pos = obj.selected; + if (pos === -1) + return; + + var mod = g_modsEnabled[pos]; + + g_modsAvailable.push(g_modsEnabled.splice(pos, 1)[0]); + + // Remove mods that required the removed mod and cascade + // Sort them, so we know which ones can depend on the removed mod + // TODO: Find position where the removed mod would have fit (for now assume idx 0) + sortMods(); + for (var i = 0; i < g_modsEnabled.length; ++i) + { + if (!areDependenciesMet(g_modsEnabled[i])) + { + g_modsAvailable.push(g_modsEnabled.splice(i, 1)[0]); + --i; + } + } + + // select the last element even if more than 1 mod has been removed: + if (pos > g_modsEnabled.length - 1) + pos = g_modsEnabled.length - 1; + obj.selected = pos; + + generateModsLists(); +} + +function resetFilters() +{ + // Reset states of gui objects. + Engine.GetGUIObjectByName("modTypeFilter").selected = 0; + Engine.GetGUIObjectByName("negateFilter").checked = false; + Engine.GetGUIObjectByName("modGenericFilter").caption = ""; + + // NOTE: Calling generateModsLists() is not needed as the selection changes and that calls applyFilters() +} + +function applyFilters() +{ + Engine.GetGUIObjectByName("modsAvailableList").selected = -1; + Engine.GetGUIObjectByName("modsEnabledList").selected = -1; + generateModsLists(); +} + +/** + * Filter a mod based on the status of the filters. + * + * @param modFolder Mod to be tested. + * @return True if mod should not be displayed. + */ +function filterMod(modFolder) +{ + var mod = g_mods[modFolder]; + + var modTypeFilter = Engine.GetGUIObjectByName("modTypeFilter"); + var genericFilter = Engine.GetGUIObjectByName("modGenericFilter"); + var negateFilter = Engine.GetGUIObjectByName("negateFilter"); + + // TODO: and result of filters together (type && generic) + + // We assume index 0 means display all for any given filter. + if (modTypeFilter.selected > 0 + && (mod.type || "") != modTypeFilter.list[modTypeFilter.selected]) + return !negateFilter.checked; + + if (genericFilter && genericFilter.caption && genericFilter.caption != "" && genericFilter.caption != "Filter") + { + var t = genericFilter.caption; + if (modFolder.indexOf(t) === -1 + && mod.name.indexOf(t) === -1 + && mod.label.indexOf(t) === -1 + && (mod.type || "").indexOf(t) === -1 + && mod.url.indexOf(t) === -1 + && mod.version.indexOf(t) === -1 + && mod.description.indexOf(t) === -1 + && mod.dependencies.indexOf(t) === -1) + { + return !negateFilter.checked; + } + } + + return negateFilter.checked; +} + +function closePage() +{ + Engine.SwitchGuiPage("page_pregame.xml", {}); +} + + +/** + * Moves an item in the list @p objectName up or down depending on the value of @p up. + */ +function moveCurrItem(objectName, up) +{ + // reuse the check for null and if something is selected. + if (getCurrItemValue(objectName) == "") + return; + + var idx = Engine.GetGUIObjectByName(objectName).selected; + if (idx === -1) + return; + + var num = getNumItems(objectName); + var idx2 = idx + (up ? -1 : 1); + if (idx2 < 0 || idx2 >= num) + return; + + var tmp = g_modsEnabled[idx]; + g_modsEnabled[idx] = g_modsEnabled[idx2]; + g_modsEnabled[idx2] = tmp; + + // Selected object reached the new position. + Engine.GetGUIObjectByName(objectName).list = g_modsEnabled; + Engine.GetGUIObjectByName(objectName).selected = idx2; + generateModsList('modsEnabledList', g_modsEnabled); +} + +function areDependenciesMet(mod) +{ + var guiObject = Engine.GetGUIObjectByName("message"); + for each (var dependency in g_mods[mod].dependencies) + { + if (isDependencyMet(dependency)) + continue; + guiObject.caption = '[color="250 100 100"]' + translate(sprintf('Dependency not met: %(dep)s', { "dep": dependency })) +'[/color]'; + return false; + } + + guiObject.caption = '[color="100 250 100"]' + translate('All dependencies met') + '[/color]'; + + return true; +} + +/** + * @param dependency: Either id (unique modJson.name) and version or only the unique mod name. + * Concatenated by either "=", ">", "<", ">=", "<=". + */ +function isDependencyMet(dependency_idAndVersion, modsEnabled = null) +{ + if (!modsEnabled) + modsEnabled = g_modsEnabled; + + // Split on {=,<,<=,>,>=} and use the second part as the version number + // and whatever we split on as a way to handle that version. + var op = dependency_idAndVersion.match(/(<=|>=|<|>|=)/); + // Did the dependency contain a version number? + if (op) + { + op = op[0]; + var dependency_parts = dependency_idAndVersion.split(op); + var dependency_version = dependency_parts[1]; + var dependency_id = dependency_parts[0]; + } + else + var dependency_id = dependency_idAndVersion; + + // modsEnabled_key currently is the mod folder name. + for each (var modsEnabled_key in modsEnabled) + { + var modJson = g_mods[modsEnabled_key]; + if (modJson.name != dependency_id) + continue; + + // There could be another mod with a satisfying version + if (!op || versionSatisfied(modJson.version, op, dependency_version)) + return true; + } + return false; +} + +/** + * Returns true if @p version satisfies @p op (<,<=,=,>=,>) @p requirement. + * @note @p version and @p requirement are split on '.' and everything after + * '-' or '_' is ignored. Only numbers are supported. + * @note "5.3" < "5.3.0" + */ +function versionSatisfied(version, op, requirement) +{ + var reqList = requirement.split(/[-_]/)[0].split(/\./g); + var avList = version.split(/[-_]/)[0].split(/\./g); + + var eq = op.indexOf("=") !== -1; + var lt = op.indexOf("<") !== -1; + var gt = op.indexOf(">") !== -1; + if (!(eq || lt || gt)) + { + warn("No valid compare op"); + return false; + } + + var l = Math.min(reqList.length, avList.length); + for (var i = 0; i < l; ++i) + { + // TODO: Handle NaN + var diff = +avList[i] - +reqList[i]; + + // Early success + if (gt && diff > 0) + return true; + if (lt && diff < 0) + return true; + + // Early failure + if (gt && diff < 0) + return false; + if (lt && diff > 0) + return false; + if (eq && diff !== 0) + return false; + } + // common prefix matches + var ldiff = avList.length - reqList.length; + if (ldiff === 0) + return eq; + // NB: 2.3 != 2.3.0 + if (ldiff < 0) + return lt; + if (ldiff > 0) + return gt; + + // Can't be reached + error("version checking code broken"); + return false; +} + +function sortMods() +{ + // store the list of dependencies per mod, but strip the version numbers + var deps = {}; + for (var mod of g_modsEnabled) + { + deps[mod] = []; + if (!g_mods[mod].dependencies) + continue; + deps[mod] = g_mods[mod].dependencies.map(function(d) { return d.split(/(<=|>=|<|>|=)/)[0]; }); + } + var sortFunction = function(mod1, mod2) + { + var name1 = g_mods[mod1].name; + var name2 = g_mods[mod2].name; + if (deps[mod1].indexOf(name2) != -1) + return 1; + if (deps[mod2].indexOf(name1) != -1) + return -1; + return 0; + } + g_modsEnabled.sort(sortFunction); + generateModsList("modsEnabledList", g_modsEnabled); +} + +function showModDescription(listObjectName, mod_keys) +{ + var listObject = Engine.GetGUIObjectByName(listObjectName); + if (listObject.selected == -1) + var desc = '[color="255 100 100"]' + translate("No mod has been selected.") + '[/color]'; + else + { + var mod_key = mod_keys[listObject.selected]; + var desc = g_mods[mod_key].description; + } + + Engine.GetGUIObjectByName("globalModDescription").caption = desc; +} Index: ps/trunk/binaries/data/mods/mod/gui/modmod/modmod.xml =================================================================== --- ps/trunk/binaries/data/mods/mod/gui/modmod/modmod.xml (nonexistent) +++ ps/trunk/binaries/data/mods/mod/gui/modmod/modmod.xml (revision 15677) @@ -0,0 +1,192 @@ + + + +