Index: ps/trunk/binaries/data/config/default.cfg =================================================================== --- ps/trunk/binaries/data/config/default.cfg (revision 22087) +++ ps/trunk/binaries/data/config/default.cfg (revision 22088) @@ -1,512 +1,513 @@ ; 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 = "arena24" ; Default MUC room to join server = "lobby.wildfiregames.com" ; Address of lobby server tls = true ; Whether to use TLS encryption when connecting to the server. verify_certificate = false ; Whether to reject connecting to the lobby if the TLS certificate is invalid (TODO: wait for Gloox GnuTLS trust implementation to be fixed) terms_url = "https://trac.wildfiregames.com/browser/ps/trunk/binaries/data/mods/public/gui/prelobby/common/terms/"; Allows the user to save the text and print the terms terms_of_service = "0" ; Version (hash) of the Terms of Service that the user has accepted terms_of_use = "0" ; Version (hash) of the Terms of Use that the user has accepted privacy_policy = "0" ; Version (hash) of the Privacy Policy that the user has accepted xpartamupp = "wfgbot24" ; Name of the server-side XMPP-account that manage games echelon = "echelon24" ; 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 [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 = "RWTsHxQMrRq4xwHisyBa2rNQfAedcINzbTT83jeX4/ZcfVxqLfWB4y8w" ; Public key corresponding to the private key valid mods are signed with disclaimer = "0" ; Version (hash) of the Disclaimer that the user has accepted [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 gamestarttimeout = 60000 ; Don't disconnect clients timing out in the loading screen and rejoin process before exceeding this timeout. [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 +gamesetup.join = false ; Play a sound when a new client joins the game setup [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_upload = "https://feedback.wildfiregames.com/report/upload/v1/" ; URL where UserReports are uploaded to url_publication = "https://feedback.wildfiregames.com/" ; URL where UserReports were analyzed and published url_terms = "https://trac.wildfiregames.com/browser/ps/trunk/binaries/data/mods/public/gui/userreport/Terms_and_Conditions.txt"; Allows the user to save the text and print the terms terms = "0" ; Version (hash) of the UserReporter Terms that the user has accepted [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/audio/interface/ui/gamesetup_join.ogg =================================================================== Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream Index: ps/trunk/binaries/data/mods/public/audio/interface/ui/gamesetup_join.ogg =================================================================== --- ps/trunk/binaries/data/mods/public/audio/interface/ui/gamesetup_join.ogg (nonexistent) +++ ps/trunk/binaries/data/mods/public/audio/interface/ui/gamesetup_join.ogg (revision 22088) Property changes on: ps/trunk/binaries/data/mods/public/audio/interface/ui/gamesetup_join.ogg ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +application/octet-stream \ No newline at end of property Index: ps/trunk/binaries/data/mods/public/gui/common/functions_utility.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/common/functions_utility.js (revision 22087) +++ ps/trunk/binaries/data/mods/public/gui/common/functions_utility.js (revision 22088) @@ -1,234 +1,235 @@ /** * Used for acoustic GUI notifications. * Define the soundfile paths and specific time thresholds (avoid spam). * And store the timestamp of last interaction for each notification. */ var g_SoundNotifications = { - "nick": { "soundfile": "audio/interface/ui/chat_alert.ogg", "threshold": 3000 } + "nick": { "soundfile": "audio/interface/ui/chat_alert.ogg", "threshold": 3000 }, + "gamesetup.join": { "soundfile": "audio/interface/ui/gamesetup_join.ogg", "threshold": 0 } }; /** * Save setting for current instance and write setting to the user config file. */ function saveSettingAndWriteToUserConfig(setting, value) { Engine.ConfigDB_CreateValue("user", setting, value); Engine.ConfigDB_WriteValueToFile("user", setting, value, "config/user.cfg"); } /** * Returns translated history and gameplay data of all civs, optionally including a mock gaia civ. */ function loadCivData(selectableOnly, gaia) { let civData = loadCivFiles(selectableOnly); translateObjectKeys(civData, ["Name", "Description", "History", "Special"]); if (gaia) civData.gaia = { "Code": "gaia", "Name": translate("Gaia") }; return deepfreeze(civData); } // A sorting function for arrays of objects with 'name' properties, ignoring case function sortNameIgnoreCase(x, y) { let lowerX = x.name.toLowerCase(); let lowerY = y.name.toLowerCase(); if (lowerX < lowerY) return -1; if (lowerX > lowerY) return 1; return 0; } /** * Escape tag start and escape characters, so users cannot use special formatting. */ function escapeText(text) { return text.replace(/\\/g, "\\\\").replace(/\[/g, "\\["); } function unescapeText(text) { return text.replace(/\\\\/g, "\\").replace(/\\\[/g, "\["); } /** * Merge players by team to remove duplicate Team entries, thus reducing the packet size of the lobby report. */ function playerDataToStringifiedTeamList(playerData) { let teamList = {}; for (let pData of playerData) { let team = pData.Team === undefined ? -1 : pData.Team; if (!teamList[team]) teamList[team] = []; teamList[team].push(pData); delete teamList[team].Team; } return escapeText(JSON.stringify(teamList)); } function stringifiedTeamListToPlayerData(stringifiedTeamList) { let teamList = {}; try { teamList = JSON.parse(unescapeText(stringifiedTeamList)); } catch (e) {} let playerData = []; for (let team in teamList) for (let pData of teamList[team]) { pData.Team = team; playerData.push(pData); } return playerData; } function translateMapTitle(mapTitle) { return mapTitle == "random" ? translateWithContext("map selection", "Random") : translate(mapTitle); } function removeDupes(array) { // loop backwards to make splice operations cheaper let i = array.length; while (i--) if (array.indexOf(array[i]) != i) array.splice(i, 1); } function singleplayerName() { return Engine.ConfigDB_GetValue("user", "playername.singleplayer") || Engine.GetSystemUsername(); } function multiplayerName() { return Engine.ConfigDB_GetValue("user", "playername.multiplayer") || Engine.GetSystemUsername(); } function tryAutoComplete(text, autoCompleteList) { if (!text.length) return text; var wordSplit = text.split(/\s/g); if (!wordSplit.length) return text; var lastWord = wordSplit.pop(); if (!lastWord.length) return text; for (var word of autoCompleteList) { if (word.toLowerCase().indexOf(lastWord.toLowerCase()) != 0) continue; text = wordSplit.join(" "); if (text.length > 0) text += " "; text += word; break; } return text; } function autoCompleteNick(guiObject, playernames) { let text = guiObject.caption; if (!text.length) return; let bufferPosition = guiObject.buffer_position; let textTillBufferPosition = text.substring(0, bufferPosition); let newText = tryAutoComplete(textTillBufferPosition, playernames); guiObject.caption = newText + text.substring(bufferPosition); guiObject.buffer_position = bufferPosition + (newText.length - textTillBufferPosition.length); } function clearChatMessages() { g_ChatMessages.length = 0; Engine.GetGUIObjectByName("chatText").caption = ""; try { for (let timer of g_ChatTimers) clearTimeout(timer); g_ChatTimers.length = 0; } catch (e) {} } /** * Manage acoustic GUI notifications. * * @param {string} type - Notification type. */ function soundNotification(type) { if (Engine.ConfigDB_GetValue("user", "sound.notify." + type) != "true") return; let notificationType = g_SoundNotifications[type]; let timeNow = Date.now(); if (!notificationType.lastInteractionTime || timeNow > notificationType.lastInteractionTime + notificationType.threshold) Engine.PlayUISound(notificationType.soundfile, false); notificationType.lastInteractionTime = timeNow; } /** * Horizontally spaces objects within a parent * * @param margin The gap, in px, between the objects */ function horizontallySpaceObjects(parentName, margin = 0) { let objects = Engine.GetGUIObjectByName(parentName).children; for (let i = 0; i < objects.length; ++i) { let size = objects[i].size; let width = size.right - size.left; size.left = i * (width + margin) + margin; size.right = (i + 1) * (width + margin); objects[i].size = size; } } /** * Hide all children after a certain index */ function hideRemaining(parentName, start = 0) { let objects = Engine.GetGUIObjectByName(parentName).children; for (let i = start; i < objects.length; ++i) objects[i].hidden = true; } function getBuildString() { return sprintf(translate("Build: %(buildDate)s (%(revision)s)"), { "buildDate": Engine.GetBuildDate(), "revision": Engine.GetBuildRevision() }); } Index: ps/trunk/binaries/data/mods/public/gui/gamesetup/gamesetup.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/gamesetup/gamesetup.js (revision 22087) +++ ps/trunk/binaries/data/mods/public/gui/gamesetup/gamesetup.js (revision 22088) @@ -1,2765 +1,2768 @@ const g_MatchSettings_SP = "config/matchsettings.json"; const g_MatchSettings_MP = "config/matchsettings.mp.json"; const g_Ceasefire = prepareForDropdown(g_Settings && g_Settings.Ceasefire); const g_MapSizes = prepareForDropdown(g_Settings && g_Settings.MapSizes); const g_MapTypes = prepareForDropdown(g_Settings && g_Settings.MapTypes); const g_TriggerDifficulties = prepareForDropdown(g_Settings && g_Settings.TriggerDifficulties); const g_PopulationCapacities = prepareForDropdown(g_Settings && g_Settings.PopulationCapacities); const g_StartingResources = prepareForDropdown(g_Settings && g_Settings.StartingResources); const g_VictoryDurations = prepareForDropdown(g_Settings && g_Settings.VictoryDurations); const g_VictoryConditions = g_Settings && g_Settings.VictoryConditions; var g_GameSpeeds = getGameSpeedChoices(false); /** * Offer users to select playable civs only. * Load unselectable civs as they could appear in scenario maps. */ const g_CivData = loadCivData(false, false); /** * Store civilization code and page (structree or history) opened in civilization info. */ var g_CivInfo = { "code": "", "page": "page_civinfo.xml" }; /** * Highlight the "random" dropdownlist item. */ var g_ColorRandom = "orange"; /** * Color for regular dropdownlist items. */ var g_ColorRegular = "white"; /** * Color for "Unassigned"-placeholder item in the dropdownlist. */ var g_PlayerAssignmentColors = { "player": g_ColorRegular, "observer": "170 170 250", "unassigned": "140 140 140", "AI": "70 150 70" }; /** * Used for highlighting the sender of chat messages. */ var g_SenderFont = "sans-bold-13"; /** * This yields [1, 2, ..., MaxPlayers]. */ var g_NumPlayersList = Array(g_MaxPlayers).fill(0).map((v, i) => i + 1); /** * Used for generating the botnames. */ var g_RomanNumbers = [undefined, "I", "II", "III", "IV", "V", "VI", "VII", "VIII"]; var g_PlayerTeamList = prepareForDropdown([{ "label": translateWithContext("team", "None"), "id": -1 }].concat( Array(g_MaxTeams).fill(0).map((v, i) => ({ "label": i + 1, "id": i })) ) ); /** * Number of relics: [1, ..., NumCivs] */ var g_RelicCountList = Object.keys(g_CivData).map((civ, i) => i + 1); var g_PlayerCivList = g_CivData && prepareForDropdown([{ "name": translateWithContext("civilization", "Random"), "tooltip": translate("Picks one civilization at random when the game starts."), "color": g_ColorRandom, "code": "random" }].concat( Object.keys(g_CivData).filter( civ => g_CivData[civ].SelectableInGameSetup ).map(civ => ({ "name": g_CivData[civ].Name, "tooltip": g_CivData[civ].History, "color": g_ColorRegular, "code": civ })).sort(sortNameIgnoreCase) ) ); /** * All selectable playercolors except gaia. */ var g_PlayerColorPickerList = g_Settings && g_Settings.PlayerDefaults.slice(1).map(pData => pData.Color); /** * Directory containing all maps of the given type. */ var g_MapPath = { "random": "maps/random/", "scenario": "maps/scenarios/", "skirmish": "maps/skirmishes/" }; /** * Containing the colors to highlight the ready status of players, * the chat ready messages and * the tooltips and captions for the ready button */ var g_ReadyData = [ { "color": g_ColorRegular, "chat": translate("* %(username)s is not ready."), "caption": translate("I'm ready"), "tooltip": translate("State that you are ready to play.") }, { "color": "green", "chat": translate("* %(username)s is ready!"), "caption": translate("Stay ready"), "tooltip": translate("Stay ready even when the game settings change.") }, { "color": "150 150 250", "chat": "", "caption": translate("I'm not ready!"), "tooltip": translate("State that you are not ready to play.") } ]; /** * Processes a CNetMessage (see NetMessage.h, NetMessages.h) sent by the CNetServer. */ var g_NetMessageTypes = { "netstatus": msg => handleNetStatusMessage(msg), "netwarn": msg => addNetworkWarning(msg), "gamesetup": msg => handleGamesetupMessage(msg), "players": msg => handlePlayerAssignmentMessage(msg), "ready": msg => handleReadyMessage(msg), "start": msg => handleGamestartMessage(msg), "kicked": msg => addChatMessage({ "type": msg.banned ? "banned" : "kicked", "username": msg.username }), "chat": msg => addChatMessage({ "type": "chat", "guid": msg.guid, "text": msg.text }), }; var g_FormatChatMessage = { "system": (msg, user) => systemMessage(msg.text), "settings": (msg, user) => systemMessage(translate('Game settings have been changed')), "connect": (msg, user) => systemMessage(sprintf(translate("%(username)s has joined"), { "username": user })), "disconnect": (msg, user) => systemMessage(sprintf(translate("%(username)s has left"), { "username": user })), "kicked": (msg, user) => systemMessage(sprintf(translate("%(username)s has been kicked"), { "username": user })), "banned": (msg, user) => systemMessage(sprintf(translate("%(username)s has been banned"), { "username": user })), "chat": (msg, user) => sprintf(translate("%(username)s %(message)s"), { "username": senderFont(sprintf(translate("<%(username)s>"), { "username": user })), "message": escapeText(msg.text || "") }), "ready": (msg, user) => sprintf(g_ReadyData[msg.status].chat, { "username": user }), "clientlist": (msg, user) => getUsernameList(), }; var g_MapFilters = [ { "id": "default", "name": translateWithContext("map filter", "Default"), "tooltip": translateWithContext("map filter", "All maps except naval and demo maps."), "filter": mapKeywords => mapKeywords.every(keyword => ["naval", "demo", "hidden"].indexOf(keyword) == -1), "Default": true }, { "id": "naval", "name": translate("Naval Maps"), "tooltip": translateWithContext("map filter", "Maps where ships are needed to reach the enemy."), "filter": mapKeywords => mapKeywords.indexOf("naval") != -1 }, { "id": "demo", "name": translate("Demo Maps"), "tooltip": translateWithContext("map filter", "These maps are not playable but for demonstration purposes only."), "filter": mapKeywords => mapKeywords.indexOf("demo") != -1 }, { "id": "new", "name": translate("New Maps"), "tooltip": translateWithContext("map filter", "Maps that are brand new in this release of the game."), "filter": mapKeywords => mapKeywords.indexOf("new") != -1 }, { "id": "trigger", "name": translate("Trigger Maps"), "tooltip": translateWithContext("map filter", "Maps that come with scripted events and potentially spawn enemy units."), "filter": mapKeywords => mapKeywords.indexOf("trigger") != -1 }, { "id": "all", "name": translate("All Maps"), "tooltip": translateWithContext("map filter", "Every map of the chosen maptype."), "filter": mapKeywords => true }, ]; /** * This contains only filters that have at least one map matching them. */ var g_MapFilterList; /** * Array of biome identifiers supported by the currently selected map. */ var g_BiomeList; /** * Array of trigger difficulties identifiers supported by the currently selected map. */ var g_TriggerDifficultyList; /** * Whether this is a single- or multiplayer match. */ const g_IsNetworked = Engine.HasNetClient(); /** * Is this user in control of game settings (i.e. is a network server, or offline player). */ const g_IsController = !g_IsNetworked || Engine.HasNetServer(); /** * Whether this is a tutorial. */ var g_IsTutorial; /** * To report the game to the lobby bot. */ var g_ServerName; var g_ServerPort; /** * IP address and port of the STUN endpoint. */ var g_StunEndpoint; /** * States whether the GUI is currently updated in response to network messages instead of user input * and therefore shouldn't send further messages to the network. */ var g_IsInGuiUpdate = false; /** * Whether the current player is ready to start the game. * 0 - not ready * 1 - ready * 2 - stay ready */ var g_IsReady = 0; /** * Ignore duplicate ready commands on init. */ var g_ReadyInit = true; /** * If noone has changed the ready status, we have no need to spam the settings changed message. * * <=0 - Suppressed settings message * 1 - Will show settings message * 2 - Host's initial ready, suppressed settings message */ var g_ReadyChanged = 2; /** * Used to prevent calling resetReadyData when starting a game or doubleclicking on the "Start Game" button. */ var g_GameStarted = false; /** * Selectable options (player, AI, unassigned) in the player assignment dropdowns and * their colorized, textual representation. */ var g_PlayerAssignmentList = {}; /** * Remembers which clients are assigned to which player slots and whether they are ready. * The keys are guids or "local" in Singleplayer. */ var g_PlayerAssignments = {}; var g_DefaultPlayerData = []; var g_GameAttributes = { "settings": {} }; /** * List of translated words that can be used to autocomplete titles of settings * and their values (for example playernames). */ var g_Autocomplete = []; /** * Array of strings formatted as displayed, including playername. */ var g_ChatMessages = []; /** * Minimum amount of pixels required for the chat panel to be visible. */ var g_MinChatWidth = 96; /** * Horizontal space between chat window and settings. */ var g_ChatSettingsMargin = 10; /** * Filename and translated title of all maps, given the currently selected * maptype and filter. Sorted by title, shown in the dropdown. */ var g_MapSelectionList = []; /** * Cache containing the mapsettings. Just-in-time loading. */ var g_MapData = {}; /** * Wait one tick before initializing the GUI objects and * don't process netmessages prior to that. */ var g_LoadingState = 0; /** * Send the current gamesettings to the lobby bot if the settings didn't change for this number of seconds. */ var g_GameStanzaTimeout = 2; /** * Index of the GUI timer. */ var g_GameStanzaTimer; /** * Only send a lobby update if something actually changed. */ var g_LastGameStanza; /** * Remembers if the current player viewed the AI settings of some playerslot. */ var g_LastViewedAIPlayer = -1; /** * Total number of units that the engine can run with smoothly. * It means a 4v4 with 150 population can still run nicely, but more than that might "lag". */ var g_PopulationCapacityRecommendation = 1200; /** * Horizontal space between tab buttons and lobby button. */ var g_LobbyButtonSpacing = 8; /** * Vertical size of a tab button. */ var g_TabButtonHeight = 30; /** * Vertical space between two tab buttons. */ var g_TabButtonDist = 4; /** * Vertical size of a setting object. */ var g_SettingHeight = 32; /** * Vertical space between two setting objects. */ var g_SettingDist = 2; /** * Maximum width of a column in the settings panel. */ var g_MaxColumnWidth = 470; /** * Pixels per millisecond the settings panel slides when opening/closing. */ var g_SlideSpeed = 1.2; /** * Store last tick time. */ var g_LastTickTime = Date.now(); /** * Order in which the GUI elements will be shown. * All valid settings are required to appear here. */ var g_SettingsTabsGUI = [ { "label": translateWithContext("Match settings tab name", "Map"), "settings": [ "mapType", "mapFilter", "mapSelection", "mapSize", "biome", "triggerDifficulty", "nomad", "disableTreasures", "exploreMap", "revealMap" ] }, { "label": translateWithContext("Match settings tab name", "Player"), "settings": [ "numPlayers", "populationCap", "startingResources", "disableSpies", "enableCheats" ] }, { "label": translateWithContext("Match settings tab name", "Game Type"), "settings": [ ...g_VictoryConditions.map(victoryCondition => victoryCondition.Name), "relicCount", "relicDuration", "wonderDuration", "regicideGarrison", "gameSpeed", "ceasefire", "lockTeams", "lastManStanding", "enableRating" ] } ]; /** * Contains the logic of all multiple-choice gamesettings. * * Logic * ids - Array of identifier strings that indicate the selected value. * default - Returns the index of the default value (not the value itself). * defined - Whether a value for the setting is actually specified. * get - The identifier of the currently selected value. * select - Saves and processes the value of the selected index of the ids array. * * GUI * title - The caption shown in the label. * tooltip - A description shown when hovering the dropdown or a specific item. * labels - Array of translated strings selectable for this dropdown. * colors - Optional array of colors to tint the according dropdown items with. * hidden - If hidden, both the label and dropdown won't be visible. (default: false) * enabled - Only the label will be shown if the setting is disabled. (default: true) * autocomplete - Marks whether to autocomplete translated values of the string. (default: undefined) * If not undefined, must be a number that denotes the priority (higher numbers come first). * If undefined, still autocompletes the translated title of the setting. * initOrder - Settings with lower values will be initialized first. */ var g_Dropdowns = { "mapType": { "title": () => translate("Map Type"), "tooltip": (hoverIdx) => g_MapTypes.Description[hoverIdx] || translate("Select a map type."), "labels": () => g_MapTypes.Title, "ids": () => g_MapTypes.Name, "default": () => g_MapTypes.Default, "defined": () => g_GameAttributes.mapType !== undefined, "get": () => g_GameAttributes.mapType, "select": (itemIdx) => { g_MapData = {}; g_GameAttributes.mapType = g_MapTypes.Name[itemIdx]; g_GameAttributes.mapPath = g_MapPath[g_GameAttributes.mapType]; delete g_GameAttributes.map; if (g_GameAttributes.mapType != "scenario") g_GameAttributes.settings = { "PlayerData": clone(g_DefaultPlayerData.slice(0, 4)) }; reloadMapFilterList(); }, "autocomplete": 0, "initOrder": 1 }, "mapFilter": { "title": () => translate("Map Filter"), "tooltip": (hoverIdx) => g_MapFilterList.tooltip[hoverIdx] || translate("Select a map filter."), "labels": () => g_MapFilterList.name, "ids": () => g_MapFilterList.id, "default": () => g_MapFilterList.Default, "defined": () => g_MapFilterList.id.indexOf(g_GameAttributes.mapFilter || "") != -1, "get": () => g_GameAttributes.mapFilter, "select": (itemIdx) => { g_GameAttributes.mapFilter = g_MapFilterList.id[itemIdx]; delete g_GameAttributes.map; reloadMapList(); }, "autocomplete": 0, "initOrder": 2 }, "mapSelection": { "title": () => translate("Select Map"), "tooltip": (hoverIdx) => g_MapSelectionList.description[hoverIdx] || translate("Select a map to play on."), "labels": () => g_MapSelectionList.name, "colors": () => g_MapSelectionList.color, "ids": () => g_MapSelectionList.file, "default": () => 0, "defined": () => g_GameAttributes.map !== undefined, "get": () => g_GameAttributes.map, "select": (itemIdx) => { selectMap(g_MapSelectionList.file[itemIdx]); }, "autocomplete": 0, "initOrder": 3 }, "mapSize": { "title": () => translate("Map Size"), "tooltip": (hoverIdx) => g_MapSizes.Tooltip[hoverIdx] || translate("Select map size. (Larger sizes may reduce performance.)"), "labels": () => g_MapSizes.Name, "ids": () => g_MapSizes.Tiles, "default": () => g_MapSizes.Default, "defined": () => g_GameAttributes.settings.Size !== undefined, "get": () => g_GameAttributes.settings.Size, "select": (itemIdx) => { g_GameAttributes.settings.Size = g_MapSizes.Tiles[itemIdx]; }, "hidden": () => g_GameAttributes.mapType != "random", "autocomplete": 0, "initOrder": 1000 }, "biome": { "title": () => translate("Biome"), "tooltip": (hoverIdx) => g_BiomeList && g_BiomeList.Description && g_BiomeList.Description[hoverIdx] || translate("Select the flora and fauna."), "labels": () => g_BiomeList ? g_BiomeList.Title : [], "colors": (itemIdx) => g_BiomeList ? g_BiomeList.Color : [], "ids": () => g_BiomeList ? g_BiomeList.Id : [], "default": () => 0, "defined": () => g_GameAttributes.settings.Biome !== undefined, "get": () => g_GameAttributes.settings.Biome, "select": (itemIdx) => { g_GameAttributes.settings.Biome = g_BiomeList && g_BiomeList.Id[itemIdx]; }, "hidden": () => !g_BiomeList, "autocomplete": 0, "initOrder": 1000 }, "numPlayers": { "title": () => translate("Number of Players"), "tooltip": (hoverIdx) => translate("Select number of players."), "labels": () => g_NumPlayersList, "ids": () => g_NumPlayersList, "default": () => g_MaxPlayers - 1, "defined": () => g_GameAttributes.settings.PlayerData !== undefined, "get": () => g_GameAttributes.settings.PlayerData.length, "enabled": () => g_GameAttributes.mapType == "random", "select": (itemIdx) => { let num = itemIdx + 1; let pData = g_GameAttributes.settings.PlayerData; g_GameAttributes.settings.PlayerData = num > pData.length ? pData.concat(clone(g_DefaultPlayerData.slice(pData.length, num))) : pData.slice(0, num); unassignInvalidPlayers(num); sanitizePlayerData(g_GameAttributes.settings.PlayerData); }, "initOrder": 1000 }, "populationCap": { "title": () => translate("Population Cap"), "tooltip": (hoverIdx) => { let popCap = g_PopulationCapacities.Population[hoverIdx]; let players = g_GameAttributes.settings.PlayerData.length; if (hoverIdx == -1 || popCap * players <= g_PopulationCapacityRecommendation) return translate("Select population limit."); return coloredText( sprintf(translate("Warning: There might be performance issues if all %(players)s players reach %(popCap)s population."), { "players": players, "popCap": popCap }), "orange"); }, "labels": () => g_PopulationCapacities.Title, "ids": () => g_PopulationCapacities.Population, "default": () => g_PopulationCapacities.Default, "defined": () => g_GameAttributes.settings.PopulationCap !== undefined, "get": () => g_GameAttributes.settings.PopulationCap, "select": (itemIdx) => { g_GameAttributes.settings.PopulationCap = g_PopulationCapacities.Population[itemIdx]; }, "enabled": () => g_GameAttributes.mapType != "scenario", "initOrder": 1000 }, "startingResources": { "title": () => translate("Starting Resources"), "tooltip": (hoverIdx) => { return hoverIdx >= 0 ? sprintf(translate("Initial amount of each resource: %(resources)s."), { "resources": g_StartingResources.Resources[hoverIdx] }) : translate("Select the game's starting resources."); }, "labels": () => g_StartingResources.Title, "ids": () => g_StartingResources.Resources, "default": () => g_StartingResources.Default, "defined": () => g_GameAttributes.settings.StartingResources !== undefined, "get": () => g_GameAttributes.settings.StartingResources, "select": (itemIdx) => { g_GameAttributes.settings.StartingResources = g_StartingResources.Resources[itemIdx]; }, "hidden": () => g_GameAttributes.mapType == "scenario", "autocomplete": 0, "initOrder": 1000 }, "ceasefire": { "title": () => translate("Ceasefire"), "tooltip": (hoverIdx) => translate("Set time where no attacks are possible."), "labels": () => g_Ceasefire.Title, "ids": () => g_Ceasefire.Duration, "default": () => g_Ceasefire.Default, "defined": () => g_GameAttributes.settings.Ceasefire !== undefined, "get": () => g_GameAttributes.settings.Ceasefire, "select": (itemIdx) => { g_GameAttributes.settings.Ceasefire = g_Ceasefire.Duration[itemIdx]; }, "enabled": () => g_GameAttributes.mapType != "scenario", "initOrder": 1000 }, "relicCount": { "title": () => translate("Relic Count"), "tooltip": (hoverIdx) => translate("Total number of relics spawned on the map. Relic victory is most realistic with only one or two relics. With greater numbers, the relics are important to capture to receive aura bonuses."), "labels": () => g_RelicCountList, "ids": () => g_RelicCountList, "default": () => g_RelicCountList.indexOf(2), "defined": () => g_GameAttributes.settings.RelicCount !== undefined, "get": () => g_GameAttributes.settings.RelicCount, "select": (itemIdx) => { g_GameAttributes.settings.RelicCount = g_RelicCountList[itemIdx]; }, "hidden": () => g_GameAttributes.settings.VictoryConditions.indexOf("capture_the_relic") == -1, "enabled": () => g_GameAttributes.mapType != "scenario", "initOrder": 1000 }, "relicDuration": { "title": () => translate("Relic Duration"), "tooltip": (hoverIdx) => translate("Minutes until the player has achieved Relic Victory."), "labels": () => g_VictoryDurations.Title, "ids": () => g_VictoryDurations.Duration, "default": () => g_VictoryDurations.Default, "defined": () => g_GameAttributes.settings.RelicDuration !== undefined, "get": () => g_GameAttributes.settings.RelicDuration, "select": (itemIdx) => { g_GameAttributes.settings.RelicDuration = g_VictoryDurations.Duration[itemIdx]; }, "hidden": () => g_GameAttributes.settings.VictoryConditions.indexOf("capture_the_relic") == -1, "enabled": () => g_GameAttributes.mapType != "scenario", "initOrder": 1000 }, "wonderDuration": { "title": () => translate("Wonder Duration"), "tooltip": (hoverIdx) => translate("Minutes until the player has achieved Wonder Victory."), "labels": () => g_VictoryDurations.Title, "ids": () => g_VictoryDurations.Duration, "default": () => g_VictoryDurations.Default, "defined": () => g_GameAttributes.settings.WonderDuration !== undefined, "get": () => g_GameAttributes.settings.WonderDuration, "select": (itemIdx) => { g_GameAttributes.settings.WonderDuration = g_VictoryDurations.Duration[itemIdx]; }, "hidden": () => g_GameAttributes.settings.VictoryConditions.indexOf("wonder") == -1, "enabled": () => g_GameAttributes.mapType != "scenario", "initOrder": 1000 }, "gameSpeed": { "title": () => translate("Game Speed"), "tooltip": (hoverIdx) => translate("Select game speed."), "labels": () => g_GameSpeeds.Title, "ids": () => g_GameSpeeds.Speed, "default": () => g_GameSpeeds.Default, "defined": () => g_GameAttributes.gameSpeed !== undefined && g_GameSpeeds.Speed.indexOf(g_GameAttributes.gameSpeed) != -1, "get": () => g_GameAttributes.gameSpeed, "select": (itemIdx) => { g_GameAttributes.gameSpeed = g_GameSpeeds.Speed[itemIdx]; }, "initOrder": 1000 }, "triggerDifficulty": { "title": () => translate("Difficulty"), "tooltip": (hoverIdx) => g_TriggerDifficultyList && g_TriggerDifficultyList.Description[hoverIdx] || translate("Select the difficulty of this scenario."), "labels": () => g_TriggerDifficultyList ? g_TriggerDifficultyList.Title : [], "ids": () => g_TriggerDifficultyList ? g_TriggerDifficultyList.Id : [], "default": () => g_TriggerDifficultyList ? g_TriggerDifficultyList.Default : 0, "defined": () => g_GameAttributes.settings.TriggerDifficulty !== undefined, "get": () => g_GameAttributes.settings.TriggerDifficulty, "select": (itemIdx) => { g_GameAttributes.settings.TriggerDifficulty = g_TriggerDifficultyList && g_TriggerDifficultyList.Id[itemIdx]; }, "hidden": () => !g_TriggerDifficultyList, "initOrder": 1000 }, }; /** * These dropdowns provide a setting that is repeated once for each player * (where playerIdx is starting from 0 for player 1). */ var g_PlayerDropdowns = { "playerAssignment": { "labels": (playerIdx) => g_PlayerAssignmentList.Name || [], "colors": (playerIdx) => g_PlayerAssignmentList.Color || [], "ids": (playerIdx) => g_PlayerAssignmentList.Choice || [], "default": (playerIdx) => "ai:petra", "defined": (playerIdx) => playerIdx < g_GameAttributes.settings.PlayerData.length, "get": (playerIdx) => { for (let guid in g_PlayerAssignments) if (g_PlayerAssignments[guid].player == playerIdx + 1) return "guid:" + guid; for (let ai of g_Settings.AIDescriptions) if (g_GameAttributes.settings.PlayerData[playerIdx].AI == ai.id) return "ai:" + ai.id; return "unassigned"; }, "select": (selectedIdx, playerIdx) => { let choice = g_PlayerAssignmentList.Choice[selectedIdx]; if (choice == "unassigned" || choice.startsWith("ai:")) { if (g_IsNetworked) Engine.AssignNetworkPlayer(playerIdx+1, ""); else if (g_PlayerAssignments.local.player == playerIdx+1) g_PlayerAssignments.local.player = -1; g_GameAttributes.settings.PlayerData[playerIdx].AI = choice.startsWith("ai:") ? choice.substr(3) : ""; } else swapPlayers(choice.substr("guid:".length), playerIdx); }, "autocomplete": 100, }, "playerTeam": { "labels": (playerIdx) => g_PlayerTeamList.label, "ids": (playerIdx) => g_PlayerTeamList.id, "default": (playerIdx) => 0, "defined": (playerIdx) => g_GameAttributes.settings.PlayerData[playerIdx].Team !== undefined, "get": (playerIdx) => g_GameAttributes.settings.PlayerData[playerIdx].Team, "select": (selectedIdx, playerIdx) => { g_GameAttributes.settings.PlayerData[playerIdx].Team = selectedIdx - 1; }, "enabled": () => g_GameAttributes.mapType != "scenario", }, "playerCiv": { "tooltip": (hoverIdx, playerIdx) => g_PlayerCivList.tooltip[hoverIdx] || translate("Choose the civilization for this player"), "labels": (playerIdx) => g_PlayerCivList.name, "colors": (playerIdx) => g_PlayerCivList.color, "ids": (playerIdx) => g_PlayerCivList.code, "default": (playerIdx) => 0, "defined": (playerIdx) => g_GameAttributes.settings.PlayerData[playerIdx].Civ !== undefined, "get": (playerIdx) => g_GameAttributes.settings.PlayerData[playerIdx].Civ, "select": (selectedIdx, playerIdx) => { g_GameAttributes.settings.PlayerData[playerIdx].Civ = g_PlayerCivList.code[selectedIdx]; }, "enabled": () => g_GameAttributes.mapType != "scenario", "autocomplete": 90, }, "playerColorPicker": { "labels": (playerIdx) => g_PlayerColorPickerList.map(color => "■"), "colors": (playerIdx) => g_PlayerColorPickerList.map(color => rgbToGuiColor(color)), "ids": (playerIdx) => g_PlayerColorPickerList.map((color, index) => index), "default": (playerIdx) => playerIdx, "defined": (playerIdx) => g_GameAttributes.settings.PlayerData[playerIdx].Color !== undefined, "get": (playerIdx) => g_PlayerColorPickerList.indexOf(g_GameAttributes.settings.PlayerData[playerIdx].Color), "select": (selectedIdx, playerIdx) => { let playerData = g_GameAttributes.settings.PlayerData; // If someone else has that color, give that player the old color let sameColorPData = playerData.find(pData => sameColor(g_PlayerColorPickerList[selectedIdx], pData.Color)); if (sameColorPData) sameColorPData.Color = playerData[playerIdx].Color; playerData[playerIdx].Color = g_PlayerColorPickerList[selectedIdx]; ensureUniquePlayerColors(playerData); }, "enabled": () => g_GameAttributes.mapType != "scenario", }, }; /** * Contains the logic of all boolean gamesettings. */ var g_Checkboxes = Object.assign( {}, g_VictoryConditions.reduce((obj, victoryCondition) => { obj[victoryCondition.Name] = { "title": () => victoryCondition.Title, "tooltip": () => victoryCondition.Description, // Defaults are set in supplementDefault directly from g_VictoryConditions since we use an array "defined": () => true, "get": () => g_GameAttributes.settings.VictoryConditions.indexOf(victoryCondition.Name) != -1, "set": checked => { if (checked) { g_GameAttributes.settings.VictoryConditions.push(victoryCondition.Name); if (victoryCondition.ChangeOnChecked) for (let setting in victoryCondition.ChangeOnChecked) g_Checkboxes[setting].set(victoryCondition.ChangeOnChecked[setting]); } else g_GameAttributes.settings.VictoryConditions = g_GameAttributes.settings.VictoryConditions.filter(victoryConditionName => victoryConditionName != victoryCondition.Name); }, "enabled": () => g_GameAttributes.mapType != "scenario" && (!victoryCondition.DisabledWhenChecked || victoryCondition.DisabledWhenChecked.every(victoryConditionName => g_GameAttributes.settings.VictoryConditions.indexOf(victoryConditionName) == -1)) }; return obj; }, {}), { "regicideGarrison": { "title": () => translate("Hero Garrison"), "tooltip": () => translate("Toggle whether heroes can be garrisoned."), "default": () => false, "defined": () => g_GameAttributes.settings.RegicideGarrison !== undefined, "get": () => g_GameAttributes.settings.RegicideGarrison, "set": checked => { g_GameAttributes.settings.RegicideGarrison = checked; }, "hidden": () => g_GameAttributes.settings.VictoryConditions.indexOf("regicide") == -1, "enabled": () => g_GameAttributes.mapType != "scenario", "initOrder": 1000 }, "nomad": { "title": () => translate("Nomad"), "tooltip": () => translate("In Nomad mode, players start with only few units and have to find a suitable place to build their city. Ceasefire is recommended."), "default": () => false, "defined": () => g_GameAttributes.settings.Nomad !== undefined, "get": () => g_GameAttributes.settings.Nomad, "set": checked => { g_GameAttributes.settings.Nomad = checked; }, "hidden": () => g_GameAttributes.mapType != "random", "initOrder": 1000 }, "revealMap": { "title": () => // Translation: Make sure to differentiate between the revealed map and explored map settings! translate("Revealed Map"), "tooltip": // Translation: Make sure to differentiate between the revealed map and explored map settings! () => translate("Toggle revealed map (see everything)."), "default": () => false, "defined": () => g_GameAttributes.settings.RevealMap !== undefined, "get": () => g_GameAttributes.settings.RevealMap, "set": checked => { g_GameAttributes.settings.RevealMap = checked; if (checked) g_Checkboxes.exploreMap.set(true); }, "enabled": () => g_GameAttributes.mapType != "scenario", "initOrder": 1000 }, "exploreMap": { "title": // Translation: Make sure to differentiate between the revealed map and explored map settings! () => translate("Explored Map"), "tooltip": // Translation: Make sure to differentiate between the revealed map and explored map settings! () => translate("Toggle explored map (see initial map)."), "default": () => false, "defined": () => g_GameAttributes.settings.ExploreMap !== undefined, "get": () => g_GameAttributes.settings.ExploreMap, "set": checked => { g_GameAttributes.settings.ExploreMap = checked; }, "enabled": () => g_GameAttributes.mapType != "scenario" && !g_GameAttributes.settings.RevealMap, "initOrder": 1000 }, "disableTreasures": { "title": () => translate("Disable Treasures"), "tooltip": () => translate("Disable all treasures on the map."), "default": () => false, "defined": () => g_GameAttributes.settings.DisableTreasures !== undefined, "get": () => g_GameAttributes.settings.DisableTreasures, "set": checked => { g_GameAttributes.settings.DisableTreasures = checked; }, "enabled": () => g_GameAttributes.mapType != "scenario", "initOrder": 1000 }, "disableSpies": { "title": () => translate("Disable Spies"), "tooltip": () => translate("Disable spies during the game."), "default": () => false, "defined": () => g_GameAttributes.settings.DisableSpies !== undefined, "get": () => g_GameAttributes.settings.DisableSpies, "set": checked => { g_GameAttributes.settings.DisableSpies = checked; }, "enabled": () => g_GameAttributes.mapType != "scenario", "initOrder": 1000 }, "lockTeams": { "title": () => translate("Teams Locked"), "tooltip": () => translate("Toggle locked teams."), "default": () => Engine.HasXmppClient(), "defined": () => g_GameAttributes.settings.LockTeams !== undefined, "get": () => g_GameAttributes.settings.LockTeams, "set": checked => { g_GameAttributes.settings.LockTeams = checked; g_GameAttributes.settings.LastManStanding = false; }, "enabled": () => g_GameAttributes.mapType != "scenario" && !g_GameAttributes.settings.RatingEnabled, "initOrder": 1000 }, "lastManStanding": { "title": () => translate("Last Man Standing"), "tooltip": () => translate("Toggle whether the last remaining player or the last remaining set of allies wins."), "default": () => false, "defined": () => g_GameAttributes.settings.LastManStanding !== undefined, "get": () => g_GameAttributes.settings.LastManStanding, "set": checked => { g_GameAttributes.settings.LastManStanding = checked; }, "enabled": () => g_GameAttributes.mapType != "scenario" && !g_GameAttributes.settings.LockTeams, "initOrder": 1000 }, "enableCheats": { "title": () => translate("Cheats"), "tooltip": () => translate("Toggle the usability of cheats."), "default": () => !g_IsNetworked, "hidden": () => !g_IsNetworked, "defined": () => g_GameAttributes.settings.CheatsEnabled !== undefined, "get": () => g_GameAttributes.settings.CheatsEnabled, "set": checked => { g_GameAttributes.settings.CheatsEnabled = !g_IsNetworked || checked && !g_GameAttributes.settings.RatingEnabled; }, "enabled": () => !g_GameAttributes.settings.RatingEnabled, "initOrder": 1000 }, "enableRating": { "title": () => translate("Rated Game"), "tooltip": () => translate("Toggle if this game will be rated for the leaderboard."), "default": () => Engine.HasXmppClient(), "hidden": () => !Engine.HasXmppClient(), "defined": () => g_GameAttributes.settings.RatingEnabled !== undefined, "get": () => !!g_GameAttributes.settings.RatingEnabled, "set": checked => { g_GameAttributes.settings.RatingEnabled = Engine.HasXmppClient() ? checked : undefined; Engine.SetRankedGame(!!g_GameAttributes.settings.RatingEnabled); if (checked) { g_Checkboxes.lockTeams.set(true); g_Checkboxes.enableCheats.set(false); } }, "initOrder": 1000 }, } ); /** * For setting up arbitrary GUI objects. */ var g_MiscControls = { "chatPanel": { "hidden": () => { if (!g_IsNetworked) return true; let size = Engine.GetGUIObjectByName("chatPanel").getComputedSize(); return size.right - size.left < g_MinChatWidth; }, }, "chatInput": { "tooltip": () => colorizeAutocompleteHotkey(translate("Press %(hotkey)s to autocomplete playernames or settings.")), }, "cheatWarningText": { "hidden": () => !g_IsNetworked || !g_GameAttributes.settings.CheatsEnabled, }, "cancelGame": { "tooltip": () => Engine.HasXmppClient() ? translate("Return to the lobby.") : translate("Return to the main menu."), }, "startGame": { "caption": () => g_IsController ? translate("Start Game!") : g_ReadyData[g_IsReady].caption, "onPress": () => function() { if (g_IsController) launchGame(); else toggleReady(); }, "onPressRight": () => function() { if (!g_IsController && g_IsReady) setReady(0, true); }, "tooltip": (hoverIdx) => !g_IsController ? g_ReadyData[g_IsReady].tooltip : !g_IsNetworked || Object.keys(g_PlayerAssignments).every(guid => g_PlayerAssignments[guid].status || g_PlayerAssignments[guid].player == -1) ? translate("Start a new game with the current settings.") : translate("Start a new game with the current settings (disabled until all players are ready)"), "enabled": () => !g_GameStarted && ( !g_IsController || Object.keys(g_PlayerAssignments).every(guid => g_PlayerAssignments[guid].status || g_PlayerAssignments[guid].player == -1 || guid == Engine.GetPlayerGUID() && g_IsController)), "hidden": () => !g_PlayerAssignments[Engine.GetPlayerGUID()] || g_PlayerAssignments[Engine.GetPlayerGUID()].player == -1 && !g_IsController, }, "civInfoButton": { "tooltip": () => sprintf( translate("%(hotkey_civinfo)s / %(hotkey_structree)s: View History / Structure Tree\nLast opened will be reopened on click."), { "hotkey_civinfo": colorizeHotkey("%(hotkey)s", "civinfo"), "hotkey_structree": colorizeHotkey("%(hotkey)s", "structree") }), "onPress": () => function() { Engine.PushGuiPage(g_CivInfo.page, { "civ": g_CivInfo.code, "callback": "storeCivInfoPage" }); } }, "civResetButton": { "hidden": () => g_GameAttributes.mapType == "scenario" || !g_IsController, }, "teamResetButton": { "hidden": () => g_GameAttributes.mapType == "scenario" || !g_IsController, }, "lobbyButton": { "onPress": () => function() { if (Engine.HasXmppClient()) Engine.PushGuiPage("page_lobby.xml", { "dialog": true }); }, "hidden": () => !Engine.HasXmppClient() }, "spTips": { "hidden": () => { let settingsPanel = Engine.GetGUIObjectByName("settingsPanel"); let spTips = Engine.GetGUIObjectByName("spTips"); return g_IsNetworked || Engine.ConfigDB_GetValue("user", "gui.gamesetup.enabletips") !== "true" || spTips.size.right > settingsPanel.getComputedSize().left; } } }; /** * Contains gui elements that are repeated for every player. */ var g_PlayerMiscElements = { "playerBox": { "size": (playerIdx) => ["0", 32 * playerIdx, "100%", 32 * (playerIdx + 1)].join(" "), }, "playerName": { "caption": (playerIdx) => { let pData = g_GameAttributes.settings.PlayerData[playerIdx]; let assignedGUID = Object.keys(g_PlayerAssignments).find( guid => g_PlayerAssignments[guid].player == playerIdx + 1); let name = translate(pData.Name || g_DefaultPlayerData[playerIdx].Name); if (g_IsNetworked) name = coloredText(name, g_ReadyData[assignedGUID ? g_PlayerAssignments[assignedGUID].status : 2].color); return name; }, }, "playerColor": { "sprite": (playerIdx) => "color:" + rgbToGuiColor(g_GameAttributes.settings.PlayerData[playerIdx].Color, 100), }, "playerConfig": { "hidden": (playerIdx) => !g_GameAttributes.settings.PlayerData[playerIdx].AI, "onPress": (playerIdx) => function() { openAIConfig(playerIdx); }, "tooltip": (playerIdx) => sprintf(translate("Configure AI: %(description)s."), { "description": translateAISettings(g_GameAttributes.settings.PlayerData[playerIdx]) }), }, }; /** * Initializes some globals without touching the GUI. * * @param {Object} attribs - context data sent by the lobby / mainmenu */ function init(attribs) { if (!g_Settings) { cancelSetup(); return; } g_IsTutorial = !!attribs.tutorial; g_ServerName = attribs.serverName; g_ServerPort = attribs.serverPort; g_StunEndpoint = attribs.stunEndpoint; if (!g_IsNetworked) g_PlayerAssignments = { "local": { "name": singleplayerName(), "player": 1 } }; // Replace empty playername when entering a singleplayermatch for the first time if (!g_IsNetworked) saveSettingAndWriteToUserConfig("playername.singleplayer", singleplayerName()); initDefaults(); supplementDefaults(); setTimeout(displayGamestateNotifications, 1000); } function initDefaults() { // Remove gaia from both arrays g_DefaultPlayerData = clone(g_Settings.PlayerDefaults.slice(1)); let aiDifficulty = +Engine.ConfigDB_GetValue("user", "gui.gamesetup.aidifficulty"); let aiBehavior = Engine.ConfigDB_GetValue("user", "gui.gamesetup.aibehavior"); // Don't change the underlying defaults file, as Atlas uses that file too for (let i in g_DefaultPlayerData) { g_DefaultPlayerData[i].Civ = "random"; g_DefaultPlayerData[i].Team = -1; g_DefaultPlayerData[i].AIDiff = aiDifficulty; g_DefaultPlayerData[i].AIBehavior = aiBehavior; } deepfreeze(g_DefaultPlayerData); } /** * Sets default values for all g_GameAttribute settings which don't have a value set. */ function supplementDefaults() { g_GameAttributes.settings.VictoryConditions = g_GameAttributes.settings.VictoryConditions || g_VictoryConditions.filter(victoryCondition => !!victoryCondition.Default).map(victoryCondition => victoryCondition.Name); for (let dropdown in g_Dropdowns) if (!g_Dropdowns[dropdown].defined()) g_Dropdowns[dropdown].select(g_Dropdowns[dropdown].default()); for (let checkbox in g_Checkboxes) if (!g_Checkboxes[checkbox].defined()) g_Checkboxes[checkbox].set(g_Checkboxes[checkbox].default()); for (let dropdown in g_PlayerDropdowns) for (let i = 0; i < g_GameAttributes.settings.PlayerData.length; ++i) if (!isControlArrayElementHidden(i) && !g_PlayerDropdowns[dropdown].defined(i)) g_PlayerDropdowns[dropdown].select(g_PlayerDropdowns[dropdown].default(i), i); } /** * Called after the first tick. */ function initGUIObjects() { initSettingObjects(); initSettingsTabButtons(); initSPTips(); loadPersistMatchSettings(); updateGameAttributes(); sendRegisterGameStanzaImmediate(); if (g_IsTutorial) { launchTutorial(); return; } // Don't lift the curtain until the controls are updated the first time if (!g_IsNetworked) hideLoadingWindow(); } /** * @param {number} dt - Time in milliseconds since last call. */ function slideSettingsPanel(dt) { let slideSpeed = Engine.ConfigDB_GetValue("user", "gui.gamesetup.settingsslide") == "true" ? g_SlideSpeed : Infinity; let settingsPanel = Engine.GetGUIObjectByName("settingsPanel"); let rightBorder = Engine.GetGUIObjectByName("settingTabButtons").size.left; let offset = 0; if (g_TabCategorySelected === undefined) { let maxOffset = rightBorder - settingsPanel.size.left; if (maxOffset > 0) offset = Math.min(slideSpeed * dt, maxOffset); } else if (rightBorder > settingsPanel.size.right) offset = Math.min(slideSpeed * dt, rightBorder - settingsPanel.size.right); else { let maxOffset = settingsPanel.size.right - rightBorder; if (maxOffset > 0) offset = -Math.min(slideSpeed * dt, maxOffset); } updateSettingsPanelPosition(offset); } /** * Directly change the position of the settingsPanel. * @param {number} offset - Number of pixels the panel needs to move. */ function updateSettingsPanelPosition(offset) { if (!offset) return; let settingsPanel = Engine.GetGUIObjectByName("settingsPanel"); let settingsPanelSize = settingsPanel.size; settingsPanelSize.left += offset; settingsPanelSize.right += offset; settingsPanel.size = settingsPanelSize; let settingsBackground = Engine.GetGUIObjectByName("settingsBackground"); let backgroundSize = settingsBackground.size; backgroundSize.left = settingsPanelSize.left; settingsBackground.size = backgroundSize; let chatPanel = Engine.GetGUIObjectByName("chatPanel"); let chatSize = chatPanel.size; chatSize.right = settingsPanelSize.left - g_ChatSettingsMargin; chatPanel.size = chatSize; chatPanel.hidden = g_MiscControls.chatPanel.hidden(); let spTips = Engine.GetGUIObjectByName("spTips"); spTips.hidden = g_MiscControls.spTips.hidden(); } function hideLoadingWindow() { let loadingWindow = Engine.GetGUIObjectByName("loadingWindow"); if (loadingWindow.hidden) return; loadingWindow.hidden = true; Engine.GetGUIObjectByName("setupWindow").hidden = false; if (!Engine.GetGUIObjectByName("chatPanel").hidden) Engine.GetGUIObjectByName("chatInput").focus(); } /** * Settings under the settings tabs use a generic name. * Player settings use custom names. */ function getGUIObjectNameFromSetting(setting) { let idxOffset = 0; for (let category of g_SettingsTabsGUI) { let idx = category.settings.indexOf(setting); if (idx != -1) return [ "setting", g_Dropdowns[setting] ? "Dropdown" : "Checkbox", "[" + (idx + idxOffset) + "]" ]; idxOffset += category.settings.length; } // Assume there is a GUI object with exactly that setting name return [setting, "", ""]; } /** * Initialize all settings dropdowns and checkboxes. */ function initSettingObjects() { // Copy all initOrder values into one object let initOrder = {}; for (let dropdown in g_Dropdowns) initOrder[dropdown] = g_Dropdowns[dropdown].initOrder; for (let checkbox in g_Checkboxes) initOrder[checkbox] = g_Checkboxes[checkbox].initOrder; // Sort the object on initOrder so we can init the settings in an arbitrary order for (let setting of Object.keys(initOrder).sort((a, b) => initOrder[a] - initOrder[b])) if (g_Dropdowns[setting]) initDropdown(setting); else if (g_Checkboxes[setting]) initCheckbox(setting); else warn('The setting "' + setting + '" is not defined.'); for (let dropdown in g_PlayerDropdowns) initPlayerDropdowns(dropdown); } function initDropdown(name, playerIdx) { let [guiName, guiType, guiIdx] = getGUIObjectNameFromSetting(name); let idxName = playerIdx === undefined ? "" : "[" + playerIdx + "]"; let data = (playerIdx === undefined ? g_Dropdowns : g_PlayerDropdowns)[name]; let dropdown = Engine.GetGUIObjectByName(guiName + guiType + guiIdx + idxName); dropdown.list = data.labels(playerIdx).map((label, id) => data.colors && data.colors(playerIdx) ? coloredText(label, data.colors(playerIdx)[id]) : label); dropdown.list_data = data.ids(playerIdx); dropdown.onSelectionChange = function() { if (!g_IsController || g_IsInGuiUpdate || !this.list_data[this.selected] || data.hidden && data.hidden(playerIdx) || data.enabled && !data.enabled(playerIdx)) return; data.select(this.selected, playerIdx); supplementDefaults(); updateGameAttributes(); }; if (data.tooltip) dropdown.onHoverChange = function() { this.tooltip = data.tooltip(this.hovered, playerIdx); }; } function initPlayerDropdowns(name) { for (let i = 0; i < g_MaxPlayers; ++i) initDropdown(name, i); } function initCheckbox(name) { let [guiName, guiType, guiIdx] = getGUIObjectNameFromSetting(name); Engine.GetGUIObjectByName(guiName + guiType + guiIdx).onPress = function() { let obj = g_Checkboxes[name]; if (!g_IsController || g_IsInGuiUpdate || obj.enabled && !obj.enabled() || obj.hidden && obj.hidden()) return; obj.set(this.checked); supplementDefaults(); updateGameAttributes(); }; } function initSettingsTabButtons() { for (let tab in g_SettingsTabsGUI) g_SettingsTabsGUI[tab].tooltip = sprintf(translate("Toggle the %(name)s settings tab."), { "name": g_SettingsTabsGUI[tab].label }) + colorizeHotkey("\n" + translate("Use %(hotkey)s to move a settings tab down."), "tab.next") + colorizeHotkey("\n" + translate("Use %(hotkey)s to move a settings tab up."), "tab.prev"); let settingTabButtons = Engine.GetGUIObjectByName("settingTabButtons"); let settingTabButtonsSize = settingTabButtons.size; settingTabButtonsSize.bottom = settingTabButtonsSize.top + g_SettingsTabsGUI.length * (g_TabButtonHeight + g_TabButtonDist); settingTabButtonsSize.right = g_MiscControls.lobbyButton.hidden() ? settingTabButtonsSize.right : Engine.GetGUIObjectByName("lobbyButton").size.left - g_LobbyButtonSpacing; settingTabButtons.size = settingTabButtonsSize; let settingTabButtonsBackground = Engine.GetGUIObjectByName("settingTabButtonsBackground"); settingTabButtonsBackground.size = settingTabButtonsSize; let gameDescription = Engine.GetGUIObjectByName("mapInfoDescriptionFrame"); let gameDescriptionSize = gameDescription.size; gameDescriptionSize.top = settingTabButtonsSize.bottom + 3; gameDescription.size = gameDescriptionSize; if (!g_IsController) { g_TabCategorySelected = undefined; updateSettingsPanelPosition(Engine.GetGUIObjectByName("settingTabButtons").size.left - Engine.GetGUIObjectByName("settingsPanel").size.left); } placeTabButtons( g_SettingsTabsGUI, g_TabButtonHeight, g_TabButtonDist, category => { selectPanel(category == g_TabCategorySelected ? undefined : category); }, () => { updateGUIObjects(); Engine.GetGUIObjectByName("settingsPanel").hidden = false; }); } function initSPTips() { if (g_IsNetworked || Engine.ConfigDB_GetValue("user", "gui.gamesetup.enabletips") !== "true") return; Engine.GetGUIObjectByName("spTips").hidden = false; Engine.GetGUIObjectByName("displaySPTips").checked = true; Engine.GetGUIObjectByName("aiTips").caption = Engine.TranslateLines(Engine.ReadFile("gui/gamesetup/ai.txt")); } /** * Distribute the currently visible settings over the settings panel. * First calculate the number of columns required, then place the objects. */ function distributeSettings() { let setupWindowSize = Engine.GetGUIObjectByName("setupWindow").getComputedSize(); let columnWidth = Math.min( g_MaxColumnWidth, (setupWindowSize.right - setupWindowSize.left + Engine.GetGUIObjectByName("settingTabButtons").size.left) / 2); let settingsPanel = Engine.GetGUIObjectByName("settingsPanel"); let actualSettingsPanelSize = settingsPanel.getComputedSize(); let maxPerColumn = Math.floor((actualSettingsPanelSize.bottom - actualSettingsPanelSize.top) / g_SettingHeight); let childCount = settingsPanel.children.filter(child => !child.hidden).length; let perColumn = childCount / Math.ceil(childCount / maxPerColumn); let yPos = g_SettingDist; let column = 0; let thisColumn = 0; let settingsPanelSize = settingsPanel.size; for (let child of settingsPanel.children) { if (child.hidden) continue; if (thisColumn >= perColumn) { yPos = g_SettingDist; ++column; thisColumn = 0; } let childSize = child.size; child.size = new GUISize( column * columnWidth, yPos, column * columnWidth + columnWidth - 10, yPos + g_SettingHeight - g_SettingDist); yPos += g_SettingHeight; ++thisColumn; } settingsPanelSize.right = settingsPanelSize.left + (column + 1) * columnWidth; settingsPanel.size = settingsPanelSize; } /** * Called when the client disconnects. * The other cases from NetClient should never occur in the gamesetup. */ function handleNetStatusMessage(message) { if (message.status != "disconnected") { error("Unrecognised netstatus type " + message.status); return; } cancelSetup(); reportDisconnect(message.reason, true); } /** * Called whenever a client clicks on ready (or not ready). */ function handleReadyMessage(message) { --g_ReadyChanged; if (g_ReadyChanged < 1 && g_PlayerAssignments[message.guid].player != -1) addChatMessage({ "type": "ready", "status": message.status, "guid": message.guid }); g_PlayerAssignments[message.guid].status = message.status; updateGUIObjects(); } /** * Called after every player is ready and the host decided to finally start the game. */ function handleGamestartMessage(message) { // Immediately inform the lobby server instead of waiting for the load to finish if (g_IsController && Engine.HasXmppClient()) { sendRegisterGameStanzaImmediate(); let clients = formatClientsForStanza(); Engine.SendChangeStateGame(clients.connectedPlayers, clients.list); } Engine.SwitchGuiPage("page_loading.xml", { "attribs": g_GameAttributes, "playerAssignments": g_PlayerAssignments }); } /** * Called whenever the host changed any setting. */ function handleGamesetupMessage(message) { if (!message.data) return; g_GameAttributes = message.data; if (!!g_GameAttributes.settings.RatingEnabled) { g_GameAttributes.settings.CheatsEnabled = false; g_GameAttributes.settings.LockTeams = true; g_GameAttributes.settings.LastManStanding = false; } Engine.SetRankedGame(!!g_GameAttributes.settings.RatingEnabled); resetReadyData(); updateGUIObjects(); hideLoadingWindow(); } /** * Called whenever a client joins/leaves or any gamesetting is changed. */ function handlePlayerAssignmentMessage(message) { let playerChange = false; for (let guid in message.newAssignments) if (!g_PlayerAssignments[guid]) { onClientJoin(guid, message.newAssignments); playerChange = true; } for (let guid in g_PlayerAssignments) if (!message.newAssignments[guid]) { onClientLeave(guid); playerChange = true; } g_PlayerAssignments = message.newAssignments; sanitizePlayerData(g_GameAttributes.settings.PlayerData); updateGUIObjects(); if (playerChange) sendRegisterGameStanzaImmediate(); else sendRegisterGameStanza(); } function onClientJoin(newGUID, newAssignments) { let playername = newAssignments[newGUID].name; addChatMessage({ "type": "connect", "guid": newGUID, "username": playername }); + if (newGUID != Engine.GetPlayerGUID() && Object.keys(g_PlayerAssignments).length) + soundNotification("gamesetup.join"); + let isRejoiningPlayer = newAssignments[newGUID].player != -1; // Assign the client (or only buddies if prefered) to an unused playerslot and rejoining players to their old slot if (!isRejoiningPlayer && playername != newAssignments[Engine.GetPlayerGUID()].name) { let assignOption = Engine.ConfigDB_GetValue("user", "gui.gamesetup.assignplayers"); if (assignOption == "disabled" || assignOption == "buddies" && g_Buddies.indexOf(splitRatingFromNick(playername).nick) == -1) return; } let freeSlot = g_GameAttributes.settings.PlayerData.findIndex((v, i) => Object.keys(g_PlayerAssignments).every(guid => g_PlayerAssignments[guid].player != i + 1) ); // Client is not and cannot become assigned as player if (!isRejoiningPlayer && freeSlot == -1) return; // Assign the joining client to the free slot if (g_IsController && !isRejoiningPlayer) Engine.AssignNetworkPlayer(freeSlot + 1, newGUID); resetReadyData(); } function onClientLeave(guid) { addChatMessage({ "type": "disconnect", "guid": guid }); if (g_PlayerAssignments[guid].player != -1) resetReadyData(); } /** * Doesn't translate, so that lobby clients can do that locally * (even if they don't have that map). */ function getMapDisplayName(map) { if (map == "random") return map; let mapData = loadMapData(map); if (!mapData || !mapData.settings || !mapData.settings.Name) return map; return mapData.settings.Name; } function getMapPreview(map) { let biomePreview = g_GameAttributes.settings.Biome && getBiomePreview(map, g_GameAttributes.settings.Biome); if (biomePreview) return biomePreview; let mapData = loadMapData(map); if (!mapData || !mapData.settings || !mapData.settings.Preview) return "nopreview.png"; return mapData.settings.Preview; } /** * Filter maps with filterFunc and by chosen map type. * * @param {function} filterFunc - Filter function that should be applied. * @return {Array} the maps that match the filterFunc and the chosen map type. */ function getFilteredMaps(filterFunc) { if (!g_MapPath[g_GameAttributes.mapType]) { error("Unexpected map type: " + g_GameAttributes.mapType); return []; } let maps = []; // TODO: Should verify these are valid maps before adding to list for (let mapFile of listFiles(g_GameAttributes.mapPath, g_GameAttributes.mapType == "random" ? ".json" : ".xml", false)) { if (mapFile.startsWith("_")) continue; let file = g_GameAttributes.mapPath + mapFile; let mapData = loadMapData(file); if (!mapData.settings || filterFunc && !filterFunc(mapData.settings.Keywords || [])) continue; maps.push({ "file": file, "name": translate(getMapDisplayName(file)), "color": g_ColorRegular, "description": translate(mapData.settings.Description) }); } return maps; } /** * Initialize the dropdown containing all map filters for the selected maptype. */ function reloadMapFilterList() { g_MapFilterList = prepareForDropdown(g_MapFilters.filter( mapFilter => getFilteredMaps(mapFilter.filter).length )); initDropdown("mapFilter"); reloadMapList(); } /** * Initialize the dropdown containing all maps for the selected maptype and mapfilter. */ function reloadMapList() { let filterID = g_MapFilterList.id.findIndex(id => id == g_GameAttributes.mapFilter); let filterFunc = g_MapFilterList.filter[filterID]; let mapList = getFilteredMaps(filterFunc).sort(sortNameIgnoreCase); if (g_GameAttributes.mapType == "random") mapList.unshift({ "file": "random", "name": translateWithContext("map selection", "Random"), "color": g_ColorRandom, "description": translate("Pick any of the given maps at random.") }); g_MapSelectionList = prepareForDropdown(mapList); initDropdown("mapSelection"); } /** * Initialize the dropdowns specific to each map. */ function reloadMapSpecific() { reloadBiomeList(); reloadTriggerDifficulties(); } function reloadBiomeList() { let biomeList; if (g_GameAttributes.mapType == "random" && g_GameAttributes.settings.SupportedBiomes) { if (typeof g_GameAttributes.settings.SupportedBiomes == "string") biomeList = g_Settings.Biomes.filter(biome => biome.Id.startsWith(g_GameAttributes.settings.SupportedBiomes)); else biomeList = g_Settings.Biomes.filter( biome => g_GameAttributes.settings.SupportedBiomes.indexOf(biome.Id) != -1); } g_BiomeList = biomeList && prepareForDropdown( [{ "Id": "random", "Title": translateWithContext("biome", "Random"), "Description": translate("Pick a biome at random."), "Color": g_ColorRandom }].concat(biomeList.map(biome => ({ "Id": biome.Id, "Title": biome.Title, "Description": biome.Description, "Color": g_ColorRegular })))); initDropdown("biome"); updateGUIDropdown("biome"); } function reloadTriggerDifficulties() { g_TriggerDifficultyList = undefined; if (!g_GameAttributes.settings.SupportedTriggerDifficulties) return; let triggerDifficultyList; if (g_GameAttributes.settings.SupportedTriggerDifficulties.Values === true) triggerDifficultyList = g_Settings.TriggerDifficulties; else { triggerDifficultyList = g_Settings.TriggerDifficulties.filter( diff => g_GameAttributes.settings.SupportedTriggerDifficulties.Values.indexOf(diff.Name) != -1); if (!triggerDifficultyList.length) return; } g_TriggerDifficultyList = prepareForDropdown( triggerDifficultyList.map(diff => ({ "Id": diff.Difficulty, "Title": diff.Title, "Description": diff.Tooltip, "Default": diff.Name == g_GameAttributes.settings.SupportedTriggerDifficulties.Default }))); initDropdown("triggerDifficulty"); updateGUIDropdown("triggerDifficulty"); } function reloadGameSpeedChoices() { g_GameSpeeds = getGameSpeedChoices(Object.keys(g_PlayerAssignments).every(guid => g_PlayerAssignments[guid].player == -1)); initDropdown("gameSpeed"); supplementDefaults(); } function loadMapData(name) { if (!name || !g_MapPath[g_GameAttributes.mapType]) return undefined; if (name == "random") return { "settings": { "Name": "", "Description": "" } }; if (!g_MapData[name]) g_MapData[name] = g_GameAttributes.mapType == "random" ? Engine.ReadJSONFile(name + ".json") : Engine.LoadMapSettings(name); return g_MapData[name]; } /** * Sets the gameattributes the way they were the last time the user left the gamesetup. */ function loadPersistMatchSettings() { if (!g_IsController || Engine.ConfigDB_GetValue("user", "persistmatchsettings") != "true" || g_IsTutorial) return; let settingsFile = g_IsNetworked ? g_MatchSettings_MP : g_MatchSettings_SP; if (!Engine.FileExists(settingsFile)) return; let data = Engine.ReadJSONFile(settingsFile); if (!data || !data.attributes || !data.attributes.settings) return; if (data.engine_info.engine_version != Engine.GetEngineInfo().engine_version || !hasSameMods(data.engine_info.mods, Engine.GetEngineInfo().mods)) return; g_IsInGuiUpdate = true; let mapName = data.attributes.map || ""; let mapSettings = data.attributes.settings; g_GameAttributes = data.attributes; if (!g_IsNetworked) mapSettings.CheatsEnabled = true; // Replace unselectable civs with random civ let playerData = mapSettings.PlayerData; if (playerData && g_GameAttributes.mapType != "scenario") for (let i in playerData) if (!g_CivData[playerData[i].Civ] || !g_CivData[playerData[i].Civ].SelectableInGameSetup) playerData[i].Civ = "random"; // Apply map settings let newMapData = loadMapData(mapName); if (newMapData && newMapData.settings) { for (let prop in newMapData.settings) mapSettings[prop] = newMapData.settings[prop]; if (playerData) mapSettings.PlayerData = playerData; } if (mapSettings.PlayerData) sanitizePlayerData(mapSettings.PlayerData); // Reload, as the maptype or mapfilter might have changed reloadMapFilterList(); reloadMapSpecific(); g_GameAttributes.settings.RatingEnabled = Engine.HasXmppClient(); Engine.SetRankedGame(g_GameAttributes.settings.RatingEnabled); supplementDefaults(); g_IsInGuiUpdate = false; } function savePersistMatchSettings() { if (g_IsTutorial) return; Engine.WriteJSONFile( g_IsNetworked ? g_MatchSettings_MP : g_MatchSettings_SP, { "attributes": // Delete settings if disabled, so that players are not confronted with old settings after enabling the setting again Engine.ConfigDB_GetValue("user", "persistmatchsettings") == "true" ? g_GameAttributes : {}, "engine_info": Engine.GetEngineInfo() }); } function sanitizePlayerData(playerData) { // Remove gaia if (playerData.length && !playerData[0]) playerData.shift(); playerData.forEach((pData, index) => { // Use defaults if the map doesn't specify a value for (let prop in g_DefaultPlayerData[index]) if (!(prop in pData)) pData[prop] = clone(g_DefaultPlayerData[index][prop]); // Replace colors with the best matching color of PlayerDefaults if (g_GameAttributes.mapType != "scenario") { let colorDistances = g_PlayerColorPickerList.map(color => colorDistance(color, pData.Color)); let smallestDistance = colorDistances.find(distance => colorDistances.every(distance2 => (distance2 >= distance))); pData.Color = g_PlayerColorPickerList.find(color => colorDistance(color, pData.Color) == smallestDistance); } // If there is a player in that slot, then there can't be an AI if (Object.keys(g_PlayerAssignments).some(guid => g_PlayerAssignments[guid].player == index + 1)) pData.AI = ""; }); ensureUniquePlayerColors(playerData); } function cancelSetup() { if (g_IsController) savePersistMatchSettings(); Engine.DisconnectNetworkGame(); if (Engine.HasXmppClient()) { Engine.LobbySetPlayerPresence("available"); if (g_IsController) Engine.SendUnregisterGame(); Engine.SwitchGuiPage("page_lobby.xml", { "dialog": false }); } else Engine.SwitchGuiPage("page_pregame.xml"); } /** * Can't init the GUI before the first tick. * Process netmessages afterwards. */ function onTick() { if (!g_Settings) return; // First tick happens before first render, so don't load yet if (g_LoadingState == 0) ++g_LoadingState; else if (g_LoadingState == 1) { initGUIObjects(); ++g_LoadingState; } else if (g_LoadingState == 2) handleNetMessages(); updateTimers(); let now = Date.now(); let tickLength = now - g_LastTickTime; g_LastTickTime = now; slideSettingsPanel(tickLength); } /** * Handles all pending messages sent by the net client. */ function handleNetMessages() { while (g_IsNetworked) { let message = Engine.PollNetworkClient(); if (!message) break; log("Net message: " + uneval(message)); if (g_NetMessageTypes[message.type]) g_NetMessageTypes[message.type](message); else error("Unrecognised net message type " + message.type); } } /** * Called when the map or the number of players changes. */ function unassignInvalidPlayers(maxPlayers) { if (g_IsNetworked) // Remove invalid playerIDs from the servers playerassignments copy for (let playerID = +maxPlayers + 1; playerID <= g_MaxPlayers; ++playerID) Engine.AssignNetworkPlayer(playerID, ""); else if (g_PlayerAssignments.local.player > maxPlayers) g_PlayerAssignments.local.player = -1; } function ensureUniquePlayerColors(playerData) { for (let i = playerData.length - 1; i >= 0; --i) // If someone else has that color, assign an unused color if (playerData.some((pData, j) => i != j && sameColor(playerData[i].Color, pData.Color))) playerData[i].Color = g_PlayerColorPickerList.find(color => playerData.every(pData => !sameColor(color, pData.Color))); } function selectMap(name) { // Reset some map specific properties which are not necessarily redefined on each map for (let prop of ["TriggerScripts", "CircularMap", "Garrison", "DisabledTemplates", "Biome", "SupportedBiomes", "SupportedTriggerDifficulties", "TriggerDifficulty"]) g_GameAttributes.settings[prop] = undefined; let mapData = loadMapData(name); let mapSettings = mapData && mapData.settings ? clone(mapData.settings) : {}; if (g_GameAttributes.mapType != "random") delete g_GameAttributes.settings.Nomad; if (g_GameAttributes.mapType == "scenario") { delete g_GameAttributes.settings.RelicDuration; delete g_GameAttributes.settings.WonderDuration; delete g_GameAttributes.settings.LastManStanding; delete g_GameAttributes.settings.RegicideGarrison; } if (mapSettings.PlayerData) sanitizePlayerData(mapSettings.PlayerData); // Copy any new settings g_GameAttributes.map = name; g_GameAttributes.script = mapSettings.Script; if (g_GameAttributes.map !== "random") for (let prop in mapSettings) g_GameAttributes.settings[prop] = mapSettings[prop]; reloadMapSpecific(); unassignInvalidPlayers(g_GameAttributes.settings.PlayerData.length); supplementDefaults(); } function isControlArrayElementHidden(playerIdx) { return playerIdx !== undefined && playerIdx >= g_GameAttributes.settings.PlayerData.length; } /** * @param playerIdx - Only specified for dropdown arrays. */ function updateGUIDropdown(name, playerIdx = undefined) { let [guiName, guiType, guiIdx] = getGUIObjectNameFromSetting(name); let idxName = playerIdx === undefined ? "" : "[" + playerIdx + "]"; let dropdown = Engine.GetGUIObjectByName(guiName + guiType + guiIdx + idxName); let label = Engine.GetGUIObjectByName(guiName + "Text" + guiIdx + idxName); let frame = Engine.GetGUIObjectByName(guiName + "Frame" + guiIdx + idxName); let title = Engine.GetGUIObjectByName(guiName + "Title" + guiIdx + idxName); if (guiType == "Dropdown") Engine.GetGUIObjectByName(guiName + "Checkbox" + guiIdx).hidden = true; let indexHidden = isControlArrayElementHidden(playerIdx); let obj = (playerIdx === undefined ? g_Dropdowns : g_PlayerDropdowns)[name]; let hidden = indexHidden || obj.hidden && obj.hidden(playerIdx); let selected = hidden ? -1 : dropdown.list_data.indexOf(String(obj.get(playerIdx))); let enabled = !indexHidden && (!obj.enabled || obj.enabled(playerIdx)); dropdown.enabled = g_IsController && enabled; dropdown.hidden = !g_IsController || !enabled || hidden; dropdown.selected = selected; dropdown.tooltip = !indexHidden && obj.tooltip ? obj.tooltip(-1, playerIdx) : ""; if (frame) frame.hidden = hidden; if (title && obj.title && !indexHidden) title.caption = sprintf(translateWithContext("Title for specific setting", "%(setting)s:"), { "setting": obj.title(playerIdx) }); if (label && !indexHidden) { label.hidden = g_IsController && enabled || hidden; label.caption = selected == -1 ? translateWithContext("settings value", "Unknown") : dropdown.list[selected]; } } /** * Not used for the player assignments, so playerCheckboxes are not implemented, * hence no index. */ function updateGUICheckbox(name) { let obj = g_Checkboxes[name]; let checked = obj.get(); let hidden = obj.hidden && obj.hidden(); let enabled = !obj.enabled || obj.enabled(); let [guiName, guiType, guiIdx] = getGUIObjectNameFromSetting(name); let checkbox = Engine.GetGUIObjectByName(guiName + guiType + guiIdx); let label = Engine.GetGUIObjectByName(guiName + "Text" + guiIdx); let frame = Engine.GetGUIObjectByName(guiName + "Frame" + guiIdx); let title = Engine.GetGUIObjectByName(guiName + "Title" + guiIdx); if (guiType == "Checkbox") Engine.GetGUIObjectByName(guiName + "Dropdown" + guiIdx).hidden = true; checkbox.checked = checked; checkbox.enabled = g_IsController && enabled; checkbox.hidden = hidden || !g_IsController; checkbox.tooltip = obj.tooltip ? obj.tooltip() : ""; label.caption = checked ? translate("Yes") : translate("No"); label.hidden = hidden || g_IsController; if (frame) frame.hidden = hidden; if (title && obj.title) title.caption = sprintf(translate("%(setting)s:"), { "setting": obj.title() }); } function updateGUIMiscControl(name, playerIdx) { let idxName = playerIdx === undefined ? "" : "[" + playerIdx + "]"; let obj = (playerIdx === undefined ? g_MiscControls : g_PlayerMiscElements)[name]; let control = Engine.GetGUIObjectByName(name + idxName); if (!control) warn("No GUI object with name '" + name + "'"); let hide = isControlArrayElementHidden(playerIdx); control.hidden = hide; if (hide) return; for (let property in obj) control[property] = obj[property](playerIdx); } function launchGame() { if (!g_IsController) { error("Only host can start game"); return; } if (!g_GameAttributes.map || g_GameStarted) return; // Prevent reseting the readystate or calling this function twice g_GameStarted = true; updateGUIMiscControl("startGame"); savePersistMatchSettings(); // Select random map if (g_GameAttributes.map == "random") selectMap(pickRandom(g_Dropdowns.mapSelection.ids().slice(1))); if (g_GameAttributes.settings.Biome == "random") g_GameAttributes.settings.Biome = pickRandom( typeof g_GameAttributes.settings.SupportedBiomes == "string" ? g_BiomeList.Id.slice(1).filter(biomeID => biomeID.startsWith(g_GameAttributes.settings.SupportedBiomes)) : g_GameAttributes.settings.SupportedBiomes); g_GameAttributes.settings.VictoryScripts = g_GameAttributes.settings.VictoryConditions.reduce( (scripts, victoryConditionName) => scripts.concat(g_VictoryConditions[g_VictoryConditions.map(data => data.Name).indexOf(victoryConditionName)].Scripts.filter(script => scripts.indexOf(script) == -1)), []); g_GameAttributes.settings.TriggerScripts = g_GameAttributes.settings.VictoryScripts.concat(g_GameAttributes.settings.TriggerScripts || []); g_GameAttributes.settings.mapType = g_GameAttributes.mapType; // Get a unique array of selectable cultures let cultures = Object.keys(g_CivData).filter(civ => g_CivData[civ].SelectableInGameSetup).map(civ => g_CivData[civ].Culture); cultures = cultures.filter((culture, index) => cultures.indexOf(culture) === index); // Determine random civs and botnames for (let i in g_GameAttributes.settings.PlayerData) { // Pick a random civ of a random culture let chosenCiv = g_GameAttributes.settings.PlayerData[i].Civ || "random"; if (chosenCiv == "random") { let culture = pickRandom(cultures); chosenCiv = pickRandom(Object.keys(g_CivData).filter(civ => g_CivData[civ].Culture == culture && g_CivData[civ].SelectableInGameSetup)); } g_GameAttributes.settings.PlayerData[i].Civ = chosenCiv; // Pick one of the available botnames for the chosen civ if (g_GameAttributes.mapType === "scenario" || !g_GameAttributes.settings.PlayerData[i].AI) continue; let chosenName = pickRandom(g_CivData[chosenCiv].AINames); if (!g_IsNetworked) chosenName = translate(chosenName); // Count how many players use the chosenName let usedName = g_GameAttributes.settings.PlayerData.filter(pData => pData.Name && pData.Name.indexOf(chosenName) !== -1).length; g_GameAttributes.settings.PlayerData[i].Name = !usedName ? chosenName : sprintf(translate("%(playerName)s %(romanNumber)s"), { "playerName": chosenName, "romanNumber": g_RomanNumbers[usedName+1] }); } // Copy playernames for the purpose of replays for (let guid in g_PlayerAssignments) { let player = g_PlayerAssignments[guid]; if (player.player > 0) // not observer or GAIA g_GameAttributes.settings.PlayerData[player.player - 1].Name = player.name; } // Seed used for both map generation and simulation g_GameAttributes.settings.Seed = randIntExclusive(0, Math.pow(2, 32)); g_GameAttributes.settings.AISeed = randIntExclusive(0, Math.pow(2, 32)); // Used for identifying rated game reports for the lobby g_GameAttributes.matchID = Engine.GetMatchID(); if (g_IsNetworked) { Engine.SetNetworkGameAttributes(g_GameAttributes); Engine.StartNetworkGame(); } else { // Find the player ID which the user has been assigned to let playerID = -1; for (let i in g_GameAttributes.settings.PlayerData) { let assignBox = Engine.GetGUIObjectByName("playerAssignment[" + i + "]"); if (assignBox.list_data[assignBox.selected] == "guid:local") playerID = +i + 1; } Engine.StartGame(g_GameAttributes, playerID); Engine.SwitchGuiPage("page_loading.xml", { "attribs": g_GameAttributes, "playerAssignments": g_PlayerAssignments }); } } function launchTutorial() { g_GameAttributes.mapType = "scenario"; selectMap("maps/tutorials/starting_economy_walkthrough"); launchGame(); } /** * Don't set any attributes here, just show the changes in the GUI. * * Unless the mapsettings don't specify a property and the user didn't set it in g_GameAttributes previously. */ function updateGUIObjects() { g_IsInGuiUpdate = true; reloadMapFilterList(); reloadMapSpecific(); reloadGameSpeedChoices(); reloadPlayerAssignmentChoices(); // Hide exceeding dropdowns and checkboxes for (let setting of Engine.GetGUIObjectByName("settingsPanel").children) setting.hidden = true; // Show the relevant ones if (g_TabCategorySelected !== undefined) { for (let name in g_Dropdowns) if (g_SettingsTabsGUI[g_TabCategorySelected].settings.indexOf(name) != -1) updateGUIDropdown(name); for (let name in g_Checkboxes) if (g_SettingsTabsGUI[g_TabCategorySelected].settings.indexOf(name) != -1) updateGUICheckbox(name); } for (let i = 0; i < g_MaxPlayers; ++i) { for (let name in g_PlayerDropdowns) updateGUIDropdown(name, i); for (let name in g_PlayerMiscElements) updateGUIMiscControl(name, i); } for (let name in g_MiscControls) updateGUIMiscControl(name); updateGameDescription(); distributeSettings(); rightAlignCancelButton(); updateAutocompleteEntries(); g_IsInGuiUpdate = false; // Refresh AI config page if (g_LastViewedAIPlayer != -1) { Engine.PopGuiPage(); openAIConfig(g_LastViewedAIPlayer); } } function rightAlignCancelButton() { let offset = 10; let startGame = Engine.GetGUIObjectByName("startGame"); let right = startGame.hidden ? startGame.size.right : startGame.size.left - offset; let cancelGame = Engine.GetGUIObjectByName("cancelGame"); let cancelGameSize = cancelGame.size; let buttonWidth = cancelGameSize.right - cancelGameSize.left; cancelGameSize.right = right; right -= buttonWidth; for (let element of ["cheatWarningText", "onscreenToolTip"]) { let elementSize = Engine.GetGUIObjectByName(element).size; elementSize.right = right - (cancelGameSize.left - elementSize.right); Engine.GetGUIObjectByName(element).size = elementSize; } cancelGameSize.left = right; cancelGame.size = cancelGameSize; } function updateGameDescription() { setMapPreviewImage("mapPreview", getMapPreview(g_GameAttributes.map)); Engine.GetGUIObjectByName("mapInfoName").caption = translateMapTitle(getMapDisplayName(g_GameAttributes.map)); Engine.GetGUIObjectByName("mapInfoDescription").caption = getGameDescription(); } /** * Broadcast the changed settings to all clients and the lobbybot. */ function updateGameAttributes() { if (g_IsInGuiUpdate || !g_IsController) return; if (g_IsNetworked) { Engine.SetNetworkGameAttributes(g_GameAttributes); if (g_LoadingState >= 2) sendRegisterGameStanza(); resetReadyData(); } else updateGUIObjects(); } function openAIConfig(playerSlot) { g_LastViewedAIPlayer = playerSlot; Engine.PushGuiPage("page_aiconfig.xml", { "callback": "AIConfigCallback", "playerSlot": playerSlot, "id": g_GameAttributes.settings.PlayerData[playerSlot].AI, "difficulty": g_GameAttributes.settings.PlayerData[playerSlot].AIDiff, "behavior": g_GameAttributes.settings.PlayerData[playerSlot].AIBehavior }); } /** * Called after closing the dialog. */ function AIConfigCallback(ai) { g_LastViewedAIPlayer = -1; if (!ai.save || !g_IsController) return; g_GameAttributes.settings.PlayerData[ai.playerSlot].AI = ai.id; g_GameAttributes.settings.PlayerData[ai.playerSlot].AIDiff = ai.difficulty; g_GameAttributes.settings.PlayerData[ai.playerSlot].AIBehavior = ai.behavior; updateGameAttributes(); } function reloadPlayerAssignmentChoices() { let playerChoices = sortGUIDsByPlayerID().map(guid => ({ "Choice": "guid:" + guid, "Color": g_PlayerAssignments[guid].player == -1 ? g_PlayerAssignmentColors.observer : g_PlayerAssignmentColors.player, "Name": g_PlayerAssignments[guid].name })); // Only display hidden AIs if the map preselects them let aiChoices = g_Settings.AIDescriptions .filter(ai => !ai.data.hidden || g_GameAttributes.settings.PlayerData.some(pData => pData.AI == ai.id)) .map(ai => ({ "Choice": "ai:" + ai.id, "Name": sprintf(translate("AI: %(ai)s"), { "ai": translate(ai.data.name) }), "Color": g_PlayerAssignmentColors.AI })); let unassignedSlot = [{ "Choice": "unassigned", "Name": translate("Unassigned"), "Color": g_PlayerAssignmentColors.unassigned }]; g_PlayerAssignmentList = prepareForDropdown(playerChoices.concat(aiChoices).concat(unassignedSlot)); initPlayerDropdowns("playerAssignment"); } function swapPlayers(guidToSwap, newSlot) { // Player slots are indexed from 0 as Gaia is omitted. let newPlayerID = newSlot + 1; let playerID = g_PlayerAssignments[guidToSwap].player; // Attempt to swap the player or AI occupying the target slot, // if any, into the slot this player is currently in. if (playerID != -1) { for (let guid in g_PlayerAssignments) { // Move the player in the destination slot into the current slot. if (g_PlayerAssignments[guid].player != newPlayerID) continue; if (g_IsNetworked) Engine.AssignNetworkPlayer(playerID, guid); else g_PlayerAssignments[guid].player = playerID; break; } // Transfer the AI from the target slot to the current slot. g_GameAttributes.settings.PlayerData[playerID - 1].AI = g_GameAttributes.settings.PlayerData[newSlot].AI; g_GameAttributes.settings.PlayerData[playerID - 1].AIDiff = g_GameAttributes.settings.PlayerData[newSlot].AIDiff; g_GameAttributes.settings.PlayerData[playerID - 1].AIBehavior = g_GameAttributes.settings.PlayerData[newSlot].AIBehavior; // Swap civilizations and colors if they aren't fixed if (g_GameAttributes.mapType != "scenario") { [g_GameAttributes.settings.PlayerData[playerID - 1].Civ, g_GameAttributes.settings.PlayerData[newSlot].Civ] = [g_GameAttributes.settings.PlayerData[newSlot].Civ, g_GameAttributes.settings.PlayerData[playerID - 1].Civ]; [g_GameAttributes.settings.PlayerData[playerID - 1].Color, g_GameAttributes.settings.PlayerData[newSlot].Color] = [g_GameAttributes.settings.PlayerData[newSlot].Color, g_GameAttributes.settings.PlayerData[playerID - 1].Color]; } } if (g_IsNetworked) Engine.AssignNetworkPlayer(newPlayerID, guidToSwap); else g_PlayerAssignments[guidToSwap].player = newPlayerID; g_GameAttributes.settings.PlayerData[newSlot].AI = ""; } function submitChatInput() { let chatInput = Engine.GetGUIObjectByName("chatInput"); let text = chatInput.caption; if (!text.length) return; chatInput.caption = ""; if (!executeNetworkCommand(text)) Engine.SendNetworkChat(text); chatInput.focus(); } function senderFont(text) { return '[font="' + g_SenderFont + '"]' + text + '[/font]'; } function systemMessage(message) { return senderFont(sprintf(translate("== %(message)s"), { "message": message })); } function colorizePlayernameByGUID(guid, username = "") { // TODO: Maybe the host should have the moderator-prefix? if (!username) username = g_PlayerAssignments[guid] ? escapeText(g_PlayerAssignments[guid].name) : translate("Unknown Player"); let playerID = g_PlayerAssignments[guid] ? g_PlayerAssignments[guid].player : -1; let color = g_ColorRegular; if (playerID > 0) { color = g_GameAttributes.settings.PlayerData[playerID - 1].Color; // Enlighten playercolor to improve readability let [h, s, l] = rgbToHsl(color.r, color.g, color.b); let [r, g, b] = hslToRgb(h, s, Math.max(0.6, l)); color = rgbToGuiColor({ "r": r, "g": g, "b": b }); } return coloredText(username, color); } function addChatMessage(msg) { if (!g_FormatChatMessage[msg.type]) return; if (msg.type == "chat") { let userName = g_PlayerAssignments[Engine.GetPlayerGUID()].name; if (userName != g_PlayerAssignments[msg.guid].name && msg.text.toLowerCase().indexOf(splitRatingFromNick(userName).nick.toLowerCase()) != -1) soundNotification("nick"); } let user = colorizePlayernameByGUID(msg.guid || -1, msg.username || ""); let text = g_FormatChatMessage[msg.type](msg, user); if (!text) return; if (Engine.ConfigDB_GetValue("user", "chat.timestamp") == "true") text = sprintf(translate("%(time)s %(message)s"), { "time": sprintf(translate("\\[%(time)s]"), { "time": Engine.FormatMillisecondsIntoDateStringLocal(Date.now(), translate("HH:mm")) }), "message": text }); g_ChatMessages.push(text); Engine.GetGUIObjectByName("chatText").caption = g_ChatMessages.join("\n"); } function resetCivilizations() { for (let i in g_GameAttributes.settings.PlayerData) g_GameAttributes.settings.PlayerData[i].Civ = "random"; updateGameAttributes(); } function resetTeams() { for (let i in g_GameAttributes.settings.PlayerData) g_GameAttributes.settings.PlayerData[i].Team = -1; updateGameAttributes(); } function toggleReady() { setReady((g_IsReady + 1) % 3, true); } function setReady(ready, sendMessage) { g_IsReady = ready; if (sendMessage) Engine.SendNetworkReady(g_IsReady); updateGUIObjects(); } function resetReadyData() { if (g_GameStarted) return; if (g_ReadyChanged < 1) addChatMessage({ "type": "settings" }); else if (g_ReadyChanged == 2 && !g_ReadyInit) return; // duplicate calls on init else g_ReadyInit = false; g_ReadyChanged = 2; if (!g_IsNetworked) g_IsReady = 2; else if (g_IsController) { Engine.ClearAllPlayerReady(); setReady(2, true); } else if (g_IsReady != 2) setReady(0, false); } /** * Send a list of playernames and distinct between players and observers. * Don't send teams, AIs or anything else until the game was started. * The playerData format from g_GameAttributes is kept to reuse the GUI function presenting the data. */ function formatClientsForStanza() { let connectedPlayers = 0; let playerData = []; for (let guid in g_PlayerAssignments) { let pData = { "Name": g_PlayerAssignments[guid].name }; if (g_GameAttributes.settings.PlayerData[g_PlayerAssignments[guid].player - 1]) ++connectedPlayers; else pData.Team = "observer"; playerData.push(pData); } return { "list": playerDataToStringifiedTeamList(playerData), "connectedPlayers": connectedPlayers }; } /** * Send the relevant gamesettings to the lobbybot immediately. */ function sendRegisterGameStanzaImmediate() { if (!g_IsController || !Engine.HasXmppClient()) return; if (g_GameStanzaTimer !== undefined) { clearTimeout(g_GameStanzaTimer); g_GameStanzaTimer = undefined; } let clients = formatClientsForStanza(); let stanza = { "name": g_ServerName, "port": g_ServerPort, "hostUsername": Engine.LobbyGetNick(), "mapName": g_GameAttributes.map, "niceMapName": getMapDisplayName(g_GameAttributes.map), "mapSize": g_GameAttributes.mapType == "random" ? g_GameAttributes.settings.Size : "Default", "mapType": g_GameAttributes.mapType, "victoryConditions": g_GameAttributes.settings.VictoryConditions.join(","), "nbp": clients.connectedPlayers, "maxnbp": g_GameAttributes.settings.PlayerData.length, "players": clients.list, "stunIP": g_StunEndpoint ? g_StunEndpoint.ip : "", "stunPort": g_StunEndpoint ? g_StunEndpoint.port : "", "mods": JSON.stringify(Engine.GetEngineInfo().mods), }; // Only send the stanza if the relevant settings actually changed if (g_LastGameStanza && Object.keys(stanza).every(prop => g_LastGameStanza[prop] == stanza[prop])) return; g_LastGameStanza = stanza; Engine.SendRegisterGame(stanza); } /** * Send the relevant gamesettings to the lobbybot in a deferred manner. */ function sendRegisterGameStanza() { if (!g_IsController || !Engine.HasXmppClient()) return; if (g_GameStanzaTimer !== undefined) clearTimeout(g_GameStanzaTimer); g_GameStanzaTimer = setTimeout(sendRegisterGameStanzaImmediate, g_GameStanzaTimeout * 1000); } /** * Figures out all strings that can be autocompleted and sorts * them by priority (so that playernames are always autocompleted first). */ function updateAutocompleteEntries() { let autocomplete = { "0": [] }; for (let control of [g_Dropdowns, g_Checkboxes]) for (let name in control) autocomplete[0] = autocomplete[0].concat(control[name].title()); for (let dropdown of [g_Dropdowns, g_PlayerDropdowns]) for (let name in dropdown) { let priority = dropdown[name].autocomplete; if (priority === undefined) continue; autocomplete[priority] = (autocomplete[priority] || []).concat(dropdown[name].labels()); } g_Autocomplete = Object.keys(autocomplete).sort().reverse().reduce((all, priority) => all.concat(autocomplete[priority]), []); } function storeCivInfoPage(data) { g_CivInfo.code = data.civ; g_CivInfo.page = data.page; } Index: ps/trunk/binaries/data/mods/public/gui/options/options.json =================================================================== --- ps/trunk/binaries/data/mods/public/gui/options/options.json (revision 22087) +++ ps/trunk/binaries/data/mods/public/gui/options/options.json (revision 22088) @@ -1,534 +1,540 @@ [ { "label": "General", "options": [ { "type": "string", "label": "Playername (Single Player)", "tooltip": "How you want to be addressed in Single Player matches.", "config": "playername.singleplayer" }, { "type": "string", "label": "Playername (Multiplayer)", "tooltip": "How you want to be addressed in Multiplayer matches (except lobby).", "config": "playername.multiplayer" }, { "type": "boolean", "label": "Background Pause", "tooltip": "Pause single player games when window loses focus.", "config": "pauseonfocusloss" }, { "type": "boolean", "label": "Enable Welcome Screen", "tooltip": "If you disable it, the welcome screen will still appear once, each time a new version is available. You can always launch it from the main menu.", "config": "gui.splashscreen.enable" }, { "type": "boolean", "label": "Network Warnings", "tooltip": "Show which player has a bad connection in multiplayer games.", "config": "overlay.netwarnings" }, { "type": "boolean", "label": "FPS Overlay", "tooltip": "Show frames per second in top right corner.", "config": "overlay.fps" }, { "type": "boolean", "label": "Realtime Overlay", "tooltip": "Show current system time in top right corner.", "config": "overlay.realtime" }, { "type": "boolean", "label": "Gametime Overlay", "tooltip": "Show current simulation time in top right corner.", "config": "gui.session.timeelapsedcounter" }, { "type": "boolean", "label": "Ceasefire Time Overlay", "tooltip": "Always show the remaining ceasefire time.", "config": "gui.session.ceasefirecounter" }, { "type": "dropdown", "label": "Late Observer Joins", "tooltip": "Allow everybody or buddies only to join the game as observer after it started.", "config": "network.lateobservers", "list": [ { "value": "everyone", "label": "Everyone" }, { "value": "buddies", "label": "Buddies" }, { "value": "disabled", "label": "Disabled" } ] }, { "type": "number", "label": "Observer Limit", "tooltip": "Prevent further observers from joining if the limit is reached.", "config": "network.observerlimit", "min": 0, "max": 32 }, { "type": "boolean", "label": "Chat Timestamp", "tooltip": "Show time that messages are posted in the lobby, gamesetup and ingame chat.", "config": "chat.timestamp" } ] }, { "label": "Graphics", "tooltip": "Set the balance between performance and visual appearance.", "options": [ { "type": "boolean", "label": "Windowed Mode", "tooltip": "Start 0 A.D. in a window.", "config": "windowed" }, { "type": "boolean", "label": "Prefer GLSL", "tooltip": "Use OpenGL 2.0 shaders (recommended).", "config": "preferglsl", "function": "Renderer_SetPreferGLSLEnabled" }, { "type": "boolean", "label": "Fog", "tooltip": "Enable Fog.", "dependencies": ["preferglsl"], "config": "fog", "function": "Renderer_SetFogEnabled" }, { "type": "boolean", "label": "Post Processing", "tooltip": "Use screen-space postprocessing filters (HDR, Bloom, DOF, etc).", "config": "postproc", "function": "Renderer_SetPostprocEnabled" }, { "type": "slider", "label": "Shader Effects", "tooltip": "Number of shader effects. REQUIRES GAME RESTART", "config": "materialmgr.quality", "min": 0, "max": 10 }, { "type": "boolean", "label": "Shadows", "tooltip": "Enable shadows.", "config": "shadows", "function": "Renderer_SetShadowsEnabled" }, { "type": "dropdown", "label": "Shadow Quality", "tooltip": "Shadow map resolution. High values can crash the game when using a graphics card with low memory!", "dependencies": ["shadows"], "config": "shadowquality", "function": "Renderer_RecreateShadowMap", "list": [ { "value": -2, "label": "Very Low" }, { "value": -1, "label": "Low" }, { "value": 0, "label": "Medium" }, { "value": 1, "label": "High" }, { "value": 2, "label": "Very High" } ] }, { "type": "boolean", "label": "Shadow Filtering", "tooltip": "Smooth shadows.", "dependencies": ["shadows"], "config": "shadowpcf", "function": "Renderer_SetShadowPCFEnabled" }, { "type": "boolean", "label": "Unit Silhouettes", "tooltip": "Show outlines of units behind buildings.", "config": "silhouettes", "function": "Renderer_SetSilhouettesEnabled" }, { "type": "boolean", "label": "Particles", "tooltip": "Enable particles.", "config": "particles", "function": "Renderer_SetParticlesEnabled" }, { "type": "boolean", "label": "Water Effects", "tooltip": "When OFF, use the lowest settings possible to render water. This makes other settings irrelevant.", "config": "watereffects", "function": "Renderer_SetWaterEffectsEnabled" }, { "type": "boolean", "label": "HQ Water Effects", "tooltip": "Use higher-quality effects for water, rendering coastal waves, shore foam, and ships trails.", "dependencies": ["watereffects"], "config": "waterfancyeffects", "function": "Renderer_SetWaterFancyEffectsEnabled" }, { "type": "boolean", "label": "Real Water Depth", "tooltip": "Use actual water depth in rendering calculations.", "dependencies": ["watereffects"], "config": "waterrealdepth", "function": "Renderer_SetWaterRealDepthEnabled" }, { "type": "boolean", "label": "Water Reflections", "tooltip": "Allow water to reflect a mirror image.", "dependencies": ["watereffects"], "config": "waterreflection", "function": "Renderer_SetWaterReflectionEnabled" }, { "type": "boolean", "label": "Water Refraction", "tooltip": "Use a real water refraction map and not transparency.", "dependencies": ["watereffects"], "config": "waterrefraction", "function": "Renderer_SetWaterRefractionEnabled" }, { "type": "boolean", "label": "Shadows on Water", "tooltip": "Cast shadows on water.", "dependencies": ["watereffects"], "config": "shadowsonwater", "function": "Renderer_SetWaterShadowsEnabled" }, { "type": "boolean", "label": "Smooth LOS", "tooltip": "Lift darkness and fog-of-war smoothly.", "config": "smoothlos", "function": "Renderer_SetSmoothLOSEnabled" }, { "type": "boolean", "label": "Show Sky", "tooltip": "Render Sky.", "config": "showsky", "function": "Renderer_SetShowSkyEnabled" }, { "type": "boolean", "label": "VSync", "tooltip": "Run vertical sync to fix screen tearing. REQUIRES GAME RESTART", "config": "vsync" }, { "type": "slider", "label": "FPS Throttling in Menus", "tooltip": "To save CPU workload, throttle render frequency in all menus. Set to maximum to disable throttling.", "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.", "config": "adaptivefps.session", "min": 20, "max": 100 } ] }, { "label": "Sound", "options": [ { "type": "slider", "label": "Master Volume", "tooltip": "Master audio gain.", "config": "sound.mastergain", "function": "SetMasterGain", "min": 0, "max": 2 }, { "type": "slider", "label": "Music Volume", "tooltip": "In game music gain.", "config": "sound.musicgain", "function": "SetMusicGain", "min": 0, "max": 2 }, { "type": "slider", "label": "Ambient Volume", "tooltip": "In game ambient sound gain.", "config": "sound.ambientgain", "function": "SetAmbientGain", "min": 0, "max": 2 }, { "type": "slider", "label": "Action Volume", "tooltip": "In game unit action sound gain.", "config": "sound.actiongain", "function": "SetActionGain", "min": 0, "max": 2 }, { "type": "slider", "label": "UI Volume", "tooltip": "UI sound gain.", "config": "sound.uigain", "function": "SetUIGain", "min": 0, "max": 2 }, { "type": "boolean", "label": "Nick Notification", "tooltip": "Receive audio notification when someone types your nick.", "config": "sound.notify.nick" + }, + { + "type": "boolean", + "label": "Game Setup - New Player Notification", + "tooltip": "Receive audio notification when a new client joins the game setup.", + "config": "sound.notify.gamesetup.join" } ] }, { "label": "Game Setup", "options": [ { "type": "boolean", "label": "Enable Game Setting Tips", "tooltip": "Show tips when setting up a game.", "config": "gui.gamesetup.enabletips" }, { "type": "boolean", "label": "Enable Settings Panel Slide", "tooltip": "Slide the settings panel when opening, closing or resizing.", "config": "gui.gamesetup.settingsslide" }, { "type": "boolean", "label": "Persist Match Settings", "tooltip": "Save and restore match settings for quick reuse when hosting another game.", "config": "persistmatchsettings" }, { "type": "dropdown", "label": "Default AI Difficulty", "tooltip": "Default difficulty of the AI.", "config": "gui.gamesetup.aidifficulty", "list": [ { "value": 0, "label": "Sandbox" }, { "value": 1, "label": "Very Easy" }, { "value": 2, "label": "Easy" }, { "value": 3, "label": "Medium" }, { "value": 4, "label": "Hard" }, { "value": 5, "label": "Very Hard" } ] }, { "type": "dropdown", "label": "Default AI Behavior", "tooltip": "Default behavior of the AI.", "config": "gui.gamesetup.aibehavior", "list": [ { "value": "random", "label": "Random" }, { "value": "balanced", "label": "Balanced" }, { "value": "aggressive", "label": "Aggressive" }, { "value": "defensive", "label": "Defensive" } ] }, { "type": "dropdown", "label": "Assign Players", "tooltip": "Automatically assign joining clients to free player slots during the match setup.", "config": "gui.gamesetup.assignplayers", "list": [ { "value": "everyone", "label": "Everyone" }, { "value": "buddies", "label": "Buddies" }, { "value": "disabled", "label": "Disabled" } ] } ] }, { "label": "Lobby", "tooltip": "These settings only affect the multiplayer.", "options": [ { "type": "boolean", "label": "TLS Encryption", "tooltip": "Protect login and data exchanged with the lobby server using TLS encryption.", "config": "lobby.tls" }, { "type": "number", "label": "Chat Backlog", "tooltip": "Number of backlogged messages to load when joining the lobby.", "config": "lobby.history", "min": "0" }, { "type": "boolean", "label": "Game Rating Column", "tooltip": "Show the average rating of the participating players in a column of the gamelist.", "config": "lobby.columns.gamerating" } ] }, { "label": "In-Game", "tooltip": "Change options regarding the in-game settings.", "options": [ { "type": "slider", "label": "Wounded Unit Health", "tooltip": "The wounded unit hotkey considers the selected units as wounded if their health percentage falls below this number.", "config": "gui.session.woundedunithotkeythreshold", "min": 0, "max": 100 }, { "type": "number", "label": "Batch Training Size", "tooltip": "Number of units trained per batch by default.", "config": "gui.session.batchtrainingsize", "callback": "updateDefaultBatchSize", "min": 1, "max": 20 }, { "type": "slider", "label": "Scroll Batch Increment Ratio", "tooltip": "Number of times you have to scroll to increase/decrease the batchsize by 1.", "config": "gui.session.scrollbatchratio", "min": 0.1, "max": 30 }, { "type": "boolean", "label": "Chat Notification Attack", "tooltip": "Show a chat notification if you are attacked by another player.", "config": "gui.session.notifications.attack" }, { "type": "boolean", "label": "Chat Notification 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.", "config": "gui.session.notifications.tribute" }, { "type": "boolean", "label": "Chat Notification Barter", "tooltip": "Show a chat notification to observers when a player bartered resources.", "config": "gui.session.notifications.barter" }, { "type": "dropdown", "label": "Chat Notification 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.", "config": "gui.session.notifications.phase", "list": [ { "value": "none", "label": "Disable" }, { "value": "completed", "label": "Completed" }, { "value": "all", "label": "All displayed" } ] }, { "type": "boolean", "label": "Attack Range Visualization", "tooltip": "Display the attack range of selected defensive structures (can also be toggled in-game with the hotkey).", "config": "gui.session.attackrange", "callback": "updateEnabledRangeOverlayTypes" }, { "type": "boolean", "label": "Aura Range Visualization", "tooltip": "Display the range of auras of selected units and structures (can also be toggled in-game with the hotkey).", "config": "gui.session.aurasrange", "callback": "updateEnabledRangeOverlayTypes" }, { "type": "boolean", "label": "Heal Range Visualization", "tooltip": "Display the healing range of selected units (can also be toggled in-game with the hotkey).", "config": "gui.session.healrange", "callback": "updateEnabledRangeOverlayTypes" }, { "type": "boolean", "label": "Rank icon above status bar", "tooltip": "Show rank icons above status bars.", "config": "gui.session.rankabovestatusbar" }, { "type": "boolean", "label": "Detailed Tooltips", "tooltip": "Show detailed tooltips for trainable units in unit-producing buildings.", "config": "showdetailedtooltips" }, { "type": "dropdown", "label": "Sort resources and population tooltip", "tooltip": "Dynamically sort players in the resources and population tooltip by value.", "config": "gui.session.respoptooltipsort", "list": [ { "value": 0, "label": "Unordered" }, { "value": -1, "label": "Ascending" }, { "value": 1, "label": "Descending" } ] }, { "type": "color", "label": "Diplomacy Colors: Self", "tooltip": "Color of your units when diplomacy colors are enabled.", "config": "gui.session.diplomacycolors.self", "callback": "updateDisplayedPlayerColors" }, { "type": "color", "label": "Diplomacy Colors: Ally", "tooltip": "Color of allies when diplomacy colors are enabled.", "config": "gui.session.diplomacycolors.ally", "callback": "updateDisplayedPlayerColors" }, { "type": "color", "label": "Diplomacy Colors: Neutral", "tooltip": "Color of neutral players when diplomacy colors are enabled.", "config": "gui.session.diplomacycolors.neutral", "callback": "updateDisplayedPlayerColors" }, { "type": "color", "label": "Diplomacy Colors: Enemy", "tooltip": "Color of enemies when diplomacy colors are enabled.", "config": "gui.session.diplomacycolors.enemy", "callback": "updateDisplayedPlayerColors" } ] } ]