Index: ps/trunk/binaries/data/config/default.cfg =================================================================== --- ps/trunk/binaries/data/config/default.cfg (revision 21758) +++ ps/trunk/binaries/data/config/default.cfg (revision 21759) @@ -1,494 +1,502 @@ ; 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. * ; * * ; * If a setting is part of a section (for instance [hotkey]) * ; * you need to append the section name at the beginning of * ; * your custom line (for instance you need to write * ; * "hotkey.pause = Space" if you want to change the pausing * ; * hotkey to the spacebar). * ; * * ; * 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 ; Pause the game on window focus loss (Only applicable to single player mode) pauseonfocusloss = true ; Persist settings after leaving the game setup screen persistmatchsettings = 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 ; Preferred display (for multidisplay setups, only works with SDL 2.0) display = 0 ; Emulate right-click with Ctrl+Click on Mac mice macmouse = false ; System settings: ; if false, actors won't be rendered but anything entity will be. renderactors = true watereffects=true ; When disabled, 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 shadowquality = 0 ; Shadow map resolution. (-2 - Very Low, -1 - Low, 0 - Medium, 1 - High, 2 - Very High) ; High values can crash the game when using a graphics card with low memory! shadowpcf = true vsync = false particles = true fog = true silhouettes = true showsky = true 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. Allows fancier graphical effects. preferglsl = false ; Experimental probably-non-working GPU skinning support; requires preferglsl; use at own risk gpuskinning = false ; Use smooth LOS interpolation 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 all effects) materialmgr.quality = 2.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 ; Color of the sky (in "r g b" format) skycolor = "0 0 0" [adaptivefps] session = 60 ; Throttle FPS in running games (prevents 100% CPU workload). menu = 30 ; Throttle FPS in menus only. [hotkey] ; Each one of the specified keys will trigger the action on the left ; for multiple-key combinations, separate keys with '+'. ; See keys.txt for the list of key names. ; > SYSTEM SETTINGS exit = "Ctrl+Break", "Super+Q" ; Exit to desktop cancel = Escape ; Close or cancel the current dialog box/popup leave = Escape ; End current game or Exit confirm = Return ; Confirm the current command pause = Pause ; Pause/unpause game screenshot = F2 ; Take PNG screenshot bigscreenshot = "Shift+F2" ; Take large BMP screenshot togglefullscreen = "Alt+Return" ; Toggle fullscreen/windowed mode screenshot.watermark = "Alt+K" ; Toggle product/company watermark for official screenshots wireframe = "Alt+Shift+W" ; Toggle wireframe mode silhouettes = "Alt+Shift+S" ; Toggle unit silhouettes showsky = "Alt+Z" ; Toggle sky ; > DIALOG HOTKEYS summary = "Ctrl+Tab" ; Toggle in-game summary lobby = "Alt+L" ; Show the multiplayer lobby in a dialog window. structree = "Alt+Shift+T" ; Show structure tree civinfo = "Alt+Shift+H" ; Show civilization info ; > CLIPBOARD CONTROLS copy = "Ctrl+C" ; Copy to clipboard paste = "Ctrl+V" ; Paste from clipboard cut = "Ctrl+X" ; Cut selected text and copy to the clipboard ; > CONSOLE SETTINGS console.toggle = BackQuote, F9 ; Open/close console ; > OVERLAY KEYS fps.toggle = "Alt+F" ; Toggle frame counter realtime.toggle = "Alt+T" ; Toggle current display of computer time session.devcommands.toggle = "Alt+D" ; Toggle developer commands panel timeelapsedcounter.toggle = "F12" ; Toggle time elapsed counter session.showstatusbars = Tab ; Toggle display of status bars session.highlightguarding = PgDn ; Toggle highlight of guarding units session.highlightguarded = PgUp ; Toggle highlight of guarded units session.toggleattackrange = "Alt+C" ; Toggle display of attack range overlays of selected defensive structures session.toggleaurasrange = "Alt+V" ; Toggle display of aura range overlays of selected units and structures session.togglehealrange = "Alt+B" ; Toggle display of heal range overlays of selected units session.diplomacycolors = "Alt+X" ; Toggle diplomacy colors ; > HOTKEYS ONLY chat = Return ; Toggle chat window teamchat = "T" ; Toggle chat window in team chat mode privatechat = "L" ; Toggle chat window and select the previous private chat partner ; > QUICKSAVE quicksave = "Shift+F5" quickload = "Shift+F8" [hotkey.camera] reset = "R" ; Reset camera rotation to default. follow = "F" ; Follow the first unit in the selection rallypointfocus = unused ; Focus the camera on the rally point of the selected building zoom.in = Plus, Equals, NumPlus ; Zoom camera in (continuous control) zoom.out = Minus, NumMinus ; Zoom camera out (continuous control) zoom.wheel.in = WheelUp ; Zoom camera in (stepped control) zoom.wheel.out = WheelDown ; Zoom camera out (stepped control) rotate.up = "Ctrl+UpArrow", "Ctrl+W" ; Rotate camera to look upwards rotate.down = "Ctrl+DownArrow", "Ctrl+S" ; Rotate camera to look downwards rotate.cw = "Ctrl+LeftArrow", "Ctrl+A", Q ; Rotate camera clockwise around terrain rotate.ccw = "Ctrl+RightArrow", "Ctrl+D", E ; Rotate camera anticlockwise around terrain rotate.wheel.cw = "Shift+WheelUp", MouseX1 ; Rotate camera clockwise around terrain (stepped control) rotate.wheel.ccw = "Shift+WheelDown", MouseX2 ; Rotate camera anticlockwise around terrain (stepped control) pan = MouseMiddle ; Enable scrolling by moving mouse left = A, LeftArrow ; Scroll or rotate left right = D, RightArrow ; Scroll or rotate right up = W, UpArrow ; Scroll or rotate up/forwards down = S, DownArrow ; Scroll or rotate down/backwards scroll.speed.increase = "Ctrl+Shift+S" ; Increase scroll speed scroll.speed.decrease = "Ctrl+Alt+S" ; Decrease scroll speed rotate.speed.increase = "Ctrl+Shift+R" ; Increase rotation speed rotate.speed.decrease = "Ctrl+Alt+R" ; Decrease rotation speed zoom.speed.increase = "Ctrl+Shift+Z" ; Increase zoom speed zoom.speed.decrease = "Ctrl+Alt+Z" ; Decrease zoom speed [hotkey.camera.jump] 1 = F5 ; Jump to position N 2 = F6 3 = F7 4 = F8 ;5 = ;6 = ;7 = ;8 = ;9 = ;10 = [hotkey.camera.jump.set] 1 = "Ctrl+F5" ; Set jump position N 2 = "Ctrl+F6" 3 = "Ctrl+F7" 4 = "Ctrl+F8" ;5 = ;6 = ;7 = ;8 = ;9 = ;10 = [hotkey.profile] toggle = "F11" ; Enable/disable real-time profiler save = "Shift+F11" ; Save current profiler data to logs/profile.txt [hotkey.profile2] toggle = "Ctrl+F11" ; Enable/disable HTTP/GPU modes for new profiler [hotkey.selection] add = Shift ; Add units to selection militaryonly = Alt ; Add only military units to the selection nonmilitaryonly = "Alt+Y" ; Add only non-military units to the selection idleonly = "I" ; Select only idle units woundedonly = "O" ; Select only wounded units remove = Ctrl ; Remove units from selection cancel = Esc ; Un-select all units and cancel building placement idleworker = Period ; Select next idle worker idlewarrior = ForwardSlash ; Select next idle warrior idleunit = BackSlash ; Select next idle unit offscreen = Alt ; Include offscreen units in selection [hotkey.selection.group.add] 0 = "Shift+0" 1 = "Shift+1" 2 = "Shift+2" 3 = "Shift+3" 4 = "Shift+4" 5 = "Shift+5" 6 = "Shift+6" 7 = "Shift+7" 8 = "Shift+8" 9 = "Shift+9" [hotkey.selection.group.save] 0 = "Ctrl+0" 1 = "Ctrl+1" 2 = "Ctrl+2" 3 = "Ctrl+3" 4 = "Ctrl+4" 5 = "Ctrl+5" 6 = "Ctrl+6" 7 = "Ctrl+7" 8 = "Ctrl+8" 9 = "Ctrl+9" [hotkey.selection.group.select] 0 = 0 1 = 1 2 = 2 3 = 3 4 = 4 5 = 5 6 = 6 7 = 7 8 = 8 9 = 9 [hotkey.session] kill = Delete ; Destroy selected units stop = "H" ; Stop the current action backtowork = "Y" ; The unit will go back to work unload = "U" ; Unload garrisoned units when a building/mechanical unit is selected move = unused ; Modifier to move to a point instead of another action (e.g. gather) attack = Ctrl ; Modifier to attack instead of another action (e.g. capture) attackmove = Ctrl ; Modifier to attackmove when clicking on a point attackmoveUnit = "Ctrl+Q" ; Modifier to attackmove targeting only units when clicking on a point (should contain the attackmove keys) garrison = Ctrl ; Modifier to garrison when clicking on building autorallypoint = Ctrl ; Modifier to set the rally point on the building itself guard = "G" ; Modifier to escort/guard when clicking on unit/building patrol = "P" ; Modifier to patrol a unit repair = "J" ; Modifier to repair when clicking on building/mechanical unit queue = Shift ; Modifier to queue unit orders instead of replacing orderone = Alt ; Modifier to order only one entity in selection. batchtrain = Shift ; Modifier to train units in batches massbarter = Shift ; Modifier to barter bunch of resources masstribute = Shift ; Modifier to tribute bunch of resources noconfirmation = Shift ; Do not ask confirmation when deleting a building/unit fulltradeswap = Shift ; Modifier to put the desired trade resource to 100% unloadtype = Shift ; Modifier to unload all units of type deselectgroup = Ctrl ; Modifier to deselect units when clicking group icon, instead of selecting rotate.cw = RightBracket ; Rotate building placement preview clockwise rotate.ccw = LeftBracket ; Rotate building placement preview anticlockwise [hotkey.session.gui] toggle = "Alt+G" ; Toggle visibility of session GUI menu.toggle = "F10" ; Toggle in-game menu barter.toggle = "Ctrl+B" ; Toggle in-game barter/trade page tutorial.toggle = "Ctrl+P" ; Toggle in-game tutorial panel [hotkey.session.savedgames] delete = Delete ; Delete the selected saved game asking confirmation noconfirmation = Shift ; Do not ask confirmation when deleting a game [hotkey.session.queueunit] ; > UNIT TRAINING 1 = "Z" ; add first unit type to queue 2 = "X" ; add second unit type to queue 3 = "C" ; add third unit type to queue 4 = "V" ; add fourth unit type to queue 5 = "B" ; add fivth unit type to queue 6 = "N" ; add sixth unit type to queue 7 = "M" ; add seventh unit type to queue 8 = Comma ; add eighth unit type to queue [hotkey.session.timewarp] fastforward = Space ; If timewarp mode enabled, speed up the game rewind = Backspace ; If timewarp mode enabled, go back to earlier point in the game [hotkey.tab] next = "Tab", "Alt+S" ; Show the next tab prev = "Shift+Tab", "Alt+W" ; Show the previous tab [hotkey.text] ; > GUI TEXTBOX HOTKEYS delete.left = "Ctrl+Backspace" ; Delete word to the left of cursor delete.right = "Ctrl+Del" ; Delete word to the right of cursor move.left = "Ctrl+LeftArrow" ; Move cursor to start of word to the left of cursor move.right = "Ctrl+RightArrow" ; Move cursor to start of word to the right of cursor [gui] cursorblinkrate = 0.5 ; Cursor blink rate in seconds (0.0 to disable blinking) scale = 1.0 ; GUI scaling factor, for improved compatibility with 4K displays [gui.gamesetup] enabletips = true ; Enable/Disable tips during gamesetup (for newcomers) assignplayers = everyone ; Whether to assign joining clients to free playerslots. Possible values: everyone, buddies, disabled. aidifficulty = 3 ; Difficulty level, from 0 (easiest) to 5 (hardest) aibehavior = "random" ; Default behavior of the AI (random, balanced, aggressive or defensive) settingsslide = true ; Enable/Disable settings panel slide [gui.session] camerajump.threshold = 40 ; How close do we have to be to the actual location in order to jump back to the previous one? timeelapsedcounter = false ; Show the game duration in the top right corner ceasefirecounter = false ; Show the remaining ceasefire time in the top right corner batchtrainingsize = 5 ; Number of units to be trained per batch by default (when pressing the hotkey) scrollbatchratio = 1 ; Number of times you have to scroll to increase/decrease the batchsize by 1 woundedunithotkeythreshold = 33 ; The wounded unit hotkey considers the selected units as wounded if their health percentage falls below this number attackrange = true ; Display attack range overlays of selected defensive structures aurasrange = true ; Display aura range overlays of selected units and structures healrange = true ; Display heal range overlays of selected units rankabovestatusbar = true ; Show rank icons above status bars respoptooltipsort = 0 ; Sorting players in the resources and population tooltip by value (0 - no sort, -1 - ascending, 1 - descending) [gui.session.minimap] blinkduration = 1.7 ; The blink duration while pinging pingduration = 50.0 ; The duration for which an entity will be pinged after an attack notification [gui.session.notifications] attack = true ; Show a chat notification if you are attacked by another player tribute = true ; Show a chat notification if an ally tributes resources to another team member if teams are locked, and all tributes in observer mode barter = true ; Show a chat notification to observers when a player bartered resources phase = completed ; Show a chat notification if you or an ally have started, aborted or completed a new phase, and phases of all players in observer mode. Possible values: none, completed, all. [gui.splashscreen] enable = true ; Enable/disable the splashscreen version = 0 ; Splashscreen version (date of last modification). By default, 0 to force splashscreen to appear at first launch [gui.session.diplomacycolors] self = "21 55 149" ; Color of your units when diplomacy colors are enabled ally = "86 180 31" ; Color of allies when diplomacy colors are enabled neutral = "231 200 5" ; Color of neutral players when diplomacy colors are enabled enemy = "150 20 20" ; Color of enemies when diplomacy colors are enabled [joystick] ; EXPERIMENTAL: joystick/gamepad settings enable = false deadzone = 8192 [joystick.camera] pan.x = 0 pan.y = 1 rotate.x = 3 rotate.y = 2 zoom.in = 5 zoom.out = 4 [chat] timestamp = true ; Show at which time chat messages have been sent [chat.session] extended = true ; Whether to display the chat history [lobby] history = 0 ; Number of past messages to display on join room = "arena23" ; Default MUC room to join server = "lobby.wildfiregames.com" ; Address of lobby server xpartamupp = "wfgbot23" ; Name of the server-side XMPP-account that manage games echelon = "echelon23" ; Name of the server-side XMPP-account that manages ratings buddies = "," ; Comma separated list of playernames that the current user has marked as buddies rememberpassword = true ; Whether to store the encrypted password in the user config secureauth = true ; Secure Lobby Authentication: This prevents the impersonation of other players. The lobby server confirms the identity of the player before they join. [lobby.columns] gamerating = false ; Show the average rating of the participating players in a column of the gamelist [lobby.stun] enabled = true ; The STUN protocol allows hosting games without configuring the firewall and router. ; If STUN is disabled, the game relies on direct connection, UPnP and port forwarding. server = "lobby.wildfiregames.com" ; Address of the STUN server. port = 3478 ; Port of the STUN server. delay = 200 ; Duration in milliseconds that is waited between STUN messages. ; Smaller numbers speed up joins but also become less stable. [mod] enabledmods = "mod public" +[modio] +public_key = "RWQBhIRg+dOifTWlwgYHe8RfD8bqoDh1cCvygboAl3GOUKiCo0NlF4fw" ; Public key corresponding to the private key valid mods are signed with + +[modio.v1] +baseurl = "https://api.mod.io/v1" +api_key = "23df258a71711ea6e4b50893acc1ba55" +name_id = "0ad" + [network] duplicateplayernames = false ; Rename joining player to "User (2)" if "User" is already connected, otherwise prohibit join. lateobservers = everyone ; Allow observers to join the game after it started. Possible values: everyone, buddies, disabled. observerlimit = 8 ; Prevent further observer joins in running games if this limit is reached [overlay] fps = "false" ; Show frames per second in top right corner realtime = "false" ; Show current system time in top right corner netwarnings = "true" ; Show warnings if the network connection is bad [profiler2] autoenable = false ; Enable HTTP server output at startup (default off for security/performance) gpu.arb.enable = true ; Allow GL_ARB_timer_query timing mode when available gpu.ext.enable = true ; Allow GL_EXT_timer_query timing mode when available gpu.intel.enable = true ; Allow GL_INTEL_performance_queries timing mode when available [sound] mastergain = 0.9 musicgain = 0.2 ambientgain = 0.6 actiongain = 0.7 uigain = 0.7 [sound.notify] nick = true ; Play a sound when someone mentions your name in the lobby or game [tinygettext] debug = false ; Print error messages each time a translation for an English string is not found. [userreport] ; Opt-in online user reporting system url = "http://feedback.wildfiregames.com/report/upload/v1/" [view] ; Camera control settings scroll.speed = 120.0 scroll.speed.modifier = 1.05 ; Multiplier for changing scroll speed rotate.x.speed = 1.2 rotate.x.min = 28.0 rotate.x.max = 60.0 rotate.x.default = 35.0 rotate.y.speed = 2.0 rotate.y.speed.wheel = 0.45 rotate.y.default = 0.0 rotate.speed.modifier = 1.05 ; Multiplier for changing rotation speed drag.speed = 0.5 zoom.speed = 256.0 zoom.speed.wheel = 32.0 zoom.min = 50.0 zoom.max = 200.0 zoom.default = 120.0 zoom.speed.modifier = 1.05 ; Multiplier for changing zoom speed pos.smoothness = 0.1 zoom.smoothness = 0.4 rotate.x.smoothness = 0.5 rotate.y.smoothness = 0.3 near = 2.0 ; Near plane distance far = 4096.0 ; Far plane distance fov = 45.0 ; Field of view (degrees), lower is narrow, higher is wide height.smoothness = 0.5 height.min = 16 Index: ps/trunk/binaries/data/mods/mod/gui/common/l10n.js =================================================================== --- ps/trunk/binaries/data/mods/mod/gui/common/l10n.js (revision 21758) +++ ps/trunk/binaries/data/mods/mod/gui/common/l10n.js (revision 21759) @@ -1,197 +1,232 @@ /** + * @param filesize - In bytes. + * @return Object with quantized filesize and suitable unit of size. + */ +function filesizeToObj(filesize) +{ + // We are unlikely to download files measured in units greater than GiB. + let units = [ + translateWithContext("filesize unit", "B"), + translateWithContext("filesize unit", "KiB"), + translateWithContext("filesize unit", "MiB"), + translateWithContext("filesize unit", "GiB") + ]; + + let i = 0; + while (i < units.length - 1) + { + if (filesize < 1024) + break; + filesize /= 1024; + ++i; + } + + return { + "filesize": filesize.toFixed(i == 0 ? 0 : 1), + "unit": units[i] + }; +} + +function filesizeToString(filesize) +{ + // Translation: For example: 123.4 KiB + return sprintf(translate("%(filesize)s %(unit)s"), filesizeToObj(filesize)); +} + +/** * Convert time in milliseconds to [HH:]mm:ss string representation. * * @param time Time period in milliseconds (integer) * @return String representing time period */ function timeToString(time) { return Engine.FormatMillisecondsIntoDateStringGMT(time, time < 1000 * 60 * 60 ? // Translation: Time-format string. See http://userguide.icu-project.org/formatparse/datetime for a guide to the meaning of the letters. translate("mm:ss") : translate("HH:mm:ss")); } /** * These functions rely on the JS cache where possible and * should be prefered over the Engine.Translate ones to optimize the performance. */ var g_Translations = {}; var g_PluralTranslations = {}; var g_TranslationsWithContext = {}; var g_PluralTranslationsWithContext = {}; function isTranslatableString(message) { return typeof message == "string" && !!message.trim(); } /** * Translates the specified English message into the current language. */ function translate(message) { if (!g_Translations[message]) g_Translations[message] = Engine.Translate(message); return g_Translations[message]; } /** * Translates the specified English message into the current language for the specified number. */ function translatePlural(singularMessage, pluralMessage, number) { if (!g_PluralTranslations[singularMessage]) g_PluralTranslations[singularMessage] = {}; if (!g_PluralTranslations[singularMessage][number]) g_PluralTranslations[singularMessage][number] = Engine.TranslatePlural(singularMessage, pluralMessage, number); return g_PluralTranslations[singularMessage][number]; } /** * Translates the specified English message into the current language for the specified context. */ function translateWithContext(context, message) { if (!g_TranslationsWithContext[context]) g_TranslationsWithContext[context] = {}; if (!g_TranslationsWithContext[context][message]) g_TranslationsWithContext[context][message] = Engine.TranslateWithContext(context, message); return g_TranslationsWithContext[context][message]; } /** * Translates the specified English message into the current language for the specified context and number. */ function translatePluralWithContext(context, singularMessage, pluralMessage, number) { if (!g_PluralTranslationsWithContext[context]) g_PluralTranslationsWithContext[context] = {}; if (!g_PluralTranslationsWithContext[context][singularMessage]) g_PluralTranslationsWithContext[context][singularMessage] = {}; if (!g_PluralTranslationsWithContext[context][singularMessage][number]) g_PluralTranslationsWithContext[context][singularMessage][number] = Engine.TranslatePluralWithContext(context, singularMessage, pluralMessage, number); return g_PluralTranslationsWithContext[context][singularMessage][number]; } /** * The input object should contain either of the following properties: * * • A ‘message’ property that contains a message to translate. * * • A ‘list’ property that contains a list of messages to translate as a * comma-separated list of translated. * * Optionally, the input object may contain a ‘context’ property. In that case, * the value of this property is used as translation context, that is, passed to * the translateWithContext(context, message) function. */ function translateMessageObject(object) { let trans = translate; if (object.context) trans = msg => translateWithContext(object.context, msg); if (object.message) object = trans(object.message); else if (object.list) object = object.list.map(trans).join(translateWithContext("enumeration", ", ")); return object; } /** * Translates any string value in the specified JavaScript object * that is associated with a key included in the specified keys array. * * it accepts an object in the form of * * { * translatedString1: "my first message", * unTranslatedString1: "some English string", * ignoredObject: { * translatedString2: "my second message", * unTranslatedString2: "some English string" * }, * translatedObject1: { * message: "my third singular message", * context: "message context", * }, * translatedObject2: { * list: ["list", "of", "strings"], * context: "message context", * }, * } * * Together with a keys list to translate the strings and objects * ["translatedString1", "translatedString2", "translatedObject1", * "translatedObject2"] * * The result will be (f.e. in Dutch) * { * translatedString1: "mijn eerste bericht", * unTranslatedString1: "some English string", * ignoredObject: { * translatedString2: "mijn tweede bericht", * unTranslatedString2: "some English string" * }, * translatedObject1: "mijn derde bericht", * translatedObject2: "lijst, van, teksten", * } * * So you see that the keys array can also contain lower-level keys, * And that you can include objects in the keys array to translate * them with a context, or to join a list of translations. * * Also, the keys array may be an object where properties are keys to translate * and values are translation contexts to use for each key. */ function translateObjectKeys(object, keys) { if (keys instanceof Array) { for (let property in object) { if (keys.indexOf(property) > -1) { if (isTranslatableString(object[property])) object[property] = translate(object[property]); else if (object[property] instanceof Object) object[property] = translateMessageObject(object[property]); } else if (object[property] instanceof Object) translateObjectKeys(object[property], keys); } } // If ‘keys’ is not an array, it is an object where keys are properties to // translate and values are translation contexts to use for each key. // An empty value means no context. else { for (let property in object) { if (property in keys) { if (isTranslatableString(object[property])) if (keys[property]) object[property] = translateWithContext(keys[property], object[property]); else object[property] = translate(object[property]); else if (object[property] instanceof Object) object[property] = translateMessageObject(object[property]); } else if (object[property] instanceof Object) translateObjectKeys(object[property], keys); } } } Index: ps/trunk/binaries/data/mods/mod/gui/modio/modio.js =================================================================== --- ps/trunk/binaries/data/mods/mod/gui/modio/modio.js (nonexistent) +++ ps/trunk/binaries/data/mods/mod/gui/modio/modio.js (revision 21759) @@ -0,0 +1,344 @@ +var g_ModsAvailableOnline = []; + +/** + * Indicates if we have encountered an error in one of the network-interaction attempts. + * + * We use a global so we don't get multiple messageBoxes appearing (one for each "tick"). + * + * Set to `true` by showErrorMessageBox + * Set to `false` by init, updateModList, downloadFile, and cancelRequest + */ +var g_Failure; + +/** + * Indicates if the user has cancelled a request. + * + * Primarily used so the user can cancel the mod list fetch, as whenever that get cancelled, + * the modio state reverts to "ready", even if we've successfully listed mods before. + * + * Set to `true` by cancelRequest + * Set to `false` by updateModList, and downloadFile + */ +var g_RequestCancelled; + +var g_RequestStartTime; + +/** + * Returns true if ModIoAdvanceRequest should be called. + */ +var g_ModIOState = { + /** + * Finished status indicators + */ + "ready": progressData => { + // GameID acquired, ready to fetch mod list + if (!g_RequestCancelled) + updateModList(); + return true; + }, + "listed": progressData => { + // List of available mods acquired + + // Only run this once (for each update). + if (Engine.GetGUIObjectByName("modsAvailableList").list.length) + return true; + + hideDialog(); + Engine.GetGUIObjectByName("refreshButton").enabled = true; + g_ModsAvailableOnline = Engine.ModIoGetMods(); + displayMods(); + return true; + }, + "success": progressData => { + // Successfully acquired a mod file + hideDialog(); + Engine.GetGUIObjectByName("downloadButton").enabled = true; + return true; + }, + /** + * In-progress status indicators. + */ + "gameid": progressData => { + // Acquiring GameID from mod.io + return true; + }, + "listing": progressData => { + // Acquiring list of available mods from mod.io + return true; + }, + "downloading": progressData => { + // Downloading a mod file + updateProgressBar(progressData.progress, g_ModsAvailableOnline[selectedModIndex()].filesize); + return true; + }, + /** + * Error/Failure status indicators. + */ + "failed_gameid": progressData => { + // Game ID couldn't be retrieved + showErrorMessageBox( + sprintf(translateWithContext("mod.io error message", "Game ID could not be retrieved.\n\n%(technicalDetails)s"), { + "technicalDetails": progressData.error + }), + translateWithContext("mod.io error message", "Initialization Error"), + [translate("Abort"), translate("Retry")], + [closePage, init]); + return false; + }, + "failed_listing": progressData => { + // Mod list couldn't be retrieved + showErrorMessageBox( + sprintf(translateWithContext("mod.io error message", "Mod List could not be retrieved.\n\n%(technicalDetails)s"), { + "technicalDetails": progressData.error + }), + translateWithContext("mod.io error message", "Fetch Error"), + [translate("Abort"), translate("Retry")], + [cancelModListUpdate, updateModList]); + return false; + }, + "failed_downloading": progressData => { + // File couldn't be retrieved + showErrorMessageBox( + sprintf(translateWithContext("mod.io error message", "File download failed.\n\n%(technicalDetails)s"), { + "technicalDetails": progressData.error + }), + translateWithContext("mod.io error message", "Download Error"), + [translate("Abort"), translate("Retry")], + [cancelRequest, downloadMod]); + return false; + }, + "failed_filecheck": progressData => { + // The file is corrupted + showErrorMessageBox( + sprintf(translateWithContext("mod.io error message", "File verification error.\n\n%(technicalDetails)s"), { + "technicalDetails": progressData.error + }), + translateWithContext("mod.io error message", "Verification Error"), + [translate("Abort")], + [cancelRequest]); + return false; + }, + /** + * Default + */ + "none": progressData => { + // Nothing has happened yet. + return true; + } +}; + +function init(data) +{ + progressDialog( + translate("Initializing mod.io interface."), + translate("Initializing"), + false, + translate("Cancel"), + closePage); + + g_Failure = false; + Engine.ModIoStartGetGameId(); +} + +function onTick() +{ + let progressData = Engine.ModIoGetDownloadProgress(); + + let handler = g_ModIOState[progressData.status]; + if (!handler) + { + warn("Unrecognized progress status: " + progressData.status); + return; + } + + if (handler(progressData)) + Engine.ModIoAdvanceRequest(); +} + +function displayMods() +{ + let modsAvailableList = Engine.GetGUIObjectByName("modsAvailableList"); + let selectedMod = modsAvailableList.list[modsAvailableList.selected]; + modsAvailableList.selected = -1; + + let displayedMods = clone(g_ModsAvailableOnline); + for (let i = 0; i < displayedMods.length; ++i) + displayedMods[i].i = i; + + let filterColumns = ["name", "name_id", "summary"]; + let filterText = Engine.GetGUIObjectByName("modFilter").caption.toLowerCase(); + displayedMods = displayedMods.filter(mod => filterColumns.some(column => mod[column].toLowerCase().indexOf(filterText) != -1)); + + displayedMods.sort((mod1, mod2) => + modsAvailableList.selected_column_order * + (modsAvailableList.selected_column == "filesize" ? + mod1.filesize - mod2.filesize : + String(mod1[modsAvailableList.selected_column]).localeCompare(String(mod2[modsAvailableList.selected_column])))); + + modsAvailableList.list_name = displayedMods.map(mod => mod.name); + modsAvailableList.list_name_id = displayedMods.map(mod => mod.name_id); + modsAvailableList.list_version = displayedMods.map(mod => mod.version); + modsAvailableList.list_filesize = displayedMods.map(mod => filesizeToString(mod.filesize)); + modsAvailableList.list_dependencies = displayedMods.map(mod => (mod.dependencies || []).join(" ")); + modsAvailableList.list = displayedMods.map(mod => mod.i); + modsAvailableList.selected = modsAvailableList.list.indexOf(selectedMod); +} + +function clearModList() +{ + let modsAvailableList = Engine.GetGUIObjectByName("modsAvailableList"); + modsAvailableList.selected = -1; + for (let listIdx of Object.keys(modsAvailableList).filter(key => key.startsWith("list"))) + modsAvailableList[listIdx] = []; +} + +function selectedModIndex() +{ + let modsAvailableList = Engine.GetGUIObjectByName("modsAvailableList"); + + if (modsAvailableList.selected == -1) + return undefined; + + return +modsAvailableList.list[modsAvailableList.selected]; +} + +function showModDescription() +{ + let selected = selectedModIndex(); + Engine.GetGUIObjectByName("downloadButton").enabled = selected !== undefined; + Engine.GetGUIObjectByName("modDescription").caption = selected !== undefined ? g_ModsAvailableOnline[selected].summary : ""; +} + +function cancelModListUpdate() +{ + cancelRequest(); + + if (!g_ModsAvailableOnline.length) + { + closePage(); + return; + } + + displayMods(); + Engine.GetGUIObjectByName('refreshButton').enabled = true; +} + +function updateModList() +{ + clearModList(); + Engine.GetGUIObjectByName("refreshButton").enabled = false; + + progressDialog( + translate("Fetching and updating list of available mods."), + translate("Updating"), + false, + translate("Cancel Update"), + cancelModListUpdate); + + g_Failure = false; + g_RequestCancelled = false; + Engine.ModIoStartListMods(); +} + +function downloadMod() +{ + let selected = selectedModIndex(); + + progressDialog( + sprintf(translate("Downloading “%(modname)s”"), { + "modname": g_ModsAvailableOnline[selected].name + }), + translate("Downloading"), + true, + translate("Cancel Download"), + () => { Engine.GetGUIObjectByName("downloadButton").enabled = true; }); + + Engine.GetGUIObjectByName("downloadButton").enabled = false; + + g_Failure = false; + g_RequestCancelled = false; + Engine.ModIoStartDownloadMod(selected); +} + +function cancelRequest() +{ + g_Failure = false; + g_RequestCancelled = true; + Engine.ModIoCancelRequest(); + hideDialog(); +} + +function closePage(data) +{ + Engine.PopGuiPageCB(undefined); +} + +function showErrorMessageBox(caption, title, buttonCaptions, buttonActions) +{ + if (g_Failure) + return; + + messageBox(500, 250, caption, title, buttonCaptions, buttonActions); + g_Failure = true; +} + +function progressDialog(dialogCaption, dialogTitle, showProgressBar, buttonCaption, buttonAction) +{ + Engine.GetGUIObjectByName("downloadDialog_title").caption = dialogTitle; + + let downloadDialog_caption = Engine.GetGUIObjectByName("downloadDialog_caption"); + downloadDialog_caption.caption = dialogCaption; + + let size = downloadDialog_caption.size; + size.rbottom = showProgressBar ? 40 : 80; + downloadDialog_caption.size = size; + + Engine.GetGUIObjectByName("downloadDialog_progress").hidden = !showProgressBar; + Engine.GetGUIObjectByName("downloadDialog_status").hidden = !showProgressBar; + + let downloadDialog_button = Engine.GetGUIObjectByName("downloadDialog_button"); + downloadDialog_button.caption = buttonCaption; + downloadDialog_button.onPress = () => { cancelRequest(); buttonAction(); }; + + Engine.GetGUIObjectByName("downloadDialog").hidden = false; + + g_RequestStartTime = Date.now(); +} + +/* + * The "remaining time" and "average speed" texts both naively assume that + * the connection remains relatively stable throughout the download. + */ +function updateProgressBar(progress, totalSize) +{ + let progressPercent = Math.ceil(progress * 100); + Engine.GetGUIObjectByName("downloadDialog_progressBar").caption = progressPercent; + + let transferredSize = progress * totalSize; + let transferredSizeObj = filesizeToObj(transferredSize); + // Translation: Mod file download indicator. Current size over expected final size, with percentage complete. + Engine.GetGUIObjectByName("downloadDialog_progressText").caption = sprintf(translate("%(current)s / %(total)s (%(percent)s%%)"), { + "current": filesizeToObj(totalSize).unit == transferredSizeObj.unit ? transferredSizeObj.filesize : filesizeToString(transferredSize), + "total": filesizeToString(totalSize), + "percent": progressPercent + }); + + let elapsedTime = Date.now() - g_RequestStartTime; + let remainingTime = progressPercent ? (100 - progressPercent) * elapsedTime / progressPercent : 0; + let avgSpeed = filesizeToObj(transferredSize / (elapsedTime / 1000)); + // Translation: Mod file download status message. + Engine.GetGUIObjectByName("downloadDialog_status").caption = sprintf(translate("Time Elapsed: %(elapsed)s\nEstimated Time Remaining: %(remaining)s\nAverage Speed: %(avgSpeed)s"), { + "elapsed": timeToString(elapsedTime), + "remaining": remainingTime ? timeToString(remainingTime) : translate("∞"), + // Translation: Average download speed, used to give the user a very rough and naive idea of the download time. For example: 123.4 KiB/s + "avgSpeed": sprintf(translate("%(number)s %(unit)s/s"), { + "number": avgSpeed.filesize, + "unit": avgSpeed.unit + }) + }); +} + +function hideDialog() +{ + Engine.GetGUIObjectByName("downloadDialog").hidden = true; +} Property changes on: ps/trunk/binaries/data/mods/mod/gui/modio/modio.js ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property Index: ps/trunk/binaries/data/mods/mod/gui/modio/modio.xml =================================================================== --- ps/trunk/binaries/data/mods/mod/gui/modio/modio.xml (nonexistent) +++ ps/trunk/binaries/data/mods/mod/gui/modio/modio.xml (revision 21759) @@ -0,0 +1,110 @@ + + + + +