Index: ps/trunk/binaries/data/config/default.cfg =================================================================== --- ps/trunk/binaries/data/config/default.cfg (revision 19506) +++ ps/trunk/binaries/data/config/default.cfg (revision 19507) @@ -1,439 +1,439 @@ ; 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 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 = 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+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 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 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 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 backtowork = "Y" ; The unit will go back to work unload = "U" ; Unload garrisoned units when a building/mechanical unit is selected attack = Ctrl ; Modifier to attack instead of another action (eg 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 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 [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.gamesetup] enabletips = true ; Enable/Disable tips during gamesetup (for newcomers) -[gui.menu] -limitfps = true ; Limit FPS in the menus and loading screen - [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 batchtrainingsize = 5 ; Number of units to be trained per batch (when pressing the hotkey) [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 = 1 ; 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 (0 = disable, 1 = completed phase only and 2 = display 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 [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 = "arena22" ; Default MUC room to join server = "lobby.wildfiregames.com" ; Address of lobby server xpartamupp = "wfgbot22" ; Name of the server-side xmpp client that manage games buddies = "," ; Comma separated list of playernames that the current user has marked as buddies [mod] enabledmods = "mod public" [network] duplicateplayernames = false ; Rename joining player to "User (2)" if "User" is already connected, otherwise prohibit join. lateobserverjoins = true ; Allow observers to join the game after it started 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/public/gui/options/options.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/options/options.js (revision 19506) +++ ps/trunk/binaries/data/mods/public/gui/options/options.js (revision 19507) @@ -1,401 +1,402 @@ var g_HasCallback = false; var g_Controls; function init(data) { if (data && data.callback) g_HasCallback = true; g_Controls = {}; var options = Engine.ReadJSONFile("gui/options/options.json"); for (let category in options) { let lastSize; for (let i = 0; i < options[category].length; ++i) { let option = options[category][i]; if (!option.label || !option.parameters || !option.parameters.config) continue; let body = Engine.GetGUIObjectByName(category + "[" + i + "]"); let label = Engine.GetGUIObjectByName(category + "Label[" + i + "]"); let config = option.parameters.config; g_Controls[config] = { "control": setupControl(option, i, category), "label": label, "type": option.type, "dependencies": option.dependencies || undefined, "parameters": option.parameters }; label.caption = translate(option.label); label.tooltip = option.tooltip ? translate(option.tooltip) : ""; // Move each element to the correct place. if (lastSize) { let newSize = new GUISize(); newSize.left = lastSize.left; newSize.rright = lastSize.rright; newSize.top = lastSize.bottom; newSize.bottom = newSize.top + 26; body.size = newSize; lastSize = newSize; } else lastSize = body.size; // small right shift of options which depends on another one for (let opt of options[category]) { if (!opt.label || !opt.parameters || !opt.parameters.config) continue; if (!opt.dependencies || opt.dependencies.indexOf(config) === -1) continue; label.caption = " " + label.caption; break; } // Show element. body.hidden = false; } } updateOptionPanel(); } /** * 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, category) { var control; var onUpdate; var key = option.parameters.config; switch (option.type) { case "boolean": case "invertedboolean": // More space for the label let text = Engine.GetGUIObjectByName(category + "Label[" + i + "]"); let size = text.size; size.rright = 87; text.size = size; control = Engine.GetGUIObjectByName(category + "Tickbox[" + i + "]"); let checked; let keyRenderer; for (let param in option.parameters) { switch (param) { case "config": checked = Engine.ConfigDB_GetValue("user", key) === "true"; break; case "renderer": keyRenderer = option.parameters.renderer; if (!Engine["Renderer_Get" + keyRenderer + "Enabled"]) { warn("Invalid renderer key " + keyRenderer); keyRenderer = undefined; break; } if (Engine["Renderer_Get" + keyRenderer + "Enabled"]() !== checked) { warn("Incompatible renderer option value for " + keyRenderer); Engine["Renderer_Set" + keyRenderer + "Enabled"](checked); } break; default: warn("Unknown option source type '" + param + "'"); } } // invertedboolean when we want to display the opposite of the flag value var inverted = option.type === "invertedboolean"; if (inverted) checked = !checked; onUpdate = function(key, keyRenderer, inverted) { return function() { let val = inverted ? !this.checked : this.checked; if (keyRenderer) Engine["Renderer_Set" + keyRenderer + "Enabled"](val); Engine.ConfigDB_CreateValue("user", key, String(val)); Engine.ConfigDB_SetChanges("user", true); updateOptionPanel(); }; }(key, keyRenderer, inverted); // Load final data to the control element. control.checked = checked; control.onPress = onUpdate; break; case "slider": control = Engine.GetGUIObjectByName(category + "Slider[" + i + "]"); let value; let callbackFunction; let minvalue; let maxvalue; for (let param in option.parameters) { switch (param) { case "config": value = +Engine.ConfigDB_GetValue("user", key); break; case "function": if (Engine[option.parameters.function]) callbackFunction = option.parameters.function; break; case "min": minvalue = +option.parameters.min; break; case "max": maxvalue = +option.parameters.max; break; default: warn("Unknown option source type '" + param + "'"); } } onUpdate = function(key, callbackFunction, minvalue, maxvalue) { return function() { if (Engine.ConfigDB_GetValue("user", key) === this.value) return; Engine.ConfigDB_CreateValue("user", key, this.value); Engine.ConfigDB_SetChanges("user", true); if (callbackFunction) Engine[callbackFunction](+this.value); updateOptionPanel(); + control.tooltip = this.value; }; }(key, callbackFunction, minvalue, maxvalue); control.value = value; control.max_value = maxvalue; control.min_value = minvalue; control.onValueChange = onUpdate; break; case "number": case "string": control = Engine.GetGUIObjectByName(category + "Input[" + i + "]"); let caption; let functionBody; let minval; let maxval; for (let param in option.parameters) { switch (param) { case "config": caption = Engine.ConfigDB_GetValue("user", key); break; case "function": if (Engine[option.parameters.function]) functionBody = option.parameters.function; break; case "min": minval = option.parameters.min; break; case "max": maxval = option.parameters.max; break; default: warn("Unknown option source type '" + param + "'"); } } // as the enter key is not necessarily pressed after modifying an entry, we will register the input also // - when the mouse leave the control (MouseLeave event) // - or when saving or closing the window (registerChanges function) // so we must ensure that something has indeed been modified onUpdate = function(key, functionBody, minval, maxval) { return function() { if (minval && +minval > +this.caption) this.caption = minval; if (maxval && +maxval < +this.caption) this.caption = maxval; if (Engine.ConfigDB_GetValue("user", key) === this.caption) return; Engine.ConfigDB_CreateValue("user", key, this.caption); Engine.ConfigDB_SetChanges("user", true); if (functionBody) Engine[functionBody](+this.caption); updateOptionPanel(); }; }(key, functionBody, minval, maxval); control.caption = caption; control.onPress = onUpdate; control.onMouseLeave = onUpdate; break; case "dropdown": control = Engine.GetGUIObjectByName(category + "Dropdown[" + i + "]"); control.onSelectionChange = function(){}; // just the time to setup the value for (let param in option.parameters) { switch (param) { case "config": control.selected = +Engine.ConfigDB_GetValue("user", key); break; case "list": control.list = option.parameters.list.map(e => translate(e)); break; case "list_data": control.list_data = option.parameters.list_data; break; default: warn("Unknown option source type '" + param + "'"); } } onUpdate = function(key) { return function() { Engine.ConfigDB_CreateValue("user", key, this.selected); Engine.ConfigDB_SetChanges("user", true); updateOptionPanel(); }; }(key); control.onSelectionChange = onUpdate; break; default: warn("Unknown option type " + option.type + ", assuming string."); control = Engine.GetGUIObjectByName(category + "Input[" + i + "]"); break; } control.hidden = false; control.tooltip = option.tooltip ? translate(option.tooltip) : ""; return control; } function updateOptionPanel() { // Update dependencies for (let item in g_Controls) { let control = g_Controls[item]; if (control.type !== "boolean" && control.type !== "invertedboolean" || !control.dependencies) continue; for (let dependency of control.dependencies) { g_Controls[dependency].control.enabled = control.control.checked; g_Controls[dependency].label.enabled = control.control.checked; } } // And main buttons let hasChanges = Engine.ConfigDB_HasChanges("user"); Engine.GetGUIObjectByName("revertChanges").enabled = hasChanges; Engine.GetGUIObjectByName("saveChanges").enabled = hasChanges; } /** * Register changes of input (text and number) controls */ function registerChanges() { for (let item in g_Controls) if (g_Controls[item].type === "number" || g_Controls[item].type === "string") g_Controls[item].control.onPress(); } function setDefaults() { messageBox( 500, 200, translate("Resetting the options will erase your saved settings. Do you want to continue?"), translate("Warning"), [translate("No"), translate("Yes")], [null, reallySetDefaults] ); } function reallySetDefaults() { for (let item in g_Controls) Engine.ConfigDB_RemoveValue("user", item); Engine.ConfigDB_WriteFile("user", "config/user.cfg"); revertChanges(); } function revertChanges() { Engine.ConfigDB_Reload("user"); for (let item in g_Controls) { let control = g_Controls[item]; // needs to update renderer values (which are all of boolean type) if (control.parameters.renderer) { if (control.type !== "boolean" && control.type !== "invertedboolean") { warn("Invalid type option " + control.type + " defined in renderer for " + item + ": will not be reverted"); continue; } let checked = Engine.ConfigDB_GetValue("user", item) === "true"; Engine["Renderer_Set" + control.parameters.renderer + "Enabled"](checked); } // and the possible function calls (which are of number or string types) if (control.parameters.function) { if (control.type !== "string" && control.type !== "number" && control.type !== "slider") { warn("Invalid type option " + control.type + " defined with function for " + item + ": will not be reverted"); continue; } let caption = Engine.ConfigDB_GetValue("user", item); Engine[control.parameters.function](+caption); } } Engine.ConfigDB_SetChanges("user", false); init(); } function saveChanges() { registerChanges(); Engine.ConfigDB_WriteFile("user", "config/user.cfg"); Engine.ConfigDB_SetChanges("user", false); updateOptionPanel(); } /** * Close GUI page and call callbacks if they exist. **/ function closePage() { registerChanges(); if (Engine.ConfigDB_HasChanges("user")) { messageBox( 500, 200, translate("You have unsaved changes, do you want to close this window?"), translate("Warning"), [translate("No"), translate("Yes")], [null, closePageWithoutConfirmation] ); } else closePageWithoutConfirmation(); } function closePageWithoutConfirmation() { if (g_HasCallback) Engine.PopGuiPageCB(); else Engine.PopGuiPage(); } Index: ps/trunk/binaries/data/mods/public/gui/options/options.json =================================================================== --- ps/trunk/binaries/data/mods/public/gui/options/options.json (revision 19506) +++ ps/trunk/binaries/data/mods/public/gui/options/options.json (revision 19507) @@ -1,289 +1,295 @@ { "generalSetting": [ { "type": "string", "label": "Playername (Single Player)", "tooltip": "How you want to be addressed in Single Player matches.", "parameters": { "config": "playername.singleplayer" } }, { "type": "string", "label": "Playername (Multiplayer)", "tooltip": "How you want to be addressed in Multiplayer matches (except lobby).", "parameters": { "config": "playername.multiplayer" } }, { "type": "boolean", "label": "Windowed Mode", "tooltip": "Start 0 A.D. in a window", "parameters": { "config": "windowed" } }, { "type": "boolean", "label": "Background Pause", "tooltip": "Pause single player games when window loses focus", "parameters": { "config": "pauseonfocusloss" } }, { "type": "boolean", "label": "Enable Welcome Screen", "tooltip": "When disabled, the welcome screen will nevertheless be shown once when a new version is available and you can always launch it from the main menu.", "parameters": { "config": "gui.splashscreen.enable" } }, { "type": "boolean", "label": "Enable Game Setting Tips", "tooltip": "Show tips when setting up a game.", "parameters": { "config": "gui.gamesetup.enabletips" } }, { "type": "boolean", "label": "Detailed Tooltips", "tooltip": "Show detailed tooltips for trainable units in unit-producing buildings.", "parameters": { "config": "showdetailedtooltips" } }, { "type": "boolean", "label": "Network Warnings", "tooltip": "Show which player has a bad connection in multiplayer games.", "parameters": { "config": "overlay.netwarnings" } }, { "type": "boolean", "label": "FPS Overlay", "tooltip": "Show frames per second in top right corner.", "parameters": { "config": "overlay.fps" } }, { "type": "boolean", "label": "Realtime Overlay", "tooltip": "Show current system time in top right corner.", "parameters": { "config": "overlay.realtime" } }, { "type": "boolean", "label": "Gametime Overlay", "tooltip": "Show current simulation time in top right corner.", "parameters": { "config": "gui.session.timeelapsedcounter" } }, { "type": "boolean", "label": "Ceasefire Time Overlay", "tooltip": "Always show the remaining ceasefire time.", "parameters": { "config": "gui.session.ceasefirecounter" } }, { "type": "boolean", "label": "Persist Match Settings", "tooltip": "Save and restore match settings for quick reuse when hosting another game", "parameters": { "config": "persistmatchsettings" } }, { "type": "boolean", "label": "Late Observer Joins", "tooltip": "Allow observers to join the game after it started", "parameters": { "config": "network.lateobserverjoins" } }, { "type": "number", "label": "Observer Limit", "tooltip": "Prevent further observers from joining if the limit is reached", "parameters": { "config": "network.observerlimit", "min": 0, "max": 32 } }, { "type": "number", "label": "Batch Training Size", "tooltip": "Number of units trained per batch", "parameters": { "config": "gui.session.batchtrainingsize", "min": 1, "max": 20 } }, { "type": "boolean", "label": "Chat Timestamp", "tooltip": "Show time that messages are posted in the lobby, gamesetup and ingame chat.", "parameters": { "config": "chat.timestamp" } } ], "graphicsSetting": [ { "type": "boolean", "label": "Prefer GLSL", "tooltip": "Use OpenGL 2.0 shaders (recommended)", "parameters": { "config": "preferglsl", "renderer": "PreferGLSL" } }, { "type": "boolean", "label": "Post Processing", "tooltip": "Use screen-space postprocessing filters (HDR, Bloom, DOF, etc)", "parameters": { "config": "postproc", "renderer": "Postproc" } }, { "type": "slider", "label": "Graphics quality", "tooltip": "Number of shader effects. REQUIRES GAME RESTART", "parameters": { "config": "materialmgr.quality", "min": 0, "max": 10 } }, { "type": "boolean", "label": "Shadows", "tooltip": "Enable shadows", "parameters": { "config": "shadows", "renderer": "Shadows" }, "dependencies": [ "shadowpcf" ] }, { "type": "boolean", "label": "Shadow Filtering", "tooltip": "Smooth shadows", "parameters": { "config": "shadowpcf", "renderer": "ShadowPCF" } }, { "type": "boolean", "label": "Unit Silhouettes", "tooltip": "Show outlines of units behind buildings", "parameters": { "config": "silhouettes", "renderer": "Silhouettes" } }, { "type": "boolean", "label": "Particles", "tooltip": "Enable particles", "parameters": { "config": "particles", "renderer": "Particles" } }, { "type": "invertedboolean", "label": "Activate water effects", "tooltip": "When OFF, use the lowest settings possible to render water. This makes other settings irrelevant.", "parameters": { "config": "waterugly", "renderer": "WaterUgly" }, "dependencies": [ "waterfancyeffects", "waterrealdepth", "waterreflection", "waterrefraction", "watershadows" ] }, { "type": "boolean", "label": "HQ Water Effects", "tooltip": "Use higher-quality effects for water, rendering coastal waves, shore foam, and ships trails.", "parameters": { "config": "waterfancyeffects", "renderer": "WaterFancyEffects" } }, { "type": "boolean", "label": "Real Water Depth", "tooltip": "Use actual water depth in rendering calculations", "parameters": { "config": "waterrealdepth", "renderer": "WaterRealDepth" } }, { "type": "boolean", "label": "Water Reflections", "tooltip": "Allow water to reflect a mirror image", "parameters": { "config": "waterreflection", "renderer": "WaterReflection" } }, { "type": "boolean", "label": "Water Refraction", "tooltip": "Use a real water refraction map and not transparency", "parameters": { "config": "waterrefraction", "renderer": "WaterRefraction" } }, { "type": "boolean", "label": "Shadows on Water", "tooltip": "Cast shadows on water", "parameters": { "config": "watershadows", "renderer": "WaterShadows" } }, { "type": "boolean", "label": "Smooth LOS", "tooltip": "Lift darkness and fog-of-war smoothly", "parameters": { "config": "smoothlos", "renderer": "SmoothLOS" } }, { "type": "boolean", "label": "Show Sky", "tooltip": "Render Sky", "parameters": { "config": "showsky", "renderer": "ShowSky" } }, { "type": "boolean", "label": "VSync", "tooltip": "Run vertical sync to fix screen tearing. REQUIRES GAME RESTART", "parameters": { "config": "vsync" } }, { - "type": "boolean", - "label": "Limit FPS in Menus", - "tooltip": "Limit FPS to 50 in all menus, to save power.", - "parameters": { "config": "gui.menu.limitfps" } + "type": "slider", + "label": "FPS throttling in menus", + "tooltip": "To save CPU workload, throttle render frequency in all menus. Set to maximum to disable throttling.", + "parameters": { "config": "adaptivefps.menu", "min": 20, "max": 100 } + }, + { + "type": "slider", + "label": "FPS throttling in games", + "tooltip": "To save CPU workload, throttle render frequency in running games. Set to maximum to disable throttling.", + "parameters": { "config": "adaptivefps.session", "min": 20, "max": 100 } } ], "soundSetting": [ { "type": "slider", "label": "Master Volume", "tooltip": "Master audio gain", "parameters": { "config": "sound.mastergain", "function": "SetMasterGain", "min": 0, "max": 2 } }, { "type": "slider", "label": "Music Volume", "tooltip": "In game music gain", "parameters": { "config": "sound.musicgain", "function": "SetMusicGain", "min": 0, "max": 2 } }, { "type": "slider", "label": "Ambient Volume", "tooltip": "In game ambient sound gain", "parameters": { "config": "sound.ambientgain", "function": "SetAmbientGain", "min": 0, "max": 2 } }, { "type": "slider", "label": "Action Volume", "tooltip": "In game unit action sound gain", "parameters": { "config": "sound.actiongain", "function": "SetActionGain", "min": 0, "max": 2 } }, { "type": "slider", "label": "UI Volume", "tooltip": "UI sound gain", "parameters": { "config": "sound.uigain", "function": "SetUIGain", "min": 0, "max": 2 } }, { "type": "boolean", "label": "Nick Notification", "tooltip": "Receive audio notification when someone types your nick", "parameters": { "config": "sound.notify.nick" } } ], "lobbySetting": [ { "type": "number", "label": "Chat Backlog", "tooltip": "Number of backlogged messages to load when joining the lobby", "parameters": { "config": "lobby.history", "min": "0" } } ], "notificationSetting": [ { "type": "boolean", "label": "Attack", "tooltip": "Show a chat notification if you are attacked by another player", "parameters": { "config": "gui.session.notifications.attack" } }, { "type": "boolean", "label": "Tribute", "tooltip": "Show a chat notification if an ally tributes resources to another team member if teams are locked, and all tributes in observer mode", "parameters": { "config": "gui.session.notifications.tribute" } }, { "type": "boolean", "label": "Barter", "tooltip": "Show a chat notification to observers when a player bartered resources", "parameters": { "config": "gui.session.notifications.barter" } }, { "type": "dropdown", "label": "Phase", "tooltip": "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", "parameters": { "config": "gui.session.notifications.phase", "list": [ "Disable", "Completed", "All displayed" ] } } ] } Index: ps/trunk/source/main.cpp =================================================================== --- ps/trunk/source/main.cpp (revision 19506) +++ ps/trunk/source/main.cpp (revision 19507) @@ -1,577 +1,592 @@ -/* Copyright (C) 2016 Wildfire Games. +/* Copyright (C) 2017 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 + #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 "scriptinterface/ScriptEngine.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; +static std::chrono::high_resolution_clock::time_point lastFrameTime; + // main app message handler static InReaction MainInputHandler(const SDL_Event_* ev) { switch(ev->ev.type) { 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; 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(); } +/** + * Optionally throttle the render frequency in order to + * prevent 100% workload of the currently used CPU core. + */ +inline static void LimitFPS() +{ + if (g_VSync) + return; + + double fpsLimit = 0.0; + CFG_GET_VAL(g_Game && g_Game->IsGameStarted() ? "adaptivefps.session" : "adaptivefps.menu", fpsLimit); + + // Keep in sync with options.json + if (fpsLimit < 20.0 || fpsLimit >= 100.0) + return; + + double wait = 1000.0 / fpsLimit - + std::chrono::duration_cast( + std::chrono::high_resolution_clock::now() - lastFrameTime).count() / 1000.0; + + if (wait > 0.0) + SDL_Delay(wait); + + lastFrameTime = std::chrono::high_resolution_clock::now(); +} 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) + 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); } - // 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) + if (need_render) { Render(); PROFILE3("swap buffers"); SDL_GL_SwapWindow(g_VideoMode.GetWindow()); } ogl_WarnIfError(); g_Profiler.Frame(); g_GameRestarted = false; + + LimitFPS(); } 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; if (args.Has("version") || args.Has("-version")) { debug_printf("Pyrogenesis %s\n", engine_version); return; } const bool isVisualReplay = args.Has("replay-visual"); const bool isNonVisualReplay = args.Has("replay"); const CStr replayFile = isVisualReplay ? args.Get("replay-visual") : isNonVisualReplay ? args.Get("replay") : ""; if (isVisualReplay || isNonVisualReplay) { if (!FileExists(OsPath(replayFile))) { debug_printf("ERROR: The requested replay file '%s' does not exist!\n", replayFile.c_str()); return; } if (DirectoryExists(OsPath(replayFile))) { debug_printf("ERROR: The requested replay file '%s' is a directory!\n", replayFile.c_str()); return; } } // We need to initialize SpiderMonkey and libxml2 in the main thread before // any thread uses them. So initialize them here before we might run Atlas. ScriptEngine scriptEngine; CXeromyces::Startup(); if (ATLAS_RunIfOnCmdLine(args, false)) { CXeromyces::Terminate(); return; } if (isNonVisualReplay) { if (!args.Has("mod")) { LOGERROR("At least one mod should be specified! Did you mean to add the argument '-mod=public'?"); CXeromyces::Terminate(); 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("rejointest") ? args.Get("rejointest").ToInt() : -1, args.Has("ooslog")); } g_VFS.reset(); CXeromyces::Terminate(); 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); 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; }