Index: ps/trunk/binaries/data/config/default.cfg =================================================================== --- ps/trunk/binaries/data/config/default.cfg (revision 16926) +++ ps/trunk/binaries/data/config/default.cfg (revision 16927) @@ -1,406 +1,409 @@ ; 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 ; 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 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 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 ; Color of the sky (in "r g b" format) skycolor = "0 0 0" [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 = "Alt+F4", "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+W" ; Toggle wireframe mode silhouettes = "Alt+S" ; Toggle unit silhouettes showsky = "Alt+Z" ; Toggle sky ; > 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 session.gui.toggle = "Alt+G" ; Toggle visibility of session GUI menu.toggle = "F10" ; Toggle in-game menu 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 ; > HOTKEYS ONLY chat = Return ; Toggle chat window teamchat = "T" ; Toggle chat window in team chat mode ; > 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 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 milonly = Alt ; Add only military units to selection idleonly = "I" ; Select only idle 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 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 attack = "Ctrl+Alt" ; Modifier to force attack instead of another action 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 queue = Shift ; Modifier to queue unit orders instead of replacing batchtrain = Shift ; Modifier to train units in batches massbarter = Shift ; Modifier to barter bunch of resources masstribute = Shift ; Modifier to tribute bunch of resources 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.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.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.menu] +limitfps = true ; Limit FPS in the menus and loading screen + [gui.session] attacknotificationmessage = true ; Show attack notification messages 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 [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 [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 [jsdebugger] ; 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. enable = false ; Enable Javascript debugging. [lobby] chattimestamp = false ; Show time chat message was posted history = 0 ; Number of past messages to display on join room = "arena19" ; Default MUC room to join server = "lobby.wildfiregames.com" ; Address of lobby server xpartamupp = "wfgbot19" ; Name of the server-side xmpp client that manage games [mod] enabledmods = "mod public" [overlay] fps = "false" ; Show frames per second in top right corner realtime = "false" ; Show current system time in top right corner [profiler2] autoenable = false ; Enable HTTP server output at startup (default off for security/performance) script.enable = false ; Enable Javascript profiling. Needs to be set before startup and can't be changed later. (default off for 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 [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/public/gui/options/options.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/options/options.js (revision 16926) +++ ps/trunk/binaries/data/mods/public/gui/options/options.js (revision 16927) @@ -1,234 +1,235 @@ var g_hasCallback = false; /** * This array holds the data to populate the general section with. * Data is in the form [Title, Tooltip, {ActionType:Action}, InputType]. */ var options = { "generalSetting": [ [translate("Windowed Mode"), translate("Start 0 A.D. in a window"), {"config":"windowed"}, "boolean"], [translate("Background Pause"), translate("Pause single player games when window loses focus"), {"config":"pauseonfocusloss"}, "boolean"], [translate("Disable Welcome Screen"), translate("If you disable this screen completely, you may miss important announcements.\nYou can still launch it using the main menu."), {"config":"splashscreendisable"}, "boolean"], [translate("Detailed Tooltips"), translate("Show detailed tooltips for trainable units in unit-producing buildings."), {"config":"showdetailedtooltips"}, "boolean"], [translate("FPS Overlay"), translate("Show frames per second in top right corner."), {"config":"overlay.fps"}, "boolean"], [translate("Realtime Overlay"), translate("Show current system time in top right corner."), {"config":"overlay.realtime"}, "boolean"], [translate("Gametime Overlay"), translate("Show current simulation time in top right corner."), {"config":"gui.session.timeelapsedcounter"}, "boolean"], [translate("Ceasefire Time Overlay"), translate("Always show the remaining ceasefire time."), {"config":"gui.session.ceasefirecounter"}, "boolean"], - [translate("Persist match settings"), translate("Save and restore match settings for quick reuse when hosting another game"), {"config":"persistmatchsettings"}, "boolean"], + [translate("Persist Match Settings"), translate("Save and restore match settings for quick reuse when hosting another game"), {"config":"persistmatchsettings"}, "boolean"], ], "graphicsSetting": [ [translate("Prefer GLSL"), translate("Use OpenGL 2.0 shaders (recommended)"), {"renderer":"PreferGLSL", "config":"preferglsl"}, "boolean"], [translate("Post Processing"), translate("Use screen-space postprocessing filters (HDR, Bloom, DOF, etc)"), {"renderer":"Postproc", "config":"postproc"}, "boolean"], [translate("Shadows"), translate("Enable shadows"), {"renderer":"Shadows", "config":"shadows"}, "boolean"], [translate("Particles"), translate("Enable particles"), {"renderer":"Particles", "config":"particles"}, "boolean"], [translate("Show Sky"), translate("Render Sky"), {"renderer":"ShowSky", "config":"showsky"}, "boolean"], [translate("Smooth LOS"), translate("Lift darkness and fog-of-war smoothly"), {"renderer":"SmoothLOS", "config":"smoothlos"}, "boolean"], [translate("Unit Silhouettes"), translate("Show outlines of units behind buildings"), {"renderer":"Silhouettes", "config":"silhouettes"}, "boolean"], [translate("Shadow Filtering"), translate("Smooth shadows"), {"renderer":"ShadowPCF", "config":"shadowpcf"}, "boolean"], [translate("Fast & Ugly Water"), translate("Use the lowest settings possible to render water. This makes other settings irrelevant."), {"renderer":"WaterUgly", "config":"waterugly"}, "boolean"], [translate("HQ Water Effects"), translate("Use higher-quality effects for water, rendering coastal waves, shore foam, and ships trails."), {"renderer":"WaterFancyEffects", "config":"waterfancyeffects"}, "boolean"], [translate("Real Water Depth"), translate("Use actual water depth in rendering calculations"), {"renderer":"WaterRealDepth", "config":"waterrealdepth"}, "boolean"], [translate("Water Reflections"), translate("Allow water to reflect a mirror image"), {"renderer":"WaterReflection", "config":"waterreflection"}, "boolean"], [translate("Water Refraction"), translate("Use a real water refraction map and not transparency"), {"renderer":"WaterRefraction", "config":"waterrefraction"}, "boolean"], [translate("Shadows on Water"), translate("Cast shadows on water"), {"renderer":"WaterShadows", "config":"watershadows"}, "boolean"], [translate("VSync"), translate("Run vertical sync to fix screen tearing. REQUIRES GAME RESTART"), {"config":"vsync"}, "boolean"], + [translate("Limit FPS in Menus"), translate("Limit FPS to 50 in all menus, to save power."), {"config":"gui.menu.limitfps"}, "boolean"], ], "soundSetting": [ [translate("Master Gain"), translate("Master audio gain"), {"config":"sound.mastergain", "function":"Engine.SetMasterGain(Number(this.caption));"}, "number"], [translate("Music Gain"), translate("In game music gain"), {"config":"sound.musicgain", "function":"Engine.SetMusicGain(Number(this.caption));"}, "number"], [translate("Ambient Gain"), translate("In game ambient sound gain"), {"config":"sound.ambientgain", "function":"Engine.SetMusicGain(Number(this.caption));"}, "number"], [translate("Action Gain"), translate("In game unit action sound gain"), {"config":"sound.actiongain", "function":"Engine.SetMusicGain(Number(this.caption));"}, "number"], [translate("UI Gain"), translate("UI sound gain"), {"config":"sound.uigain", "function":"Engine.SetMusicGain(Number(this.caption));"}, "number"], ], "lobbySetting": [ [translate("Chat Backlog"), translate("Number of backlogged messages to load when joining the lobby"), {"config":"lobby.history"}, "number"], [translate("Chat Timestamp"), translate("Show time that messages are posted in the lobby chat"), {"config":"lobby.chattimestamp"}, "boolean"], ], }; function init(data) { if (data && data.callback) g_hasCallback = true; // WARNING: We assume a strict formatting of the XML and do minimal checking. for each (var prefix in Object.keys(options)) { var lastSize; for (var i = 0; i < options[prefix].length; i++) { var body = Engine.GetGUIObjectByName(prefix + "[" + i + "]"); var label = Engine.GetGUIObjectByName(prefix + "Label[" + i + "]"); // Setup control. setupControl(options[prefix][i], i, prefix); // Setup label. label.caption = options[prefix][i][0]; label.tooltip = options[prefix][i][1]; // Move each element to the correct place. if (i > 0) { var newSize = new GUISize(); newSize.left = lastSize.left; newSize.rright = lastSize.rright; newSize.top = lastSize.bottom; newSize.bottom = newSize.top + 25; body.size = newSize; lastSize = newSize; } else { lastSize = body.size; } // Show element. body.hidden = false; } } } /** * Setup the apropriate control for a given option. * * @param option Structure containing the data to setup an option. * @param prefix Prefix to use when accessing control, for example "generalSetting" when the tickbox name is generalSettingTickbox[i]. */ function setupControl(option, i, prefix) { switch (option[3]) { case "boolean": // More space for the label let text = Engine.GetGUIObjectByName(prefix + "Label[" + i + "]"); let size = text.size; size.rright = 87; text.size = size; var control = Engine.GetGUIObjectByName(prefix + "Tickbox[" + i + "]"); var checked; var onPress = function(){}; // Different option action load and save differently, so this switch is needed. for each (var action in Object.keys(option[2])) { switch (action) { case "config": // Load initial value if not yet loaded. if (!checked || typeof checked != "boolean") checked = Engine.ConfigDB_GetValue("user", option[2][action]) === "true" ? true : false; // Hacky macro to create the callback. var callback = function(key) { return function() Engine.ConfigDB_CreateValue("user", key, String(this.checked)); }(option[2][action]); // Merge the new callback with any existing callbacks. onPress = mergeFunctions(callback, onPress); // TODO: Remove this once GLSL works without GenTangents (#2506) if (option[2][action] == "preferglsl") { callback = function() Engine.ConfigDB_CreateValue("user", "gentangents", String(this.checked)); onPress = mergeFunctions(callback, onPress); } break; case "renderer": // Load initial value if not yet loaded. if (!checked || typeof checked != "boolean") checked = eval("Engine.Renderer_Get" + option[2][action] + "Enabled()"); // Hacky macro to create the callback. var callback = function(key) { return function() eval("Engine.Renderer_Set" + key + "Enabled(" + this.checked + ")"); }(option[2][action]); // Merge the new callback with any existing callbacks. onPress = mergeFunctions(callback, onPress); // TODO: Remove this once GLSL works without GenTangents (#2506) if (option[2][action] == "PreferGLSL") { callback = function() eval("Engine.Renderer_SetGenTangentsEnabled(" + this.checked + ")"); onPress = mergeFunctions(callback, onPress); } break; case "function": // This allows for doing low-level actions, like hiding/showing UI elements. onPress = mergeFunctions(eval("function(){" + option[2][action] + "}"), onPress); break; default: warn("Unknown option source type '" + action + "'"); } } // Load final data to the control element. control.checked = checked; control.onPress = onPress; break; case "number": // TODO: Slider case "string": var control = Engine.GetGUIObjectByName(prefix + "Input[" + i + "]"); var caption; var onPress = function(){}; for each (var action in Object.keys(option[2])) { switch (action) { case "config": // Load initial value if not yet loaded. if (!checked || typeof checked != boolean) caption = Engine.ConfigDB_GetValue("user", option[2][action]);; // Hacky macro to create the callback. var callback = function(key) { return function() Engine.ConfigDB_CreateValue("user", key, String(this.caption)); }(option[2][action]); // Merge the new callback with any existing callbacks. onPress = mergeFunctions(callback, onPress); break; case "function": // This allows for doing low-level actions, like hiding/showing UI elements. onPress = mergeFunctions(function(){eval(option[2][action])}, onPress); break; default: warn("Unknown option source type '" + action + "'"); } } control.caption = caption; control.onPress = onPress; break; default: warn("Unknown option type '" + options[3] + "', assuming string. Valid types are 'number', 'string', or 'bool'."); var control = Engine.GetGUIObjectByName(prefix + "Input[" + i + "]"); break; } control.hidden = false; control.tooltip = option[1]; return control; } /** * Merge two functions which don't expect arguments. * * @return Merged function. */ function mergeFunctions(function1, function2) { return function() { function1.apply(this); function2.apply(this); }; } /** * Close GUI page and call callbacks if they exist. **/ function closePage() { if (g_hasCallback) Engine.PopGuiPageCB(); else Engine.PopGuiPage(); } Index: ps/trunk/source/main.cpp =================================================================== --- ps/trunk/source/main.cpp (revision 16926) +++ ps/trunk/source/main.cpp (revision 16927) @@ -1,573 +1,585 @@ /* Copyright (C) 2015 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ /* This module drives the game when running without Atlas (our integrated map editor). It receives input and OS messages via SDL and feeds them into the input dispatcher, where they are passed on to the game GUI and simulation. It also contains main(), which either runs the above controller or that of Atlas depending on commandline parameters. */ // not for any PCH effort, but instead for the (common) definitions // included there. #define MINIMAL_PCH 2 #include "lib/precompiled.h" #include "lib/debug.h" #include "lib/status.h" #include "lib/secure_crt.h" #include "lib/frequency_filter.h" #include "lib/input.h" #include "lib/ogl.h" #include "lib/timer.h" #include "lib/external_libraries/libsdl.h" #include "ps/ArchiveBuilder.h" #include "ps/CConsole.h" #include "ps/CLogger.h" +#include "ps/ConfigDB.h" #include "ps/Filesystem.h" #include "ps/Game.h" #include "ps/Globals.h" #include "ps/Hotkey.h" #include "ps/Loader.h" #include "ps/Profile.h" #include "ps/Profiler2.h" #include "ps/Pyrogenesis.h" #include "ps/Replay.h" #include "ps/TouchInput.h" #include "ps/UserReport.h" #include "ps/Util.h" #include "ps/VideoMode.h" #include "ps/World.h" #include "ps/GameSetup/GameSetup.h" #include "ps/GameSetup/Atlas.h" #include "ps/GameSetup/Config.h" #include "ps/GameSetup/CmdLineArgs.h" #include "ps/GameSetup/Paths.h" #include "ps/XML/Xeromyces.h" #include "network/NetClient.h" #include "network/NetServer.h" #include "network/NetSession.h" #include "lobby/IXmppClient.h" #include "graphics/Camera.h" #include "graphics/GameView.h" #include "graphics/TextureManager.h" #include "gui/GUIManager.h" #include "renderer/Renderer.h" #include "simulation2/Simulation2.h" #if OS_UNIX #include // geteuid #endif // OS_UNIX extern bool g_GameRestarted; void kill_mainloop(); // to avoid redundant and/or recursive resizing, we save the new // size after VIDEORESIZE messages and only update the video mode // once per frame. // these values are the latest resize message, and reset to 0 once we've // updated the video mode static int g_ResizedW; static int g_ResizedH; // main app message handler static InReaction MainInputHandler(const SDL_Event_* ev) { switch(ev->ev.type) { #if SDL_VERSION_ATLEAST(2, 0, 0) case SDL_WINDOWEVENT: switch(ev->ev.window.event) { case SDL_WINDOWEVENT_ENTER: RenderCursor(true); break; case SDL_WINDOWEVENT_LEAVE: RenderCursor(false); break; case SDL_WINDOWEVENT_RESIZED: g_ResizedW = ev->ev.window.data1; g_ResizedH = ev->ev.window.data2; break; case SDL_WINDOWEVENT_MOVED: g_VideoMode.UpdatePosition(ev->ev.window.data1, ev->ev.window.data2); } break; #else case SDL_ACTIVEEVENT: if (ev->ev.active.state & SDL_APPMOUSEFOCUS) { // Tell renderer not to render cursor if mouse focus is lost // this restores system cursor, until/if focus is regained if (!ev->ev.active.gain) RenderCursor(false); else RenderCursor(true); } break; case SDL_VIDEORESIZE: g_ResizedW = ev->ev.resize.w; g_ResizedH = ev->ev.resize.h; break; #endif case SDL_QUIT: kill_mainloop(); break; case SDL_HOTKEYDOWN: std::string hotkey = static_cast(ev->ev.user.data1); if (hotkey == "exit") { kill_mainloop(); return IN_HANDLED; } else if (hotkey == "screenshot") { WriteScreenshot(L".png"); return IN_HANDLED; } else if (hotkey == "bigscreenshot") { WriteBigScreenshot(L".bmp", 10); return IN_HANDLED; } else if (hotkey == "togglefullscreen") { g_VideoMode.ToggleFullscreen(); return IN_HANDLED; } else if (hotkey == "profile2.toggle") { g_Profiler2.Toggle(); return IN_HANDLED; } break; } return IN_PASS; } // dispatch all pending events to the various receivers. static void PumpEvents() { JSContext* cx = g_GUI->GetScriptInterface()->GetContext(); JSAutoRequest rq(cx); PROFILE3("dispatch events"); SDL_Event_ ev; while (in_poll_event(&ev)) { PROFILE2("event"); if (g_GUI) { JS::RootedValue tmpVal(cx); ScriptInterface::ToJSVal(cx, &tmpVal, ev); std::string data = g_GUI->GetScriptInterface()->StringifyJSON(&tmpVal); PROFILE2_ATTR("%s", data.c_str()); } in_dispatch_event(&ev); } g_TouchInput.Frame(); } static int ProgressiveLoad() { PROFILE3("progressive load"); wchar_t description[100]; int progress_percent; try { Status ret = LDR_ProgressiveLoad(10e-3, description, ARRAY_SIZE(description), &progress_percent); switch(ret) { // no load active => no-op (skip code below) case INFO::OK: return 0; // current task didn't complete. we only care about this insofar as the // load process is therefore not yet finished. case ERR::TIMED_OUT: break; // just finished loading case INFO::ALL_COMPLETE: g_Game->ReallyStartGame(); wcscpy_s(description, ARRAY_SIZE(description), L"Game is starting.."); // LDR_ProgressiveLoad returns L""; set to valid text to // avoid problems in converting to JSString break; // error! default: WARN_RETURN_STATUS_IF_ERR(ret); // can't do this above due to legit ERR::TIMED_OUT break; } } catch (PSERROR_Game_World_MapLoadFailed& e) { // Map loading failed // Call script function to do the actual work // (delete game data, switch GUI page, show error, etc.) CancelLoad(CStr(e.what()).FromUTF8()); } GUI_DisplayLoadProgress(progress_percent, description); return 0; } static void RendererIncrementalLoad() { PROFILE3("renderer incremental load"); const double maxTime = 0.1f; double startTime = timer_Time(); bool more; do { more = g_Renderer.GetTextureManager().MakeProgress(); } while (more && timer_Time() - startTime < maxTime); } static bool quit = false; // break out of main loop static void Frame() { g_Profiler2.RecordFrameStart(); PROFILE2("frame"); g_Profiler2.IncrementFrameNumber(); PROFILE2_ATTR("%d", g_Profiler2.GetFrameNumber()); ogl_WarnIfError(); // get elapsed time const double time = timer_Time(); g_frequencyFilter->Update(time); // .. old method - "exact" but contains jumps #if 0 static double last_time; const double time = timer_Time(); const float TimeSinceLastFrame = (float)(time-last_time); last_time = time; ONCE(return); // first call: set last_time and return // .. new method - filtered and more smooth, but errors may accumulate #else const float realTimeSinceLastFrame = 1.0 / g_frequencyFilter->SmoothedFrequency(); #endif ENSURE(realTimeSinceLastFrame > 0.0f); // decide if update/render is necessary bool need_render = !g_app_minimized; bool need_update = true; // If we are not running a multiplayer game, disable updates when the game is // minimized or out of focus and relinquish the CPU a bit, in order to make // debugging easier. if(g_PauseOnFocusLoss && !g_NetClient && !g_app_has_focus) { PROFILE3("non-focus delay"); need_update = false; // don't use SDL_WaitEvent: don't want the main loop to freeze until app focus is restored SDL_Delay(10); } - // TODO: throttling: limit update and render frequency to the minimum. - // this is mostly relevant for "inactive" state, so that other windows - // get enough CPU time, but it's always nice for power+thermal management. + // Throttling: limit update and render frequency to the minimum to 50 FPS + // in the "inactive" state, so that other windows get enough CPU time, + // (and it's always nice for power+thermal management). + // TODO: when the game performance is high enough, implementing a limit for + // in-game framerate might be sensible. + const float maxFPSMenu = 50.0; + bool limit_fps = false; + CFG_GET_VAL("gui.menu.limitfps", limit_fps); + if (limit_fps && (!g_Game || !g_Game->IsGameStarted())) + { + float remainingFrameTime = (1000.0 / maxFPSMenu) - realTimeSinceLastFrame; + if (remainingFrameTime > 0) + SDL_Delay(remainingFrameTime); + } // this scans for changed files/directories and reloads them, thus // allowing hotloading (changes are immediately assimilated in-game). ReloadChangedFiles(); ProgressiveLoad(); RendererIncrementalLoad(); PumpEvents(); // if the user quit by closing the window, the GL context will be broken and // may crash when we call Render() on some drivers, so leave this loop // before rendering if (quit) return; // respond to pumped resize events if (g_ResizedW || g_ResizedH) { g_VideoMode.ResizeWindow(g_ResizedW, g_ResizedH); g_ResizedW = g_ResizedH = 0; } if (g_NetClient) g_NetClient->Poll(); ogl_WarnIfError(); g_GUI->TickObjects(); ogl_WarnIfError(); if (g_Game && g_Game->IsGameStarted() && need_update) { g_Game->Update(realTimeSinceLastFrame); g_Game->GetView()->Update(float(realTimeSinceLastFrame)); } // Immediately flush any messages produced by simulation code if (g_NetClient) g_NetClient->Flush(); // Keep us connected to any XMPP servers if (g_XmppClient) g_XmppClient->recv(); g_UserReporter.Update(); g_Console->Update(realTimeSinceLastFrame); ogl_WarnIfError(); if(need_render) { Render(); PROFILE3("swap buffers"); #if SDL_VERSION_ATLEAST(2, 0, 0) SDL_GL_SwapWindow(g_VideoMode.GetWindow()); #else SDL_GL_SwapBuffers(); #endif } ogl_WarnIfError(); g_Profiler.Frame(); g_GameRestarted = false; } static void MainControllerInit() { // add additional input handlers only needed by this controller: // must be registered after gui_handler. Should mayhap even be last. in_add_handler(MainInputHandler); } static void MainControllerShutdown() { in_reset_handlers(); } // stop the main loop and trigger orderly shutdown. called from several // places: the event handler (SDL_QUIT and hotkey) and JS exitProgram. void kill_mainloop() { quit = true; } static bool restart_in_atlas = false; // called by game code to indicate main() should restart in Atlas mode // instead of terminating void restart_mainloop_in_atlas() { quit = true; restart_in_atlas = true; } static bool restart = false; // trigger an orderly shutdown and restart the game. void restart_engine() { quit = true; restart = true; } extern CmdLineArgs g_args; // moved into a helper function to ensure args is destroyed before // exit(), which may result in a memory leak. static void RunGameOrAtlas(int argc, const char* argv[]) { CmdLineArgs args(argc, argv); g_args = args; // We need to initialise libxml2 in the main thread before // any thread uses it. So initialise it here before we // might run Atlas. CXeromyces::Startup(); // run Atlas (if requested via args) bool ran_atlas = ATLAS_RunIfOnCmdLine(args, false); // Atlas handles the whole init/shutdown/etc sequence by itself; // when we get here, it has exited and we're done. if(ran_atlas) return; // run non-visual simulation replay if requested if (args.Has("replay")) { std::string replayFile = args.Get("replay"); if (!FileExists(OsPath(replayFile))) { debug_printf("ERROR: The requested replay file '%s' does not exist!\n", replayFile.c_str()); return; } Paths paths(args); g_VFS = CreateVfs(20 * MiB); g_VFS->Mount(L"cache/", paths.Cache(), VFS_MOUNT_ARCHIVABLE); MountMods(paths, GetMods(args, INIT_MODS)); { CReplayPlayer replay; replay.Load(replayFile); replay.Replay(args.Has("serializationtest"), args.Has("ooslog")); } g_VFS.reset(); CXeromyces::Terminate(); return; } // If visual replay file does not exist, quit before starting the renderer if (args.Has("replay-visual")) { std::string replayFile = args.Get("replay-visual"); if (!FileExists(OsPath(replayFile))) { debug_printf("ERROR: The requested replay file '%s' does not exist!\n", replayFile.c_str()); return; } } // run in archive-building mode if requested if (args.Has("archivebuild")) { Paths paths(args); OsPath mod(args.Get("archivebuild")); OsPath zip; if (args.Has("archivebuild-output")) zip = args.Get("archivebuild-output"); else zip = mod.Filename().ChangeExtension(L".zip"); CArchiveBuilder builder(mod, paths.Cache()); // Add mods provided on the command line // NOTE: We do not handle mods in the user mod path here std::vector mods = args.GetMultiple("mod"); for (size_t i = 0; i < mods.size(); ++i) builder.AddBaseMod(paths.RData()/"mods"/mods[i]); builder.Build(zip, args.Has("archivebuild-compress")); CXeromyces::Terminate(); return; } const double res = timer_Resolution(); g_frequencyFilter = CreateFrequencyFilter(res, 30.0); // run the game int flags = INIT_MODS; do { restart = false; quit = false; if (!Init(args, flags)) { flags &= ~INIT_MODS; Shutdown(SHUTDOWN_FROM_CONFIG); continue; } InitGraphics(args, 0); MainControllerInit(); while (!quit) Frame(); Shutdown(0); MainControllerShutdown(); flags &= ~INIT_MODS; } while (restart); if (restart_in_atlas) { ATLAS_RunIfOnCmdLine(args, true); return; } // Shut down libxml2 (done here to match the Startup call) CXeromyces::Terminate(); } #if OS_ANDROID // In Android we compile the engine as a shared library, not an executable, // so rename main() to a different symbol that the wrapper library can load #undef main #define main pyrogenesis_main extern "C" __attribute__((visibility ("default"))) int main(int argc, char* argv[]); #endif extern "C" int main(int argc, char* argv[]) { #if OS_UNIX // Don't allow people to run the game with root permissions, // because bad things can happen, check before we do anything if (geteuid() == 0) { std::cerr << "********************************************************\n" << "WARNING: Attempted to run the game with root permission!\n" << "This is not allowed because it can alter home directory \n" << "permissions and opens your system to vulnerabilities. \n" << "(You received this message because you were either \n" <<" logged in as root or used e.g. the 'sudo' command.) \n" << "********************************************************\n\n"; return EXIT_FAILURE; } #endif // OS_UNIX EarlyInit(); // must come at beginning of main RunGameOrAtlas(argc, const_cast(argv)); // Shut down profiler initialised by EarlyInit g_Profiler2.Shutdown(); return EXIT_SUCCESS; }