Index: ps/trunk/binaries/data/config/default.cfg =================================================================== --- ps/trunk/binaries/data/config/default.cfg (revision 22649) +++ ps/trunk/binaries/data/config/default.cfg (revision 22650) @@ -1,514 +1,516 @@ ; 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 +diplomacy.toggle = "Ctrl+H" ; Toggle in-game diplomacy page barter.toggle = "Ctrl+B" ; Toggle in-game barter/trade page +objectives.toggle = "Ctrl+O" ; Toggle in-game objectives 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 experiencestatusbar = true ; Show an experience status bar above each selected unit 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/gui/manual/intro.txt =================================================================== --- ps/trunk/binaries/data/mods/public/gui/manual/intro.txt (revision 22649) +++ ps/trunk/binaries/data/mods/public/gui/manual/intro.txt (revision 22650) @@ -1,157 +1,159 @@ [font="sans-bold-18"]0 A.D. in-game manual [font="sans-14"] Thank you for installing 0 A.D.! This page will give a brief overview of the features available in this incomplete, under-development, alpha version of the game. [font="sans-bold-16"]Graphics settings [font="sans-14"]You can switch between fullscreen and windowed mode by pressing Alt + Enter. In windowed mode, you can resize the window. If the game runs too slowly, you can change some settings in the options window: try disabling the "HQ Water Effects" and "Shadows" options. [font="sans-bold-16"]Playing the game [font="sans-14"]The controls and gameplay should be familiar to players of traditional RTS games. There are currently a lot of missing features and poorly-balanced stats – you will probably have to wait until a beta release for it to work well. Basic controls: • Left-click to select units. • Left-click-and-drag to select groups of units. • Right-click to order units to the target. • Arrow keys or WASD keys to move the camera. • Ctrl + arrow keys, or shift + mouse wheel, to rotate the camera. • Mouse wheel, or "+" and "-" keys, to zoom. [font="sans-bold-16"]Modes [font="sans-14"]The main menu gives access to two game modes: • [font="sans-bold-14"]Single-player[font="sans-14"] – play a sandbox game or against one or more AI opponents. The AI/AIs are under development and may not always be up to date on the new features, but you can test playing the game against an actual opponent if you aren't able to play with a human. • [font="sans-bold-14"]Multiplayer[font="sans-14"] – play against human opponents over the internet. To set up a multiplayer game, one player must select the "Host game" option. The game uses UDP port 20595, so the host must configure their NAT/firewall/etc to allow this. Other players can then select "Join game" and enter the host's IP address. [font="sans-bold-16"]Game setup [font="sans-14"]In a multiplayer game, only the host can alter the game setup options. First, select what type of map you want to play: • [font="sans-bold-14"]Random map[font="sans-14"] – A map created automatically from a script • [font="sans-bold-14"]Scenario map[font="sans-14"] – A map created by map designers and with fixed civilizations • [font="sans-bold-14"]Skirmish map[font="sans-14"] – A map created by map designers but where the civilizations can be chosen Then the maps can be further filtered. The default maps are generally playable maps. Naval maps are maps where not all opponents can be reached over land and demo maps are maps used for testing purposes (generally not useful to play on). The "All Maps" filter shows all maps together in one list. Finally change the settings. For random maps this includes the number of players, the size of a map, etc. For scenarios you can only select who controls which player (decides where you start on the map etc). The options are either a human player, an AI or no player at all (the opponent will just be idle). When you are ready to start, click the "Start game" button. [font="sans-bold-16"]Hotkeys: [font="sans-bold-14"]Program-wide [font="sans-14"]Alt + F4: Close the game, without confirmation Alt + Enter: Toggle between fullscreen and windowed ~ or F9: Show/hide console Alt + F: Show/hide frame counter (FPS) F11: Enable/disable real-time profiler (toggles through the displays of information) Shift + F11: Save current profiler data to "logs/profile.txt" F2: Take screenshot (in .png format, location is displayed in the top left of the GUI after the file has been saved, and can also be seen in the console/logs if you miss it there) Shift + F2: Take huge screenshot (6400px by 4800px, in .bmp format, location is displayed in the top left of the GUI after the file has been saved, and can also be seen in the console/logs if you miss it there) Tab, Alt + S: Switch to the next tab. Shift + Tab, Alt + W: Switch to the previous tab. [font="sans-bold-14"]In Game [font="sans-14"]Double Left Click \[on unit]: Select all of your units of the same kind on the screen (even if they're different ranks) Triple Left Click \[on unit]: Select all of your units of the same kind and the same rank on the screen Alt + Double Left Click \[on unit]: Select all your units of the same kind on the entire map (even if they have different ranks) Alt + Triple Left Click \[on unit]: Select all your units of the same kind and rank on the entire map Shift + F5: Quicksave Shift + F8: Quickload F10: Open/close menu F12: Show/hide time elapsed counter ESC: Close all dialogs (chat, menu) or clear selected units Enter/return: Open/send chat T: Send team chat L: Chat with the previously selected private chat partner Pause: Pause/resume the game Delete: Delete currently selected units/buildings Shift + Delete: Delete currently selected units/buildings without confirmation / (Slash): Select idle fighter Shift + /: Add idle fighter to selection Alt + /: Select all idle fighters . (Period): Select idle worker (including citizen soldiers) Shift + .: Add idle worker to selection (including citizen soldiers) Alt + .: Select all idle workers (including citizen soldiers) \ (Backslash): Select idle unit Shift + \: Add idle unit to selection Alt + \: Select all idle units H: Stop (halt) the currently selected units. Y: The unit will go back to work U: Unload the garrisoned units of the selected buildings Ctrl + 1 (and so on up to Ctrl + 0): Create control group 1 (to 0) from the selected units/buildings 1 (and so on up to 0): Select the units/buildings in control group 1 (to 0) Shift + 1 (to 0): Add control group 1 (to 0) to the selected units/buildings Ctrl + F5 (and so on up to F8): Mark the current camera position, for jumping back to later. F5, F6, F7, and F8: Move the camera to a marked position. Jump back to the last location if the camera is already over the marked position. Z, X, C, V, B, N, M: With training buildings selected. Add the 1st, 2nd, … unit shown to the training queue for all the selected buildings. PageUp with units selected: Highlights the units/buildings guarded by the selection. PageDown with units/buildings selected: Highlights the units guarding the selection. Tab: See all status bars (which would also show the building progress) Ctrl + Tab: Toggle summary window. Alt + L: Show the multiplayer lobby in a dialog window. +Ctrl + H: Toggle in-game diplomacy page. Ctrl + B: Toggle in-game barter/trade page. +Ctrl + O: Toggle in-game objectives page. Ctrl + P: Toggle in-game tutorial panel. Alt + Shift + T: Toggle structure tree. Alt + Shift + H: Toggle civilization info. [font="sans-bold-14"]Modify mouse action [font="sans-14"]Ctrl + Right Click on building: Garrison J + Right Click on building: Repair P + Right Click: Patrol Shift + Right Click: Queue the move/build/gather/etc order Alt + Right Click: Order one unit from the current selection to move/build/gather/etc and unselect it. Used to quickly dispatch units with specific tasks. Shift + Left Click when training units: Add units in batches (the batch size is 5 by default and can be changed in the options) Shift + Left Click or Left Drag over unit on map: Add unit to selection Ctrl + Left Click or Left Drag over unit on map: Remove unit from selection Alt + Left Drag over units on map: Only select military units Alt + Y + Left Drag over units on map: Only select non-military units I + Left Drag over units on map: Only select idle units O + Left Drag over units on map: Only select wounded units Ctrl + Left Click on unit/group icon with multiple units selected: Deselect Right Click with a building/buildings selected: sets a rally point for units created/ungarrisoned from that building. Ctrl + Right Click with units selected: • If the cursor is over an allied structure: Garrison • If the cursor is over a non-allied unit or building: Attack (instead of capture or gather) • Otherwise: Attack move (by default all enemy units and structures along the way are targeted, use Ctrl + Q + Right Click to target only units). [font="sans-bold-14"]Overlays [font="sans-14"]Alt + G: Hide/show the GUI Alt + D: Show/hide developer overlay (with developer options) Alt + Shift + W: Toggle wireframe mode (press once to get wireframes overlaid over the textured models, twice to get just the wireframes colored by the textures, thrice to get back to normal textured mode) Alt + Shift + S: Toggle unit silhouettes (might give a small performance boost) Alt + Z: Toggle sky Alt + X: Toggle diplomacy colors Alt + C: Toggle attack range visualizations of selected defensive structures Alt + V: Toggle aura range visualizations of selected units and structures Alt + B: Toggle heal range visualizations of selected units [font="sans-bold-14"]Camera manipulation [font="sans-14"]W or \[up]: Pan screen up S or \[down]: Pan screen down A or \[left]: Pan screen left D or \[right]: Pan screen right Ctrl + W or \[up]: Rotate camera to look upward Ctrl + S or \[down]: Rotate camera to look downward Ctrl + A or \[left]: Rotate camera clockwise around terrain Ctrl + D or \[right]: Rotate camera counter-clockwise around terrain Q: Rotate camera clockwise around terrain E: Rotate camera counter-clockwise around terrain Shift + Mouse Wheel Rotate Up: Rotate camera clockwise around terrain Shift + Mouse Wheel Rotate Down: Rotate camera counter-clockwise around terrain F: Follow the selected unit (move the camera to stop it from following the unit) R: Reset camera zoom/orientation = or +: Zoom in (keep pressed for continuous zoom) -: Zoom out (keep pressed for continuous zoom) Alt + W: Toggle through wireframe modes Middle Mouse Button: Keep pressed and move the mouse to pan [font="sans-bold-14"]During Building Placement [font="sans-14"]\[: Rotate building 15 degrees counter-clockwise ]: Rotate building 15 degrees clockwise Left Drag: Rotate building using mouse (foundation will be placed on mouse release) [font="sans-bold-14"]When loading a saved game [font="sans-14"]Delete: delete the selected saved game asking confirmation Shift: do not ask confirmation when deleting a saved game Index: ps/trunk/binaries/data/mods/public/gui/session/hotkeys/misc.xml =================================================================== --- ps/trunk/binaries/data/mods/public/gui/session/hotkeys/misc.xml (revision 22649) +++ ps/trunk/binaries/data/mods/public/gui/session/hotkeys/misc.xml (revision 22650) @@ -1,138 +1,134 @@ closeOpenDialogs(); openChat(); openChat(g_IsObserver ? "/observers" : "/allies"); openChat(g_LastChatAddressee); toggleGUI(); toggleMenu(); - - toggleTrade(); - - toggleTutorial(); openGameSummary(); Engine.PushGuiPage("page_civinfo.xml", { "civ": g_CivInfo.code, "callback": "storeCivInfoPage" }); Engine.PushGuiPage("page_structree.xml", { "civ": g_CivInfo.code, "callback": "storeCivInfoPage" }); toggleConfigBool("silhouettes"); var newSetting = !Engine.Renderer_GetShowSkyEnabled(); Engine.Renderer_SetShowSkyEnabled(newSetting); togglePause(); Engine.QuickSave(); Engine.QuickLoad(); performCommand(g_Selection.toList().map(ent => GetEntityState(ent)), "delete"); unloadAll(); stopUnits(g_Selection.toList()); backToWork(); updateSelectionDetails(); updateSelectionDetails(); updateSelectionDetails(); updateBarterButtons(); updateSelectionDetails(); updateBarterButtons(); findIdleUnit(g_MilitaryTypes); findIdleUnit(["!Domestic"]); clearSelection(); toggleRangeOverlay("Attack"); toggleRangeOverlay("Auras"); toggleRangeOverlay("Heal"); g_ShowAllStatusBars = !g_ShowAllStatusBars; recalculateStatusBarDisplay(); Index: ps/trunk/binaries/data/mods/public/gui/session/menu.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/session/menu.js (revision 22649) +++ ps/trunk/binaries/data/mods/public/gui/session/menu.js (revision 22650) @@ -1,1249 +1,1244 @@ // Menu / panel border size var MARGIN = 4; // Includes the main menu button const NUM_BUTTONS = 10; // Regular menu buttons var BUTTON_HEIGHT = 32; // The position where the bottom of the menu will end up (currently 228) const END_MENU_POSITION = (BUTTON_HEIGHT * NUM_BUTTONS) + MARGIN; // Menu starting position: bottom const MENU_BOTTOM = 0; // Menu starting position: top const MENU_TOP = MENU_BOTTOM - END_MENU_POSITION; // Number of pixels per millisecond to move var MENU_SPEED = 1.2; // Trade menu: step for probability changes var STEP = 5; // Shown in the trade dialog. var g_IdleTraderTextColor = "orange"; /** * Store civilization code and page (structree or history) opened in civilization info. */ var g_CivInfo = { "code": "", "page": "page_structree.xml" }; /** * The barter constants should match with the simulation * Quantity of goods to sell per click. */ const g_BarterResourceSellQuantity = 100; /** * Multiplier to be applied when holding the massbarter hotkey. */ const g_BarterMultiplier = 5; /** * Barter actions, as mapped to the names of GUI Buttons. */ const g_BarterActions = ["Buy", "Sell"]; /** * Currently selected resource type to sell in the barter GUI. */ var g_BarterSell; var g_IsMenuOpen = false; var g_IsDiplomacyOpen = false; var g_IsTradeOpen = false; var g_IsObjectivesOpen = false; /** * Used to disable a specific bribe button for the time we are waiting for the result of the bribe after it was clicked. * It contains an array per viewedPlayer. This array is a list of the players that were bribed. */ var g_BribeButtonsWaiting = {}; /** * Remember last viewed summary panel and charts. */ var g_SummarySelectedData; // Redefined every time someone makes a tribute (so we can save some data in a closure). Called in input.js handleInputBeforeGui. var g_FlushTributing = function() {}; function initMenu() { Engine.GetGUIObjectByName("menu").size = "100%-164 " + MENU_TOP + " 100% " + MENU_BOTTOM; // TODO: Atlas should pass g_GameAttributes.settings for (let button of ["menuExitButton", "summaryButton", "objectivesButton", "diplomacyButton"]) Engine.GetGUIObjectByName(button).enabled = !Engine.IsAtlasRunning(); } function updateMenuPosition(dt) { let menu = Engine.GetGUIObjectByName("menu"); let maxOffset = g_IsMenuOpen ? END_MENU_POSITION - menu.size.bottom : menu.size.top - MENU_TOP; if (maxOffset <= 0) return; let offset = Math.min(MENU_SPEED * dt, maxOffset) * (g_IsMenuOpen ? +1 : -1); let size = menu.size; size.top += offset; size.bottom += offset; menu.size = size; } // Opens the menu by revealing the screen which contains the menu function openMenu() { g_IsMenuOpen = true; } // Closes the menu and resets position function closeMenu() { g_IsMenuOpen = false; } function toggleMenu() { g_IsMenuOpen = !g_IsMenuOpen; } function optionsMenuButton() { closeOpenDialogs(); openOptions(); } function lobbyDialogButton() { if (!Engine.HasXmppClient()) return; closeOpenDialogs(); Engine.PushGuiPage("page_lobby.xml", { "dialog": true }); } function chatMenuButton() { closeOpenDialogs(); openChat(); } -function pauseMenuButton() -{ - togglePause(); -} - function resignMenuButton() { closeOpenDialogs(); pauseGame(); messageBox( 400, 200, translate("Are you sure you want to resign?"), translate("Confirmation"), [translate("No"), translate("Yes")], [resumeGame, resignGame] ); } function exitMenuButton() { closeOpenDialogs(); pauseGame(); let messageTypes = { "host": { "caption": translate("Are you sure you want to quit? Leaving will disconnect all other players."), "buttons": [resumeGame, leaveGame] }, "client": { "caption": translate("Are you sure you want to quit?"), "buttons": [resumeGame, resignQuestion] }, "singleplayer": { "caption": translate("Are you sure you want to quit?"), "buttons": [resumeGame, leaveGame] } }; let messageType = g_IsNetworked && g_IsController ? "host" : (g_IsNetworked && !g_IsObserver ? "client" : "singleplayer"); messageBox( 400, 200, messageTypes[messageType].caption, translate("Confirmation"), [translate("No"), translate("Yes")], messageTypes[messageType].buttons ); } function resignQuestion() { messageBox( 400, 200, translate("Do you want to resign or will you return soon?"), translate("Confirmation"), [translate("I will return"), translate("I resign")], [leaveGame, resignGame], [true, false] ); } function openDeleteDialog(selection) { closeOpenDialogs(); let deleteSelectedEntities = function(selectionArg) { Engine.PostNetworkCommand({ "type": "delete-entities", "entities": selectionArg }); }; messageBox( 400, 200, translate("Destroy everything currently selected?"), translate("Delete"), [translate("No"), translate("Yes")], [resumeGame, deleteSelectedEntities], [null, selection] ); } function openSave() { closeOpenDialogs(); pauseGame(); Engine.PushGuiPage("page_savegame.xml", { "savedGameData": getSavedGameData(), "callback": "resumeGame" }); } function openOptions() { closeOpenDialogs(); pauseGame(); Engine.PushGuiPage("page_options.xml", { "callback": "optionsPageClosed" }); } function optionsPageClosed(data) { for (let callback of data) if (global[callback]) global[callback](); resumeGame(); } function openChat(command = "") { if (g_Disconnected) return; closeOpenDialogs(); let chatAddressee = Engine.GetGUIObjectByName("chatAddressee"); chatAddressee.selected = chatAddressee.list_data.indexOf(command); Engine.GetGUIObjectByName("chatInput").focus(); Engine.GetGUIObjectByName("chatDialogPanel").hidden = false; updateChatHistory(); } function closeChat() { Engine.GetGUIObjectByName("chatInput").caption = ""; Engine.GetGUIObjectByName("chatInput").blur(); // Remove focus Engine.GetGUIObjectByName("chatDialogPanel").hidden = true; } function resizeDiplomacyDialog() { let dialog = Engine.GetGUIObjectByName("diplomacyDialogPanel"); let size = dialog.size; let tribSize = Engine.GetGUIObjectByName("diplomacyPlayer[0]_tribute[0]").size; let widthOffset = g_ResourceData.GetCodes().length * (tribSize.right - tribSize.left) / 2; size.left -= widthOffset; size.right += widthOffset; let firstRow = Engine.GetGUIObjectByName("diplomacyPlayer[0]").size; let heightOffset = (g_Players.length - 1) * (firstRow.bottom - firstRow.top) / 2; size.top -= heightOffset; size.bottom += heightOffset; dialog.size = size; } function initChatWindow() { let filters = prepareForDropdown(g_ChatHistoryFilters); let chatHistoryFilter = Engine.GetGUIObjectByName("chatHistoryFilter"); chatHistoryFilter.list = filters.text; chatHistoryFilter.list_data = filters.key; chatHistoryFilter.selected = 0; Engine.GetGUIObjectByName("extendedChat").checked = Engine.ConfigDB_GetValue("user", "chat.session.extended") == "true"; resizeChatWindow(); } function resizeChatWindow() { // Hide/show the panel let chatHistoryPage = Engine.GetGUIObjectByName("chatHistoryPage"); let extended = Engine.GetGUIObjectByName("extendedChat").checked; chatHistoryPage.hidden = !extended; // Resize the window let chatDialogPanel = Engine.GetGUIObjectByName("chatDialogPanel"); if (extended) { chatDialogPanel.size = Engine.GetGUIObjectByName("chatDialogPanelLarge").size; // Adjust the width so that the chat history is in the golden ratio let chatHistory = Engine.GetGUIObjectByName("chatHistory"); let height = chatHistory.getComputedSize().bottom - chatHistory.getComputedSize().top; let width = (1 + Math.sqrt(5)) / 2 * height; let size = chatDialogPanel.size; size.left = -width / 2 - chatHistory.size.left; size.right = width / 2 + chatHistory.size.left; chatDialogPanel.size = size; } else chatDialogPanel.size = Engine.GetGUIObjectByName("chatDialogPanelSmall").size; } function updateChatHistory() { if (Engine.GetGUIObjectByName("chatDialogPanel").hidden || !Engine.GetGUIObjectByName("extendedChat").checked) return; let chatHistoryFilter = Engine.GetGUIObjectByName("chatHistoryFilter"); let selected = chatHistoryFilter.list_data[chatHistoryFilter.selected]; Engine.GetGUIObjectByName("chatHistory").caption = g_ChatHistory.filter(msg => msg.filter[selected]).map(msg => Engine.ConfigDB_GetValue("user", "chat.timestamp") == "true" ? sprintf(translate("%(time)s %(message)s"), { "time": msg.timePrefix, "message": msg.txt }) : msg.txt ).join("\n"); } function onToggleChatWindowExtended() { Engine.ConfigDB_CreateAndWriteValueToFile("user", "chat.session.extended", String(Engine.GetGUIObjectByName("extendedChat").checked), "config/user.cfg"); resizeChatWindow(); Engine.GetGUIObjectByName("chatInput").focus(); } function openDiplomacy() { closeOpenDialogs(); if (g_ViewedPlayer < 1) return; g_IsDiplomacyOpen = true; updateDiplomacy(true); Engine.GetGUIObjectByName("diplomacyDialogPanel").hidden = false; } function closeDiplomacy() { g_IsDiplomacyOpen = false; Engine.GetGUIObjectByName("diplomacyDialogPanel").hidden = true; } function toggleDiplomacy() { let open = g_IsDiplomacyOpen; closeOpenDialogs(); if (!open) openDiplomacy(); } function updateDiplomacy(opening = false) { if (g_ViewedPlayer < 1 || !g_IsDiplomacyOpen) return; let simState = GetSimState(); let isCeasefireActive = simState.ceasefireActive; let hasSharedLos = GetSimState().players[g_ViewedPlayer].hasSharedLos; // Get offset for one line let onesize = Engine.GetGUIObjectByName("diplomacyPlayer[0]").size; let rowsize = onesize.bottom - onesize.top; // We don't include gaia for (let i = 1; i < g_Players.length; ++i) { let myself = i == g_ViewedPlayer; let playerInactive = isPlayerObserver(g_ViewedPlayer) || isPlayerObserver(i); let hasAllies = g_Players.filter(player => player.isMutualAlly[g_ViewedPlayer]).length > 1; diplomacySetupTexts(i, rowsize); diplomacyFormatStanceButtons(i, myself || playerInactive || isCeasefireActive || g_Players[g_ViewedPlayer].teamsLocked); // Tribute buttons do not need to be updated onTick, and should not because of massTributing if (opening) diplomacyFormatTributeButtons(i, myself || playerInactive); diplomacyFormatAttackRequestButton(i, myself || playerInactive || isCeasefireActive || !hasAllies || !g_Players[i].isEnemy[g_ViewedPlayer]); diplomacyFormatSpyRequestButton(i, myself || playerInactive || g_Players[i].isMutualAlly[g_ViewedPlayer] && hasSharedLos); } let diplomacyCeasefireCounter = Engine.GetGUIObjectByName("diplomacyCeasefireCounter"); diplomacyCeasefireCounter.caption = sprintf( translateWithContext("ceasefire", "Remaining ceasefire time: %(time)s."), { "time": timeToString(simState.ceasefireTimeRemaining) } ); diplomacyCeasefireCounter.hidden = !isCeasefireActive; } function diplomacySetupTexts(i, rowsize) { // Apply offset let row = Engine.GetGUIObjectByName("diplomacyPlayer[" + (i - 1) + "]"); let size = row.size; size.top = rowsize * (i - 1); size.bottom = rowsize * i; row.size = size; row.hidden = false; row.sprite = "color:" + rgbToGuiColor(g_DisplayedPlayerColors[i], 32); setOutcomeIcon(g_Players[i].state, "diplomacyPlayerOutcome[" + (i - 1) + "]"); let diplomacyPlayerName = Engine.GetGUIObjectByName("diplomacyPlayerName[" + (i - 1) + "]"); diplomacyPlayerName.caption = colorizePlayernameByID(i); diplomacyPlayerName.tooltip = translateAISettings(g_GameAttributes.settings.PlayerData[i]); Engine.GetGUIObjectByName("diplomacyPlayerCiv[" + (i - 1) + "]").caption = g_CivData[g_Players[i].civ].Name; Engine.GetGUIObjectByName("diplomacyPlayerTeam[" + (i - 1) + "]").caption = g_Players[i].team < 0 ? translateWithContext("team", "None") : g_Players[i].team + 1; Engine.GetGUIObjectByName("diplomacyPlayerTheirs[" + (i - 1) + "]").caption = i == g_ViewedPlayer ? "" : g_Players[i].isAlly[g_ViewedPlayer] ? translate("Ally") : g_Players[i].isNeutral[g_ViewedPlayer] ? translate("Neutral") : translate("Enemy"); } function diplomacyFormatStanceButtons(i, hidden) { for (let stance of ["Ally", "Neutral", "Enemy"]) { let button = Engine.GetGUIObjectByName("diplomacyPlayer" + stance + "[" + (i - 1) + "]"); button.hidden = hidden; if (hidden) continue; let isCurrentStance = g_Players[g_ViewedPlayer]["is" + stance][i]; button.caption = isCurrentStance ? translate("x") : ""; button.enabled = controlsPlayer(g_ViewedPlayer) && !isCurrentStance; button.onPress = (function(player, stance) { return function() { Engine.PostNetworkCommand({ "type": "diplomacy", "player": i, "to": stance.toLowerCase() }); }; })(i, stance); } } function diplomacyFormatTributeButtons(i, hidden) { let resCodes = g_ResourceData.GetCodes(); let r = 0; for (let resCode of resCodes) { let button = Engine.GetGUIObjectByName("diplomacyPlayer[" + (i - 1) + "]_tribute[" + r + "]"); if (!button) { warn("Current GUI limits prevent displaying more than " + r + " tribute buttons!"); break; } Engine.GetGUIObjectByName("diplomacyPlayer[" + (i - 1) + "]_tribute[" + r + "]_image").sprite = "stretched:session/icons/resources/" + resCode + ".png"; button.hidden = hidden; setPanelObjectPosition(button, r, r + 1, 0); ++r; if (hidden) continue; button.enabled = controlsPlayer(g_ViewedPlayer); button.tooltip = formatTributeTooltip(i, resCode, 100); button.onPress = (function(i, resCode, button) { // Shift+click to send 500, shift+click+click to send 1000, etc. // See INPUT_MASSTRIBUTING in input.js let multiplier = 1; return function() { let isBatchTrainPressed = Engine.HotkeyIsPressed("session.masstribute"); if (isBatchTrainPressed) { inputState = INPUT_MASSTRIBUTING; multiplier += multiplier == 1 ? 4 : 5; } let amounts = {}; for (let res of resCodes) amounts[res] = 0; amounts[resCode] = 100 * multiplier; button.tooltip = formatTributeTooltip(i, resCode, amounts[resCode]); // This is in a closure so that we have access to `player`, `amounts`, and `multiplier` without some // evil global variable hackery. g_FlushTributing = function() { Engine.PostNetworkCommand({ "type": "tribute", "player": i, "amounts": amounts }); multiplier = 1; button.tooltip = formatTributeTooltip(i, resCode, 100); }; if (!isBatchTrainPressed) g_FlushTributing(); }; })(i, resCode, button); } } function diplomacyFormatAttackRequestButton(i, hidden) { let button = Engine.GetGUIObjectByName("diplomacyAttackRequest[" + (i - 1) + "]"); button.hidden = hidden; if (hidden) return; button.enabled = controlsPlayer(g_ViewedPlayer); button.tooltip = translate("Request your allies to attack this enemy"); button.onPress = (function(i) { return function() { Engine.PostNetworkCommand({ "type": "attack-request", "source": g_ViewedPlayer, "player": i }); }; })(i); } function diplomacyFormatSpyRequestButton(i, hidden) { let button = Engine.GetGUIObjectByName("diplomacySpyRequest[" + (i - 1) + "]"); let template = GetTemplateData("special/spy"); button.hidden = hidden || !template || !!GetSimState().players[g_ViewedPlayer].disabledTemplates["special/spy"]; if (button.hidden) return; button.enabled = controlsPlayer(g_ViewedPlayer) && !(g_BribeButtonsWaiting[g_ViewedPlayer] && g_BribeButtonsWaiting[g_ViewedPlayer].indexOf(i) != -1); let modifier = ""; let tooltips = [translate("Bribe a random unit from this player and share its vision during a limited period.")]; if (!button.enabled) modifier = "color:0 0 0 127:grayscale:"; else { if (template.requiredTechnology) { let technologyEnabled = Engine.GuiInterfaceCall("IsTechnologyResearched", { "tech": template.requiredTechnology, "player": g_ViewedPlayer }); if (!technologyEnabled) { modifier = "color:0 0 0 127:grayscale:"; button.enabled = false; tooltips.push(getRequiredTechnologyTooltip(technologyEnabled, template.requiredTechnology, GetSimState().players[g_ViewedPlayer].civ)); } } if (template.cost) { let modifiedTemplate = clone(template); for (let res in template.cost) modifiedTemplate.cost[res] = Math.floor(GetSimState().players[i].spyCostMultiplier * template.cost[res]); tooltips.push(getEntityCostTooltip(modifiedTemplate)); let neededResources = Engine.GuiInterfaceCall("GetNeededResources", { "cost": modifiedTemplate.cost, "player": g_ViewedPlayer }); let costRatio = Engine.GetTemplate("special/spy").VisionSharing.FailureCostRatio; if (costRatio > 0) { tooltips.push(translate("A failed bribe will cost you:")); for (let res in modifiedTemplate.cost) modifiedTemplate.cost[res] = Math.floor(costRatio * modifiedTemplate.cost[res]); tooltips.push(getEntityCostTooltip(modifiedTemplate)); } if (neededResources) { if (button.enabled) modifier = resourcesToAlphaMask(neededResources) + ":"; button.enabled = false; tooltips.push(getNeededResourcesTooltip(neededResources)); } } } let icon = Engine.GetGUIObjectByName("diplomacySpyRequestImage[" + (i - 1) + "]"); icon.sprite = modifier + "stretched:session/icons/bribes.png"; button.tooltip = tooltips.filter(tip => tip).join("\n"); button.onPress = (function(i, button) { return function() { Engine.PostNetworkCommand({ "type": "spy-request", "source": g_ViewedPlayer, "player": i }); if (!g_BribeButtonsWaiting[g_ViewedPlayer]) g_BribeButtonsWaiting[g_ViewedPlayer] = []; // Don't push i twice if (g_BribeButtonsWaiting[g_ViewedPlayer].indexOf(i) == -1) g_BribeButtonsWaiting[g_ViewedPlayer].push(i); diplomacyFormatSpyRequestButton(i, false); }; })(i, button); } function resizeTradeDialog() { let dialog = Engine.GetGUIObjectByName("tradeDialogPanel"); let size = dialog.size; let width = size.right - size.left; let tradeSize = Engine.GetGUIObjectByName("tradeResource[0]").size; width += g_ResourceData.GetCodes().length * (tradeSize.right - tradeSize.left); size.left = -width / 2; size.right = width / 2; dialog.size = size; } function openTrade() { closeOpenDialogs(); if (g_ViewedPlayer < 1) return; g_IsTradeOpen = true; let proba = Engine.GuiInterfaceCall("GetTradingGoods", g_ViewedPlayer); let button = {}; let resCodes = g_ResourceData.GetCodes(); let currTradeSelection = resCodes[0]; let updateTradeButtons = function() { for (let res in button) { button[res].label.caption = proba[res] + "%"; button[res].sel.hidden = !controlsPlayer(g_ViewedPlayer) || res != currTradeSelection; button[res].up.hidden = !controlsPlayer(g_ViewedPlayer) || res == currTradeSelection || proba[res] == 100 || proba[currTradeSelection] == 0; button[res].dn.hidden = !controlsPlayer(g_ViewedPlayer) || res == currTradeSelection || proba[res] == 0 || proba[currTradeSelection] == 100; } }; hideRemaining("tradeResources", resCodes.length); Engine.GetGUIObjectByName("tradeHelp").hidden = false; for (let i = 0; i < resCodes.length; ++i) { let resCode = resCodes[i]; let barterResource = Engine.GetGUIObjectByName("barterResource[" + i + "]"); if (!barterResource) { warn("Current GUI limits prevent displaying more than " + i + " resources in the barter dialog!"); break; } // Barter: barterOpenCommon(resCode, i, "barter"); setPanelObjectPosition(barterResource, i, i + 1); // Trade: let tradeResource = Engine.GetGUIObjectByName("tradeResource[" + i + "]"); if (!tradeResource) { warn("Current GUI limits prevent displaying more than " + i + " resources in the trading goods selection dialog!"); break; } setPanelObjectPosition(tradeResource, i, i + 1); let icon = Engine.GetGUIObjectByName("tradeResourceIcon[" + i + "]"); icon.sprite = "stretched:session/icons/resources/" + resCode + ".png"; let buttonUp = Engine.GetGUIObjectByName("tradeArrowUp[" + i + "]"); let buttonDn = Engine.GetGUIObjectByName("tradeArrowDn[" + i + "]"); button[resCode] = { "up": buttonUp, "dn": buttonDn, "label": Engine.GetGUIObjectByName("tradeResourceText[" + i + "]"), "sel": Engine.GetGUIObjectByName("tradeResourceSelection[" + i + "]") }; proba[resCode] = proba[resCode] || 0; let buttonResource = Engine.GetGUIObjectByName("tradeResourceButton[" + i + "]"); buttonResource.enabled = controlsPlayer(g_ViewedPlayer); buttonResource.onPress = (resource => { return () => { if (Engine.HotkeyIsPressed("session.fulltradeswap")) { for (let res of resCodes) proba[res] = 0; proba[resource] = 100; Engine.PostNetworkCommand({ "type": "set-trading-goods", "tradingGoods": proba }); } currTradeSelection = resource; updateTradeButtons(); }; })(resCode); buttonUp.enabled = controlsPlayer(g_ViewedPlayer); buttonUp.onPress = (resource => { return () => { proba[resource] += Math.min(STEP, proba[currTradeSelection]); proba[currTradeSelection] -= Math.min(STEP, proba[currTradeSelection]); Engine.PostNetworkCommand({ "type": "set-trading-goods", "tradingGoods": proba }); updateTradeButtons(); }; })(resCode); buttonDn.enabled = controlsPlayer(g_ViewedPlayer); buttonDn.onPress = (resource => { return () => { proba[currTradeSelection] += Math.min(STEP, proba[resource]); proba[resource] -= Math.min(STEP, proba[resource]); Engine.PostNetworkCommand({ "type": "set-trading-goods", "tradingGoods": proba }); updateTradeButtons(); }; })(resCode); } updateTradeButtons(); updateTraderTexts(); Engine.GetGUIObjectByName("tradeDialogPanel").hidden = false; } function updateTraderTexts() { let traderNumber = Engine.GuiInterfaceCall("GetTraderNumber", g_ViewedPlayer); Engine.GetGUIObjectByName("traderCountText").caption = getIdleLandTradersText(traderNumber) + "\n\n" + getIdleShipTradersText(traderNumber); } function initBarterButtons() { g_BarterSell = g_ResourceData.GetCodes()[0]; } /** * Code common to both the Barter Panel and the Trade/Barter Dialog, that * only needs to be run when the panel or dialog is opened by the player. * * @param {string} resourceCode * @param {number} idx - Element index within its set * @param {string} prefix - Common prefix of the gui elements to be worked upon */ function barterOpenCommon(resourceCode, idx, prefix) { let barterButton = {}; for (let action of g_BarterActions) barterButton[action] = Engine.GetGUIObjectByName(prefix + action + "Button[" + idx + "]"); let resource = resourceNameWithinSentence(resourceCode); barterButton.Buy.tooltip = sprintf(translate("Buy %(resource)s"), { "resource": resource }); barterButton.Sell.tooltip = sprintf(translate("Sell %(resource)s"), { "resource": resource }); barterButton.Sell.onPress = function() { g_BarterSell = resourceCode; updateSelectionDetails(); updateBarterButtons(); }; } /** * Code common to both the Barter Panel and the Trade/Barter Dialog, that * needs to be run on simulation update and when relevant hotkeys * (i.e. massbarter) are pressed. * * @param {string} resourceCode * @param {number} idx - Element index within its set * @param {string} prefix - Common prefix of the gui elements to be worked upon * @param {number} player */ function barterUpdateCommon(resourceCode, idx, prefix, player) { let barterButton = {}; let barterIcon = {}; let barterAmount = {}; for (let action of g_BarterActions) { barterButton[action] = Engine.GetGUIObjectByName(prefix + action + "Button[" + idx + "]"); barterIcon[action] = Engine.GetGUIObjectByName(prefix + action + "Icon[" + idx + "]"); barterAmount[action] = Engine.GetGUIObjectByName(prefix + action + "Amount[" + idx + "]"); } let selectionIcon = Engine.GetGUIObjectByName(prefix + "SellSelection[" + idx + "]"); let amountToSell = g_BarterResourceSellQuantity; if (Engine.HotkeyIsPressed("session.massbarter")) amountToSell *= g_BarterMultiplier; let isSelected = resourceCode == g_BarterSell; let grayscale = isSelected ? "color:0 0 0 100:grayscale:" : ""; // Select color of the sell button let neededRes = {}; neededRes[resourceCode] = amountToSell; let canSellCurrent = Engine.GuiInterfaceCall("GetNeededResources", { "cost": neededRes, "player": player }) ? "color:255 0 0 80:" : ""; // Select color of the buy button neededRes = {}; neededRes[g_BarterSell] = amountToSell; let canBuyAny = Engine.GuiInterfaceCall("GetNeededResources", { "cost": neededRes, "player": player }) ? "color:255 0 0 80:" : ""; barterIcon.Sell.sprite = canSellCurrent + "stretched:" + grayscale + "session/icons/resources/" + resourceCode + ".png"; barterIcon.Buy.sprite = canBuyAny + "stretched:" + grayscale + "session/icons/resources/" + resourceCode + ".png"; barterAmount.Sell.caption = "-" + amountToSell; let prices = GetSimState().players[player].barterPrices; barterAmount.Buy.caption = "+" + Math.round(prices.sell[g_BarterSell] / prices.buy[resourceCode] * amountToSell); barterButton.Buy.onPress = function() { Engine.PostNetworkCommand({ "type": "barter", "sell": g_BarterSell, "buy": resourceCode, "amount": amountToSell }); }; barterButton.Buy.hidden = isSelected; barterButton.Buy.enabled = controlsPlayer(player); barterButton.Sell.hidden = false; selectionIcon.hidden = !isSelected; } function updateBarterButtons() { let playerState = GetSimState().players[g_ViewedPlayer]; if (!playerState) return; let canBarter = playerState.canBarter; Engine.GetGUIObjectByName("barterNoMarketsMessage").hidden = canBarter; Engine.GetGUIObjectByName("barterResources").hidden = !canBarter; Engine.GetGUIObjectByName("barterHelp").hidden = !canBarter; if (canBarter) g_ResourceData.GetCodes().forEach((resCode, i) => { barterUpdateCommon(resCode, i, "barter", g_ViewedPlayer); }); } function getIdleLandTradersText(traderNumber) { let active = traderNumber.landTrader.trading; let garrisoned = traderNumber.landTrader.garrisoned; let inactive = traderNumber.landTrader.total - active - garrisoned; let messageTypes = { "active": { "garrisoned": { "no-inactive": translate("%(openingTradingString)s, and %(garrisonedString)s."), "inactive": translate("%(openingTradingString)s, %(garrisonedString)s, and %(inactiveString)s.") }, "no-garrisoned": { "no-inactive": translate("%(openingTradingString)s."), "inactive": translate("%(openingTradingString)s, and %(inactiveString)s.") } }, "no-active": { "garrisoned": { "no-inactive": translate("%(openingGarrisonedString)s."), "inactive": translate("%(openingGarrisonedString)s, and %(inactiveString)s.") }, "no-garrisoned": { "inactive": translatePlural("There is %(inactiveString)s.", "There are %(inactiveString)s.", inactive), "no-inactive": translate("There are no land traders.") } } }; let message = messageTypes[active ? "active" : "no-active"][garrisoned ? "garrisoned" : "no-garrisoned"][inactive ? "inactive" : "no-inactive"]; let activeString = sprintf( translatePlural( "There is %(numberTrading)s land trader trading", "There are %(numberTrading)s land traders trading", active ), { "numberTrading": active } ); let inactiveString = sprintf( active || garrisoned ? translatePlural( "%(numberOfLandTraders)s inactive", "%(numberOfLandTraders)s inactive", inactive ) : translatePlural( "%(numberOfLandTraders)s land trader inactive", "%(numberOfLandTraders)s land traders inactive", inactive ), { "numberOfLandTraders": inactive } ); let garrisonedString = sprintf( active || inactive ? translatePlural( "%(numberGarrisoned)s garrisoned on a trading merchant ship", "%(numberGarrisoned)s garrisoned on a trading merchant ship", garrisoned ) : translatePlural( "There is %(numberGarrisoned)s land trader garrisoned on a trading merchant ship", "There are %(numberGarrisoned)s land traders garrisoned on a trading merchant ship", garrisoned ), { "numberGarrisoned": garrisoned } ); return sprintf(message, { "openingTradingString": activeString, "openingGarrisonedString": garrisonedString, "garrisonedString": garrisonedString, "inactiveString": coloredText(inactiveString, g_IdleTraderTextColor) }); } function getIdleShipTradersText(traderNumber) { let active = traderNumber.shipTrader.trading; let inactive = traderNumber.shipTrader.total - active; let messageTypes = { "active": { "inactive": translate("%(openingTradingString)s, and %(inactiveString)s."), "no-inactive": translate("%(openingTradingString)s.") }, "no-active": { "inactive": translatePlural("There is %(inactiveString)s.", "There are %(inactiveString)s.", inactive), "no-inactive": translate("There are no merchant ships.") } }; let message = messageTypes[active ? "active" : "no-active"][inactive ? "inactive" : "no-inactive"]; let activeString = sprintf( translatePlural( "There is %(numberTrading)s merchant ship trading", "There are %(numberTrading)s merchant ships trading", active ), { "numberTrading": active } ); let inactiveString = sprintf( active ? translatePlural( "%(numberOfShipTraders)s inactive", "%(numberOfShipTraders)s inactive", inactive ) : translatePlural( "%(numberOfShipTraders)s merchant ship inactive", "%(numberOfShipTraders)s merchant ships inactive", inactive ), { "numberOfShipTraders": inactive } ); return sprintf(message, { "openingTradingString": activeString, "inactiveString": coloredText(inactiveString, g_IdleTraderTextColor) }); } function closeTrade() { g_IsTradeOpen = false; Engine.GetGUIObjectByName("tradeDialogPanel").hidden = true; } function toggleTrade() { let open = g_IsTradeOpen; closeOpenDialogs(); if (!open) openTrade(); } function toggleTutorial() { let tutorialPanel = Engine.GetGUIObjectByName("tutorialPanel"); tutorialPanel.hidden = !tutorialPanel.hidden || !Engine.GetGUIObjectByName("tutorialText").caption; } function updateGameSpeedControl() { Engine.GetGUIObjectByName("gameSpeedButton").hidden = g_IsNetworked; let player = g_Players[Engine.GetPlayerID()]; g_GameSpeeds = getGameSpeedChoices(!player || player.state != "active"); let gameSpeed = Engine.GetGUIObjectByName("gameSpeed"); gameSpeed.list = g_GameSpeeds.Title; gameSpeed.list_data = g_GameSpeeds.Speed; let simRate = Engine.GetSimRate(); let gameSpeedIdx = g_GameSpeeds.Speed.indexOf(+simRate.toFixed(2)); if (gameSpeedIdx == -1) warn("Unknown gamespeed:" + simRate); gameSpeed.selected = gameSpeedIdx != -1 ? gameSpeedIdx : g_GameSpeeds.Default; gameSpeed.onSelectionChange = function() { changeGameSpeed(+this.list_data[this.selected]); }; } function toggleGameSpeed() { let gameSpeed = Engine.GetGUIObjectByName("gameSpeed"); gameSpeed.hidden = !gameSpeed.hidden; } function toggleObjectives() { let open = g_IsObjectivesOpen; closeOpenDialogs(); if (!open) openObjectives(); } function openObjectives() { g_IsObjectivesOpen = true; let player = g_Players[Engine.GetPlayerID()]; let playerState = player && player.state; let isActive = !playerState || playerState == "active"; Engine.GetGUIObjectByName("gameDescriptionText").caption = getGameDescription(); let objectivesPlayerstate = Engine.GetGUIObjectByName("objectivesPlayerstate"); objectivesPlayerstate.hidden = isActive; objectivesPlayerstate.caption = g_PlayerStateMessages[playerState] || ""; let gameDescription = Engine.GetGUIObjectByName("gameDescription"); let gameDescriptionSize = gameDescription.size; gameDescriptionSize.top = Engine.GetGUIObjectByName( isActive ? "objectivesTitle" : "objectivesPlayerstate").size.bottom; gameDescription.size = gameDescriptionSize; Engine.GetGUIObjectByName("objectivesPanel").hidden = false; } function closeObjectives() { g_IsObjectivesOpen = false; Engine.GetGUIObjectByName("objectivesPanel").hidden = true; } /** * Allows players to see their own summary. * If they have shared ally vision researched, they are able to see the summary of there allies too. */ function openGameSummary() { closeOpenDialogs(); pauseGame(); let extendedSimState = Engine.GuiInterfaceCall("GetExtendedSimulationState"); Engine.PushGuiPage("page_summary.xml", { "sim": { "mapSettings": g_GameAttributes.settings, "playerStates": extendedSimState.players.filter((state, player) => g_IsObserver || player == 0 || player == g_ViewedPlayer || extendedSimState.players[g_ViewedPlayer].hasSharedLos && g_Players[player].isMutualAlly[g_ViewedPlayer]), "timeElapsed": extendedSimState.timeElapsed }, "gui": { "dialog": true, "isInGame": true }, "selectedData": g_SummarySelectedData, "callback": "resumeGameAndSaveSummarySelectedData" }); } function openStrucTree() { closeOpenDialogs(); pauseGame(); // TODO add info about researched techs and unlocked entities Engine.PushGuiPage(g_CivInfo.page, { "civ": g_CivInfo.code || g_Players[g_ViewedPlayer].civ, "callback": "storeCivInfoPage" }); } function storeCivInfoPage(data) { g_CivInfo.code = data.civ; g_CivInfo.page = data.page; resumeGame(); } /** * Pause or resume the game. * * @param explicit - true if the player explicitly wants to pause or resume. * If this argument isn't set, a multiplayer game won't be paused and the pause overlay * won't be shown in single player. */ function pauseGame(pause = true, explicit = false) { // The NetServer only supports pausing after all clients finished loading the game. if (g_IsNetworked && (!explicit || !g_IsNetworkedActive)) return; if (explicit) g_Paused = pause; Engine.SetPaused(g_Paused || pause, !!explicit); if (g_IsNetworked) { setClientPauseState(Engine.GetPlayerGUID(), g_Paused); return; } updatePauseOverlay(); } function resumeGame(explicit = false) { pauseGame(false, explicit); } function resumeGameAndSaveSummarySelectedData(data) { g_SummarySelectedData = data.summarySelectedData; resumeGame(data.explicitResume); } /** * Called when the current player toggles a pause button. */ function togglePause() { if (!Engine.GetGUIObjectByName("pauseButton").enabled) return; closeOpenDialogs(); pauseGame(!g_Paused, true); } /** * Called when a client pauses or resumes in a multiplayer game. */ function setClientPauseState(guid, paused) { // Update the list of pausing clients. let index = g_PausingClients.indexOf(guid); if (paused && index == -1) g_PausingClients.push(guid); else if (!paused && index != -1) g_PausingClients.splice(index, 1); updatePauseOverlay(); Engine.SetPaused(!!g_PausingClients.length, false); } /** * Update the pause overlay. */ function updatePauseOverlay() { Engine.GetGUIObjectByName("pauseButton").caption = g_Paused ? translate("Resume") : translate("Pause"); Engine.GetGUIObjectByName("resumeMessage").hidden = !g_Paused; Engine.GetGUIObjectByName("pausedByText").hidden = !g_IsNetworked; Engine.GetGUIObjectByName("pausedByText").caption = sprintf(translate("Paused by %(players)s"), { "players": g_PausingClients.map(guid => colorizePlayernameByGUID(guid)).join(translateWithContext("Separator for a list of players", ", ")) }); Engine.GetGUIObjectByName("pauseOverlay").hidden = !(g_Paused || g_PausingClients.length); Engine.GetGUIObjectByName("pauseOverlay").onPress = g_Paused ? togglePause : function() {}; } function openManual() { closeOpenDialogs(); pauseGame(); Engine.PushGuiPage("page_manual.xml", { "page": "manual/intro", "title": translate("Manual"), "url": "https://trac.wildfiregames.com/wiki/0adManual", "callback": "resumeGame" }); } function closeOpenDialogs() { closeMenu(); closeChat(); closeDiplomacy(); closeTrade(); closeObjectives(); } function formatTributeTooltip(playerID, resourceCode, amount) { return sprintf(translate("Tribute %(resourceAmount)s %(resourceType)s to %(playerName)s. Shift-click to tribute %(greaterAmount)s."), { "resourceAmount": amount, "resourceType": resourceNameWithinSentence(resourceCode), "playerName": colorizePlayernameByID(playerID), "greaterAmount": amount < 500 ? 500 : amount + 500 }); } Index: ps/trunk/binaries/data/mods/public/gui/session/session.js =================================================================== --- ps/trunk/binaries/data/mods/public/gui/session/session.js (revision 22649) +++ ps/trunk/binaries/data/mods/public/gui/session/session.js (revision 22650) @@ -1,1707 +1,1719 @@ const g_IsReplay = Engine.IsVisualReplay(); const g_CivData = loadCivData(false, true); 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_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; /** * Whether to display diplomacy colors (where players see self/ally/neutral/enemy each in different colors and * observers see each team in a different color) or regular player colors. */ var g_DiplomacyColorsToggle = false; /** * The array of displayed player colors (either the diplomacy color or regular color for each player). */ var g_DisplayedPlayerColors; /** * Colors to flash when pop limit reached. */ var g_DefaultPopulationColor = "white"; var g_PopulationAlertColor = "orange"; /** * Seen in the tooltip of the top panel. */ var g_ResourceTitleFont = "sans-bold-16"; /** * A random file will be played. TODO: more variety */ var g_Ambient = ["audio/ambient/dayscape/day_temperate_gen_03.ogg"]; /** * Map, player and match settings set in gamesetup. */ const g_GameAttributes = deepfreeze(Engine.GetInitAttributes()); /** * True if this is a multiplayer game. */ const g_IsNetworked = Engine.HasNetClient(); /** * Is this user in control of game settings (i.e. is a network server, or offline player). */ var g_IsController = !g_IsNetworked || Engine.HasNetServer(); /** * Whether we have finished the synchronization and * can start showing simulation related message boxes. */ var g_IsNetworkedActive = false; /** * True if the connection to the server has been lost. */ var g_Disconnected = false; /** * True if the current user has observer capabilities. */ var g_IsObserver = false; /** * True if the current user has rejoined (or joined the game after it started). */ var g_HasRejoined = false; /** * Shows a message box asking the user to leave if "won" or "defeated". */ var g_ConfirmExit = false; /** * True if the current player has paused the game explicitly. */ var g_Paused = false; /** * The list of GUIDs of players who have currently paused the game, if the game is networked. */ var g_PausingClients = []; /** * The playerID selected in the change perspective tool. */ var g_ViewedPlayer = Engine.GetPlayerID(); /** * True if the camera should focus on attacks and player commands * and select the affected units. */ var g_FollowPlayer = false; /** * Cache the basic player data (name, civ, color). */ var g_Players = []; /** * Last time when onTick was called(). * Used for animating the main menu. */ var g_LastTickTime = Date.now(); /** * Recalculate which units have their status bars shown with this frequency in milliseconds. */ var g_StatusBarUpdate = 200; /** * For restoring selection, order and filters when returning to the replay menu */ var g_ReplaySelectionData; /** * Remembers which clients are assigned to which player slots. * The keys are guids or "local" in Singleplayer. */ var g_PlayerAssignments; var g_DeveloperOverlay; /** * Whether the entire UI should be hidden (useful for promotional screenshots). * Can be toggled with a hotkey. */ var g_ShowGUI = true; /** * Whether status bars should be shown for all of the player's units. */ var g_ShowAllStatusBars = false; /** * Blink the population counter if the player can't train more units. */ var g_IsTrainingBlocked = false; /** * Cache of simulation state and template data (apart from TechnologyData, updated on every simulation update). */ var g_SimState; var g_EntityStates = {}; var g_TemplateData = {}; var g_TechnologyData = {}; var g_ResourceData = new Resources(); /** * Top coordinate of the research list. * Changes depending on the number of displayed counters. */ var g_ResearchListTop = 4; /** * List of additional entities to highlight. */ var g_ShowGuarding = false; var g_ShowGuarded = false; var g_AdditionalHighlight = []; /** * Display data of the current players entities shown in the top panel. */ var g_PanelEntities = []; /** * Order in which the panel entities are shown. */ var g_PanelEntityOrder = ["Hero", "Relic"]; /** * Unit classes to be checked for the idle-worker-hotkey. */ var g_WorkerTypes = ["FemaleCitizen", "Trader", "FishingBoat", "CitizenSoldier"]; /** * Unit classes to be checked for the military-only-selection modifier and for the idle-warrior-hotkey. */ var g_MilitaryTypes = ["Melee", "Ranged"]; function GetSimState() { if (!g_SimState) g_SimState = deepfreeze(Engine.GuiInterfaceCall("GetSimulationState")); return g_SimState; } function GetMultipleEntityStates(ents) { if (!ents.length) return null; let entityStates = Engine.GuiInterfaceCall("GetMultipleEntityStates", ents); for (let item of entityStates) g_EntityStates[item.entId] = item.state && deepfreeze(item.state); return entityStates; } function GetEntityState(entId) { if (!g_EntityStates[entId]) { let entityState = Engine.GuiInterfaceCall("GetEntityState", entId); g_EntityStates[entId] = entityState && deepfreeze(entityState); } return g_EntityStates[entId]; } function GetTemplateData(templateName) { if (!(templateName in g_TemplateData)) { let template = Engine.GuiInterfaceCall("GetTemplateData", templateName); translateObjectKeys(template, ["specific", "generic", "tooltip"]); g_TemplateData[templateName] = deepfreeze(template); } return g_TemplateData[templateName]; } function GetTechnologyData(technologyName, civ) { if (!g_TechnologyData[civ]) g_TechnologyData[civ] = {}; if (!(technologyName in g_TechnologyData[civ])) { let template = GetTechnologyDataHelper(TechnologyTemplates.Get(technologyName), civ, g_ResourceData); translateObjectKeys(template, ["specific", "generic", "description", "tooltip", "requirementsTooltip"]); g_TechnologyData[civ][technologyName] = deepfreeze(template); } return g_TechnologyData[civ][technologyName]; } function init(initData, hotloadData) { if (!g_Settings) { Engine.EndGame(); Engine.SwitchGuiPage("page_pregame.xml"); return; } // Fallback used by atlas g_PlayerAssignments = initData ? initData.playerAssignments : { "local": { "player": 1 } }; // Fallback used by atlas and autostart games if (g_PlayerAssignments.local && !g_PlayerAssignments.local.name) g_PlayerAssignments.local.name = singleplayerName(); if (initData) { g_ReplaySelectionData = initData.replaySelectionData; g_HasRejoined = initData.isRejoining; if (initData.savedGUIData) restoreSavedGameData(initData.savedGUIData); } g_DeveloperOverlay = new DeveloperOverlay(); LoadModificationTemplates(); updatePlayerData(); initializeMusic(); // before changing the perspective initGUIObjects(); if (hotloadData) g_Selection.selected = hotloadData.selection; sendLobbyPlayerlistUpdate(); onSimulationUpdate(); setTimeout(displayGamestateNotifications, 1000); } function initGUIObjects() { initMenu(); updateGameSpeedControl(); resizeDiplomacyDialog(); resizeTradeDialog(); initBarterButtons(); initPanelEntities(); initViewedPlayerDropdown(); initChatWindow(); Engine.SetBoundingBoxDebugOverlay(false); updateEnabledRangeOverlayTypes(); } function updatePlayerData() { let simState = GetSimState(); if (!simState) return; let playerData = []; for (let i = 0; i < simState.players.length; ++i) { let playerState = simState.players[i]; playerData.push({ "name": playerState.name, "civ": playerState.civ, "color": { "r": playerState.color.r * 255, "g": playerState.color.g * 255, "b": playerState.color.b * 255, "a": playerState.color.a * 255 }, "team": playerState.team, "teamsLocked": playerState.teamsLocked, "cheatsEnabled": playerState.cheatsEnabled, "state": playerState.state, "isAlly": playerState.isAlly, "isMutualAlly": playerState.isMutualAlly, "isNeutral": playerState.isNeutral, "isEnemy": playerState.isEnemy, "guid": undefined, // network guid for players controlled by hosts "offline": g_Players[i] && !!g_Players[i].offline }); } for (let guid in g_PlayerAssignments) { let playerID = g_PlayerAssignments[guid].player; if (!playerData[playerID]) continue; playerData[playerID].guid = guid; playerData[playerID].name = g_PlayerAssignments[guid].name; } g_Players = playerData; } function updateDiplomacyColorsButton() { g_DiplomacyColorsToggle = !g_DiplomacyColorsToggle; let diplomacyColorsButton = Engine.GetGUIObjectByName("diplomacyColorsButton"); diplomacyColorsButton.sprite = g_DiplomacyColorsToggle ? "stretched:session/minimap-diplomacy-on.png" : "stretched:session/minimap-diplomacy-off.png"; diplomacyColorsButton.sprite_over = g_DiplomacyColorsToggle ? "stretched:session/minimap-diplomacy-on-highlight.png" : "stretched:session/minimap-diplomacy-off-highlight.png"; Engine.GetGUIObjectByName("diplomacyColorsWindowButtonIcon").sprite = g_DiplomacyColorsToggle ? "stretched:session/icons/diplomacy-on.png" : "stretched:session/icons/diplomacy.png"; updateDisplayedPlayerColors(); } /** * Updates the displayed colors of players in the simulation and GUI. */ function updateDisplayedPlayerColors() { if (g_DiplomacyColorsToggle) { let getDiplomacyColor = stance => guiToRgbColor(Engine.ConfigDB_GetValue("user", "gui.session.diplomacycolors." + stance)) || guiToRgbColor(Engine.ConfigDB_GetValue("default", "gui.session.diplomacycolors." + stance)); let teamRepresentatives = {}; for (let i = 1; i < g_Players.length; ++i) if (g_ViewedPlayer <= 0) { // Observers and gaia see team colors let team = g_Players[i].team; g_DisplayedPlayerColors[i] = g_Players[teamRepresentatives[team] || i].color; if (team != -1 && !teamRepresentatives[team]) teamRepresentatives[team] = i; } else // Players see colors depending on diplomacy g_DisplayedPlayerColors[i] = g_ViewedPlayer == i ? getDiplomacyColor("self") : g_Players[g_ViewedPlayer].isAlly[i] ? getDiplomacyColor("ally") : g_Players[g_ViewedPlayer].isNeutral[i] ? getDiplomacyColor("neutral") : getDiplomacyColor("enemy"); g_DisplayedPlayerColors[0] = g_Players[0].color; } else g_DisplayedPlayerColors = g_Players.map(player => player.color); Engine.GuiInterfaceCall("UpdateDisplayedPlayerColors", { "displayedPlayerColors": g_DisplayedPlayerColors, "displayDiplomacyColors": g_DiplomacyColorsToggle, "showAllStatusBars": g_ShowAllStatusBars, "selected": g_Selection.toList() }); updateGUIObjects(); } /** * Depends on the current player (g_IsObserver). */ function updateHotkeyTooltips() { Engine.GetGUIObjectByName("chatInput").tooltip = translateWithContext("chat input", "Type the message to send.") + "\n" + colorizeAutocompleteHotkey() + colorizeHotkey("\n" + translate("Press %(hotkey)s to open the public chat."), "chat") + colorizeHotkey( "\n" + (g_IsObserver ? translate("Press %(hotkey)s to open the observer chat.") : translate("Press %(hotkey)s to open the ally chat.")), "teamchat") + colorizeHotkey("\n" + translate("Press %(hotkey)s to open the previously selected private chat."), "privatechat"); Engine.GetGUIObjectByName("idleWorkerButton").tooltip = colorizeHotkey("%(hotkey)s" + " ", "selection.idleworker") + translate("Find idle worker"); Engine.GetGUIObjectByName("diplomacyColorsButton").tooltip = colorizeHotkey("%(hotkey)s" + " ", "session.diplomacycolors") + translate("Toggle Diplomacy Colors"); Engine.GetGUIObjectByName("diplomacyColorsWindowButton").tooltip = colorizeHotkey("%(hotkey)s" + " ", "session.diplomacycolors") + translate("Toggle Diplomacy Colors"); + Engine.GetGUIObjectByName("diplomacyButton").tooltip = + colorizeHotkey("%(hotkey)s" + " ", "session.gui.diplomacy.toggle") + + translate("Diplomacy"); + + Engine.GetGUIObjectByName("tradeButton").tooltip = + colorizeHotkey("%(hotkey)s" + " ", "session.gui.barter.toggle") + + translate("Barter & Trade"); + Engine.GetGUIObjectByName("tradeHelp").tooltip = colorizeHotkey( translate("Select one type of goods you want to modify by clicking on it, and then use the arrows of the other types to modify their shares. You can also press %(hotkey)s while selecting one type of goods to bring its share to 100%%."), "session.fulltradeswap"); Engine.GetGUIObjectByName("barterHelp").tooltip = sprintf( translate("Start by selecting the resource you wish to sell from the upper row. For each time the lower buttons are pressed, %(quantity)s of the upper resource will be sold for the displayed quantity of the lower. Press and hold %(hotkey)s to temporarily multiply the traded amount by %(multiplier)s."), { "quantity": g_BarterResourceSellQuantity, "hotkey": colorizeHotkey("%(hotkey)s", "session.massbarter"), "multiplier": g_BarterMultiplier }); + + Engine.GetGUIObjectByName("objectivesButton").tooltip = + colorizeHotkey("%(hotkey)s" + " ", "session.gui.objectives.toggle") + + translate("Objectives"); } function initPanelEntities() { Engine.GetGUIObjectByName("panelEntityPanel").children.forEach((button, slot) => { button.onPress = function() { let panelEnt = g_PanelEntities.find(ent => ent.slot !== undefined && ent.slot == slot); if (!panelEnt) return; if (!Engine.HotkeyIsPressed("selection.add")) g_Selection.reset(); g_Selection.addList([panelEnt.ent]); }; button.onDoublePress = function() { let panelEnt = g_PanelEntities.find(ent => ent.slot !== undefined && ent.slot == slot); if (panelEnt) selectAndMoveTo(getEntityOrHolder(panelEnt.ent)); }; }); } /** * Returns the entity itself except when garrisoned where it returns its garrisonHolder */ function getEntityOrHolder(ent) { let entState = GetEntityState(ent); if (entState && !entState.position && entState.unitAI && entState.unitAI.orders.length && entState.unitAI.orders[0].type == "Garrison") return getEntityOrHolder(entState.unitAI.orders[0].data.target); return ent; } function initializeMusic() { initMusic(); if (g_ViewedPlayer != -1 && g_CivData[g_Players[g_ViewedPlayer].civ].Music) global.music.storeTracks(g_CivData[g_Players[g_ViewedPlayer].civ].Music); global.music.setState(global.music.states.PEACE); playAmbient(); } function initViewedPlayerDropdown() { g_DisplayedPlayerColors = g_Players.map(player => player.color); updateViewedPlayerDropdown(); // Select "observer" in the view player dropdown when rejoining as a defeated player let player = g_Players[Engine.GetPlayerID()]; Engine.GetGUIObjectByName("viewPlayer").selected = player && player.state == "defeated" ? 0 : Engine.GetPlayerID() + 1; } function updateViewedPlayerDropdown() { let viewPlayer = Engine.GetGUIObjectByName("viewPlayer"); viewPlayer.list_data = [-1].concat(g_Players.map((player, i) => i)); viewPlayer.list = [translate("Observer")].concat(g_Players.map( (player, i) => colorizePlayernameHelper("■", i) + " " + player.name )); } /** * Change perspective tool. * Shown to observers or when enabling the developers option. */ function selectViewPlayer(playerID) { if (playerID < -1 || playerID > g_Players.length - 1) return; if (g_ShowAllStatusBars) recalculateStatusBarDisplay(true); g_IsObserver = isPlayerObserver(Engine.GetPlayerID()); if (g_IsObserver || g_DeveloperOverlay.isChangePerspective()) { if (g_ViewedPlayer != playerID) clearSelection(); g_ViewedPlayer = playerID; } if (g_DeveloperOverlay.isChangePerspective()) { Engine.SetPlayerID(g_ViewedPlayer); g_IsObserver = isPlayerObserver(g_ViewedPlayer); } Engine.SetViewedPlayer(g_ViewedPlayer); updateDisplayedPlayerColors(); updateTopPanel(); updateChatAddressees(); updateHotkeyTooltips(); // Update GUI and clear player-dependent cache g_TemplateData = {}; Engine.GuiInterfaceCall("ResetTemplateModified"); onSimulationUpdate(); if (g_IsDiplomacyOpen) openDiplomacy(); if (g_IsTradeOpen) openTrade(); } /** * Returns true if the player with that ID is in observermode. */ function isPlayerObserver(playerID) { let playerStates = GetSimState().players; return !playerStates[playerID] || playerStates[playerID].state != "active"; } /** * Returns true if the current user can issue commands for that player. */ function controlsPlayer(playerID) { let playerStates = GetSimState().players; return !!playerStates[Engine.GetPlayerID()] && playerStates[Engine.GetPlayerID()].controlsAll || Engine.GetPlayerID() == playerID && !!playerStates[playerID] && playerStates[playerID].state != "defeated"; } /** * Called when one or more players have won or were defeated. * * @param {array} - IDs of the players who have won or were defeated. * @param {object} - a plural string stating the victory reason. * @param {boolean} - whether these players have won or lost. */ function playersFinished(players, victoryString, won) { addChatMessage({ "type": "defeat-victory", "message": victoryString, "players": players }); if (players.indexOf(Engine.GetPlayerID()) != -1) reportGame(); sendLobbyPlayerlistUpdate(); updatePlayerData(); updateChatAddressees(); updateGameSpeedControl(); if (players.indexOf(g_ViewedPlayer) == -1) return; // Select "observer" item on loss. On win enable observermode without changing perspective Engine.GetGUIObjectByName("viewPlayer").selected = won ? g_ViewedPlayer + 1 : 0; if (players.indexOf(Engine.GetPlayerID()) == -1 || Engine.IsAtlasRunning()) return; global.music.setState( won ? global.music.states.VICTORY : global.music.states.DEFEAT ); g_ConfirmExit = won ? "won" : "defeated"; } /** * Sets civ icon for the currently viewed player. * Hides most gui objects for observers. */ function updateTopPanel() { let isPlayer = g_ViewedPlayer > 0; let civIcon = Engine.GetGUIObjectByName("civIcon"); civIcon.hidden = !isPlayer; if (isPlayer) { civIcon.sprite = "stretched:" + g_CivData[g_Players[g_ViewedPlayer].civ].Emblem; Engine.GetGUIObjectByName("civIconOverlay").tooltip = sprintf( translate("%(civ)s\n%(hotkey_civinfo)s / %(hotkey_structree)s: View History / Structure Tree\nLast opened will be reopened on click."), { "civ": setStringTags(g_CivData[g_Players[g_ViewedPlayer].civ].Name, { "font": "sans-bold-stroke-14" }), "hotkey_civinfo": colorizeHotkey("%(hotkey)s", "civinfo"), "hotkey_structree": colorizeHotkey("%(hotkey)s", "structree") }); } // Following gaia can be interesting on scripted maps Engine.GetGUIObjectByName("optionFollowPlayer").hidden = !g_IsObserver || g_ViewedPlayer == -1; let viewPlayer = Engine.GetGUIObjectByName("viewPlayer"); viewPlayer.hidden = !g_IsObserver && !g_DeveloperOverlay.isChangePerspective(); let followPlayerLabel = Engine.GetGUIObjectByName("followPlayerLabel"); followPlayerLabel.hidden = Engine.GetTextWidth(followPlayerLabel.font, followPlayerLabel.caption + " ") + followPlayerLabel.getComputedSize().left > viewPlayer.getComputedSize().left; let resCodes = g_ResourceData.GetCodes(); let r = 0; for (let res of resCodes) { if (!Engine.GetGUIObjectByName("resource[" + r + "]")) { warn("Current GUI limits prevent displaying more than " + r + " resources in the top panel!"); break; } Engine.GetGUIObjectByName("resource[" + r + "]_icon").sprite = "stretched:session/icons/resources/" + res + ".png"; Engine.GetGUIObjectByName("resource[" + r + "]").hidden = !isPlayer; ++r; } horizontallySpaceObjects("resourceCounts", 5); hideRemaining("resourceCounts", r); let resPop = Engine.GetGUIObjectByName("population"); let resPopSize = resPop.size; resPopSize.left = Engine.GetGUIObjectByName("resource[" + (r - 1) + "]").size.right; resPop.size = resPopSize; Engine.GetGUIObjectByName("population").hidden = !isPlayer; Engine.GetGUIObjectByName("diplomacyButton").hidden = !isPlayer; Engine.GetGUIObjectByName("tradeButton").hidden = !isPlayer; Engine.GetGUIObjectByName("observerText").hidden = isPlayer; let alphaLabel = Engine.GetGUIObjectByName("alphaLabel"); alphaLabel.hidden = isPlayer && !viewPlayer.hidden; alphaLabel.size = isPlayer ? "50%+44 0 100%-283 100%" : "155 0 85%-279 100%"; Engine.GetGUIObjectByName("pauseButton").enabled = !g_IsObserver || !g_IsNetworked || g_IsController; Engine.GetGUIObjectByName("menuResignButton").enabled = !g_IsObserver; Engine.GetGUIObjectByName("lobbyButton").enabled = Engine.HasXmppClient(); } /** * Resign a player. * @param leaveGameAfterResign If player is quitting after resignation. */ function resignGame(leaveGameAfterResign) { if (g_IsObserver || g_Disconnected) return; Engine.PostNetworkCommand({ "type": "resign" }); if (!leaveGameAfterResign) resumeGame(true); } /** * Leave the game * @param willRejoin If player is going to be rejoining a networked game. */ function leaveGame(willRejoin) { if (!willRejoin && !g_IsObserver) resignGame(true); // Before ending the game let replayDirectory = Engine.GetCurrentReplayDirectory(); let simData = getReplayMetadata(); let playerID = Engine.GetPlayerID(); Engine.EndGame(); // After the replay file was closed in EndGame // Done here to keep EndGame small if (!g_IsReplay) Engine.AddReplayToCache(replayDirectory); if (g_IsController && Engine.HasXmppClient()) Engine.SendUnregisterGame(); Engine.SwitchGuiPage("page_summary.xml", { "sim": simData, "gui": { "dialog": false, "assignedPlayer": playerID, "disconnected": g_Disconnected, "isReplay": g_IsReplay, "replayDirectory": !g_HasRejoined && replayDirectory, "replaySelectionData": g_ReplaySelectionData } }); } // Return some data that we'll use when hotloading this file after changes function getHotloadData() { return { "selection": g_Selection.selected }; } function getSavedGameData() { return { "groups": g_Groups.groups }; } function restoreSavedGameData(data) { // Restore camera if any if (data.camera) Engine.SetCameraData(data.camera.PosX, data.camera.PosY, data.camera.PosZ, data.camera.RotX, data.camera.RotY, data.camera.Zoom); // Clear selection when loading a game g_Selection.reset(); // Restore control groups for (let groupNumber in data.groups) { g_Groups.groups[groupNumber].groups = data.groups[groupNumber].groups; g_Groups.groups[groupNumber].ents = data.groups[groupNumber].ents; } updateGroups(); } /** * Called every frame. */ function onTick() { if (!g_Settings) return; let now = Date.now(); let tickLength = now - g_LastTickTime; g_LastTickTime = now; handleNetMessages(); updateCursorAndTooltip(); if (g_Selection.dirty) { g_Selection.dirty = false; // When selection changed, get the entityStates of new entities GetMultipleEntityStates(g_Selection.toList().filter(entId => !g_EntityStates[entId])); updateGUIObjects(); // Display rally points for selected buildings if (Engine.GetPlayerID() != -1) Engine.GuiInterfaceCall("DisplayRallyPoint", { "entities": g_Selection.toList() }); } else if (g_ShowAllStatusBars && now % g_StatusBarUpdate <= tickLength) recalculateStatusBarDisplay(); updateTimers(); updateMenuPosition(tickLength); // When training is blocked, flash population (alternates color every 500msec) Engine.GetGUIObjectByName("resourcePop").textcolor = g_IsTrainingBlocked && now % 1000 < 500 ? g_PopulationAlertColor : g_DefaultPopulationColor; Engine.GuiInterfaceCall("ClearRenamedEntities"); } function onWindowResized() { // Update followPlayerLabel updateTopPanel(); resizeChatWindow(); } function changeGameSpeed(speed) { if (!g_IsNetworked) Engine.SetSimRate(speed); } function updateIdleWorkerButton() { Engine.GetGUIObjectByName("idleWorkerButton").enabled = Engine.GuiInterfaceCall("HasIdleUnits", { "viewedPlayer": g_ViewedPlayer, "idleClasses": g_WorkerTypes, "excludeUnits": [] }); } function onSimulationUpdate() { // Templates change depending on technologies and auras, so they have to be reloaded after such a change. // g_TechnologyData data never changes, so it shouldn't be deleted. g_EntityStates = {}; if (Engine.GuiInterfaceCall("IsTemplateModified")) { g_TemplateData = {}; Engine.GuiInterfaceCall("ResetTemplateModified"); } g_SimState = undefined; if (!GetSimState()) return; GetMultipleEntityStates(g_Selection.toList()); updateCinemaPath(); handleNotifications(); updateGUIObjects(); if (g_ConfirmExit) confirmExit(); } /** * Don't show the message box before all playerstate changes are processed. */ function confirmExit() { if (g_IsNetworked && !g_IsNetworkedActive) return; closeOpenDialogs(); // Don't ask for exit if other humans are still playing let askExit = !Engine.HasNetServer() || g_Players.every((player, i) => i == 0 || player.state != "active" || g_GameAttributes.settings.PlayerData[i].AI != ""); let subject = g_PlayerStateMessages[g_ConfirmExit]; if (askExit) subject += "\n" + translate("Do you want to quit?"); messageBox( 400, 200, subject, g_ConfirmExit == "won" ? translate("VICTORIOUS!") : translate("DEFEATED!"), askExit ? [translate("No"), translate("Yes")] : [translate("OK")], askExit ? [resumeGame, leaveGame] : [resumeGame] ); g_ConfirmExit = false; } function toggleGUI() { g_ShowGUI = !g_ShowGUI; updateCinemaPath(); } function updateCinemaPath() { let isPlayingCinemaPath = GetSimState().cinemaPlaying && !g_Disconnected; Engine.GetGUIObjectByName("session").hidden = !g_ShowGUI || isPlayingCinemaPath; Engine.Renderer_SetSilhouettesEnabled(!isPlayingCinemaPath && Engine.ConfigDB_GetValue("user", "silhouettes") == "true"); } function updateGUIObjects() { g_Selection.update(); if (g_ShowAllStatusBars) recalculateStatusBarDisplay(); if (g_ShowGuarding || g_ShowGuarded) updateAdditionalHighlight(); updatePanelEntities(); displayPanelEntities(); updateGroups(); updatePlayerDisplay(); updateResearchDisplay(); updateSelectionDetails(); updateBuildingPlacementPreview(); updateTimeNotifications(); updateIdleWorkerButton(); if (g_IsTradeOpen) { updateTraderTexts(); updateBarterButtons(); } if (g_ViewedPlayer > 0) { let playerState = GetSimState().players[g_ViewedPlayer]; g_DeveloperOverlay.setControlAll(playerState && playerState.controlsAll); } if (!g_IsObserver) { // Update music state on basis of battle state. let battleState = Engine.GuiInterfaceCall("GetBattleState", g_ViewedPlayer); if (battleState) global.music.setState(global.music.states[battleState]); } updateViewedPlayerDropdown(); updateDiplomacy(); g_DeveloperOverlay.update(); } function saveResPopTooltipSort() { Engine.ConfigDB_CreateAndWriteValueToFile("user", "gui.session.respoptooltipsort", String((+Engine.ConfigDB_GetValue("user", "gui.session.respoptooltipsort") + 2) % 3 - 1), "config/user.cfg"); } function onReplayFinished() { closeOpenDialogs(); pauseGame(); messageBox(400, 200, translateWithContext("replayFinished", "The replay has finished. Do you want to quit?"), translateWithContext("replayFinished", "Confirmation"), [translateWithContext("replayFinished", "No"), translateWithContext("replayFinished", "Yes")], [resumeGame, leaveGame]); } /** * updates a status bar on the GUI * nameOfBar: name of the bar * points: points to show * maxPoints: max points * direction: gets less from (right to left) 0; (top to bottom) 1; (left to right) 2; (bottom to top) 3; */ function updateGUIStatusBar(nameOfBar, points, maxPoints, direction) { // check, if optional direction parameter is valid. if (!direction || !(direction >= 0 && direction < 4)) direction = 0; // get the bar and update it let statusBar = Engine.GetGUIObjectByName(nameOfBar); if (!statusBar) return; let healthSize = statusBar.size; let value = 100 * Math.max(0, Math.min(1, points / maxPoints)); // inverse bar if (direction == 2 || direction == 3) value = 100 - value; if (direction == 0) healthSize.rright = value; else if (direction == 1) healthSize.rbottom = value; else if (direction == 2) healthSize.rleft = value; else if (direction == 3) healthSize.rtop = value; statusBar.size = healthSize; } function updatePanelEntities() { let panelEnts = g_ViewedPlayer == -1 ? GetSimState().players.reduce((ents, pState) => ents.concat(pState.panelEntities), []) : GetSimState().players[g_ViewedPlayer].panelEntities; g_PanelEntities = g_PanelEntities.filter(panelEnt => panelEnts.find(ent => ent == panelEnt.ent)); for (let ent of panelEnts) { let panelEntState = GetEntityState(ent); let template = GetTemplateData(panelEntState.template); let panelEnt = g_PanelEntities.find(pEnt => ent == pEnt.ent); if (!panelEnt) { panelEnt = { "ent": ent, "tooltip": undefined, "sprite": "stretched:session/portraits/" + template.icon, "maxHitpoints": undefined, "currentHitpoints": panelEntState.hitpoints, "previousHitpoints": undefined }; g_PanelEntities.push(panelEnt); } panelEnt.tooltip = createPanelEntityTooltip(panelEntState, template); panelEnt.previousHitpoints = panelEnt.currentHitpoints; panelEnt.currentHitpoints = panelEntState.hitpoints; panelEnt.maxHitpoints = panelEntState.maxHitpoints; } let panelEntIndex = ent => g_PanelEntityOrder.findIndex(entClass => GetEntityState(ent).identity.classes.indexOf(entClass) != -1); g_PanelEntities = g_PanelEntities.sort((panelEntA, panelEntB) => panelEntIndex(panelEntA.ent) - panelEntIndex(panelEntB.ent)); } function createPanelEntityTooltip(panelEntState, template) { let getPanelEntNameTooltip = panelEntState => "[font=\"sans-bold-16\"]" + template.name.specific + "[/font]"; return [ getPanelEntNameTooltip, getCurrentHealthTooltip, getAttackTooltip, getArmorTooltip, getEntityTooltip, getAurasTooltip ].map(tooltip => tooltip(panelEntState)).filter(tip => tip).join("\n"); } function displayPanelEntities() { let buttons = Engine.GetGUIObjectByName("panelEntityPanel").children; buttons.forEach((button, slot) => { if (button.hidden || g_PanelEntities.some(ent => ent.slot !== undefined && ent.slot == slot)) return; button.hidden = true; stopColorFade("panelEntityHitOverlay[" + slot + "]"); }); // The slot identifies the button, displayIndex determines its position. for (let displayIndex = 0; displayIndex < Math.min(g_PanelEntities.length, buttons.length); ++displayIndex) { let panelEnt = g_PanelEntities[displayIndex]; // Find the first unused slot if new, otherwise reuse previous. let slot = panelEnt.slot === undefined ? buttons.findIndex(button => button.hidden) : panelEnt.slot; let panelEntButton = Engine.GetGUIObjectByName("panelEntityButton[" + slot + "]"); panelEntButton.tooltip = panelEnt.tooltip; updateGUIStatusBar("panelEntityHealthBar[" + slot + "]", panelEnt.currentHitpoints, panelEnt.maxHitpoints); if (panelEnt.slot === undefined) { let panelEntImage = Engine.GetGUIObjectByName("panelEntityImage[" + slot + "]"); panelEntImage.sprite = panelEnt.sprite; panelEntButton.hidden = false; panelEnt.slot = slot; } // If the health of the panelEnt changed since the last update, trigger the animation. if (panelEnt.previousHitpoints > panelEnt.currentHitpoints) startColorFade("panelEntityHitOverlay[" + slot + "]", 100, 0, colorFade_attackUnit, true, smoothColorFadeRestart_attackUnit); // TODO: Instead of instant position changes, animate button movement. setPanelObjectPosition(panelEntButton, displayIndex, buttons.length); } } function updateGroups() { g_Groups.update(); // Determine the sum of the costs of a given template let getCostSum = (ent) => { let cost = GetTemplateData(GetEntityState(ent).template).cost; return cost ? Object.keys(cost).map(key => cost[key]).reduce((sum, cur) => sum + cur) : 0; }; for (let i in Engine.GetGUIObjectByName("unitGroupPanel").children) { Engine.GetGUIObjectByName("unitGroupLabel[" + i + "]").caption = i; let button = Engine.GetGUIObjectByName("unitGroupButton[" + i + "]"); button.hidden = g_Groups.groups[i].getTotalCount() == 0; button.onpress = (function(i) { return function() { performGroup((Engine.HotkeyIsPressed("selection.add") ? "add" : "select"), i); }; })(i); button.ondoublepress = (function(i) { return function() { performGroup("snap", i); }; })(i); button.onpressright = (function(i) { return function() { performGroup("breakUp", i); }; })(i); // Choose the icon of the most common template (or the most costly if it's not unique) if (g_Groups.groups[i].getTotalCount() > 0) { let icon = GetTemplateData(GetEntityState(g_Groups.groups[i].getEntsGrouped().reduce((pre, cur) => { if (pre.ents.length == cur.ents.length) return getCostSum(pre.ents[0]) > getCostSum(cur.ents[0]) ? pre : cur; return pre.ents.length > cur.ents.length ? pre : cur; }).ents[0]).template).icon; Engine.GetGUIObjectByName("unitGroupIcon[" + i + "]").sprite = icon ? ("stretched:session/portraits/" + icon) : "groupsIcon"; } setPanelObjectPosition(button, i, 1); } } /** * Create ally player stat tooltip. * @param {string} resource - Resource type, on which values will be sorted. * @param {object} playerStates - Playerstates from players whos stats are viewed in the tooltip. * @param {number} sort - 0 no order, -1 descending, 1 ascending order. * @returns {string} Tooltip string. */ function getAllyStatTooltip(resource, playerStates, sort) { let tooltip = []; for (let player in playerStates) tooltip.push({ "playername": colorizePlayernameHelper("■", player) + " " + g_Players[player].name, "statValue": resource == "pop" ? sprintf(translate("%(popCount)s/%(popLimit)s/%(popMax)s"), playerStates[player]) : Math.round(playerStates[player].resourceCounts[resource]), "orderValue": resource == "pop" ? playerStates[player].popCount : Math.round(playerStates[player].resourceCounts[resource]) }); if (sort) tooltip.sort((a, b) => sort * (b.orderValue - a.orderValue)); return "\n" + tooltip.map(stat => sprintf(translate("%(playername)s: %(statValue)s"), stat)).join("\n"); } function updatePlayerDisplay() { let allPlayerStates = GetSimState().players; let viewedPlayerState = allPlayerStates[g_ViewedPlayer]; let viewablePlayerStates = {}; for (let player in allPlayerStates) if (player != 0 && player != g_ViewedPlayer && g_Players[player].state != "defeated" && (g_IsObserver || viewedPlayerState.hasSharedLos && g_Players[player].isMutualAlly[g_ViewedPlayer])) viewablePlayerStates[player] = allPlayerStates[player]; if (!viewedPlayerState) return; let tooltipSort = +Engine.ConfigDB_GetValue("user", "gui.session.respoptooltipsort"); let orderHotkeyTooltip = Object.keys(viewablePlayerStates).length <= 1 ? "" : "\n" + sprintf(translate("%(order)s: %(hotkey)s to change order."), { "hotkey": setStringTags("\\[Click]", g_HotkeyTags), "order": tooltipSort == 0 ? translate("Unordered") : tooltipSort == 1 ? translate("Descending") : translate("Ascending") }); let resCodes = g_ResourceData.GetCodes(); for (let r = 0; r < resCodes.length; ++r) { let resourceObj = Engine.GetGUIObjectByName("resource[" + r + "]"); if (!resourceObj) break; let res = resCodes[r]; let tooltip = '[font="' + g_ResourceTitleFont + '"]' + resourceNameFirstWord(res) + '[/font]'; let descr = g_ResourceData.GetResource(res).description; if (descr) tooltip += "\n" + translate(descr); tooltip += orderHotkeyTooltip + getAllyStatTooltip(res, viewablePlayerStates, tooltipSort); resourceObj.tooltip = tooltip; Engine.GetGUIObjectByName("resource[" + r + "]_count").caption = Math.floor(viewedPlayerState.resourceCounts[res]); } Engine.GetGUIObjectByName("resourcePop").caption = sprintf(translate("%(popCount)s/%(popLimit)s"), viewedPlayerState); Engine.GetGUIObjectByName("population").tooltip = translate("Population (current / limit)") + "\n" + sprintf(translate("Maximum population: %(popCap)s"), { "popCap": viewedPlayerState.popMax }) + orderHotkeyTooltip + getAllyStatTooltip("pop", viewablePlayerStates, tooltipSort); g_IsTrainingBlocked = viewedPlayerState.trainingBlocked; } function selectAndMoveTo(ent) { let entState = GetEntityState(ent); if (!entState || !entState.position) return; g_Selection.reset(); g_Selection.addList([ent]); let position = entState.position; Engine.CameraMoveTo(position.x, position.z); } function updateResearchDisplay() { let researchStarted = Engine.GuiInterfaceCall("GetStartedResearch", g_ViewedPlayer); // Set up initial positioning. let buttonSideLength = Engine.GetGUIObjectByName("researchStartedButton[0]").size.right; for (let i = 0; i < 10; ++i) { let button = Engine.GetGUIObjectByName("researchStartedButton[" + i + "]"); let size = button.size; size.top = g_ResearchListTop + (4 + buttonSideLength) * i; size.bottom = size.top + buttonSideLength; button.size = size; } let numButtons = 0; for (let tech in researchStarted) { // Show at most 10 in-progress techs. if (numButtons >= 10) break; let template = GetTechnologyData(tech, g_Players[g_ViewedPlayer].civ); let button = Engine.GetGUIObjectByName("researchStartedButton[" + numButtons + "]"); button.hidden = false; button.tooltip = getEntityNames(template); button.onpress = (function(e) { return function() { selectAndMoveTo(e); }; })(researchStarted[tech].researcher); let icon = "stretched:session/portraits/" + template.icon; Engine.GetGUIObjectByName("researchStartedIcon[" + numButtons + "]").sprite = icon; // Scale the progress indicator. let size = Engine.GetGUIObjectByName("researchStartedProgressSlider[" + numButtons + "]").size; // Buttons are assumed to be square, so left/right offsets can be used for top/bottom. size.top = size.left + Math.round(researchStarted[tech].progress * (size.right - size.left)); Engine.GetGUIObjectByName("researchStartedProgressSlider[" + numButtons + "]").size = size; Engine.GetGUIObjectByName("researchStartedTimeRemaining[" + numButtons + "]").caption = Engine.FormatMillisecondsIntoDateStringGMT(researchStarted[tech].timeRemaining, translateWithContext("countdown format", "m:ss")); ++numButtons; } // Hide unused buttons. for (let i = numButtons; i < 10; ++i) Engine.GetGUIObjectByName("researchStartedButton[" + i + "]").hidden = true; } /** * Toggles the display of status bars for all of the player's entities. * * @param {Boolean} remove - Whether to hide all previously shown status bars. */ function recalculateStatusBarDisplay(remove = false) { let entities; if (g_ShowAllStatusBars && !remove) entities = g_ViewedPlayer == -1 ? Engine.PickNonGaiaEntitiesOnScreen() : Engine.PickPlayerEntitiesOnScreen(g_ViewedPlayer); else { let selected = g_Selection.toList(); for (let ent in g_Selection.highlighted) selected.push(g_Selection.highlighted[ent]); // Remove selected entities from the 'all entities' array, // to avoid disabling their status bars. entities = Engine.GuiInterfaceCall( g_ViewedPlayer == -1 ? "GetNonGaiaEntities" : "GetPlayerEntities", { "viewedPlayer": g_ViewedPlayer }).filter(idx => selected.indexOf(idx) == -1); } Engine.GuiInterfaceCall("SetStatusBars", { "entities": entities, "enabled": g_ShowAllStatusBars && !remove, "showRank": Engine.ConfigDB_GetValue("user", "gui.session.rankabovestatusbar") == "true", "showExperience": Engine.ConfigDB_GetValue("user", "gui.session.experiencestatusbar") == "true" }); } /** * Inverts the given configuration boolean and returns the current state. * For example "silhouettes". */ function toggleConfigBool(configName) { let enabled = Engine.ConfigDB_GetValue("user", configName) != "true"; Engine.ConfigDB_CreateAndWriteValueToFile("user", configName, String(enabled), "config/user.cfg"); return enabled; } /** * Toggles the display of range overlays of selected entities for the given range type. * @param {string} type - for example "Auras" */ function toggleRangeOverlay(type) { let enabled = toggleConfigBool("gui.session." + type.toLowerCase() + "range"); Engine.GuiInterfaceCall("EnableVisualRangeOverlayType", { "type": type, "enabled": enabled }); let selected = g_Selection.toList(); for (let ent in g_Selection.highlighted) selected.push(g_Selection.highlighted[ent]); Engine.GuiInterfaceCall("SetRangeOverlays", { "entities": selected, "enabled": enabled }); } function updateEnabledRangeOverlayTypes() { for (let type of ["Attack", "Auras", "Heal"]) Engine.GuiInterfaceCall("EnableVisualRangeOverlayType", { "type": type, "enabled": Engine.ConfigDB_GetValue("user", "gui.session." + type.toLowerCase() + "range") == "true" }); } // Update the additional list of entities to be highlighted. function updateAdditionalHighlight() { let entsAdd = []; // list of entities units to be highlighted let entsRemove = []; let highlighted = g_Selection.toList(); for (let ent in g_Selection.highlighted) highlighted.push(g_Selection.highlighted[ent]); if (g_ShowGuarding) // flag the guarding entities to add in this additional highlight for (let sel in g_Selection.selected) { let state = GetEntityState(g_Selection.selected[sel]); if (!state.guard || !state.guard.entities.length) continue; for (let ent of state.guard.entities) if (highlighted.indexOf(ent) == -1 && entsAdd.indexOf(ent) == -1) entsAdd.push(ent); } if (g_ShowGuarded) // flag the guarded entities to add in this additional highlight for (let sel in g_Selection.selected) { let state = GetEntityState(g_Selection.selected[sel]); if (!state.unitAI || !state.unitAI.isGuarding) continue; let ent = state.unitAI.isGuarding; if (highlighted.indexOf(ent) == -1 && entsAdd.indexOf(ent) == -1) entsAdd.push(ent); } // flag the entities to remove (from the previously added) from this additional highlight for (let ent of g_AdditionalHighlight) if (highlighted.indexOf(ent) == -1 && entsAdd.indexOf(ent) == -1 && entsRemove.indexOf(ent) == -1) entsRemove.push(ent); _setHighlight(entsAdd, g_HighlightedAlpha, true); _setHighlight(entsRemove, 0, false); g_AdditionalHighlight = entsAdd; } function playAmbient() { Engine.PlayAmbientSound(pickRandom(g_Ambient), true); } /** * Adds the ingame time and ceasefire counter to the global FPS and * realtime counters shown in the top right corner. */ function appendSessionCounters(counters) { let simState = GetSimState(); if (Engine.ConfigDB_GetValue("user", "gui.session.timeelapsedcounter") === "true") { let currentSpeed = Engine.GetSimRate(); if (currentSpeed != 1.0) // Translation: The "x" means "times", with the mathematical meaning of multiplication. counters.push(sprintf(translate("%(time)s (%(speed)sx)"), { "time": timeToString(simState.timeElapsed), "speed": Engine.FormatDecimalNumberIntoString(currentSpeed) })); else counters.push(timeToString(simState.timeElapsed)); } if (simState.ceasefireActive && Engine.ConfigDB_GetValue("user", "gui.session.ceasefirecounter") === "true") counters.push(timeToString(simState.ceasefireTimeRemaining)); g_ResearchListTop = 4 + 14 * counters.length; } /** * Send the current list of players, teams, AIs, observers and defeated/won and offline states to the lobby. * The playerData format from g_GameAttributes is kept to reuse the GUI function presenting the data. */ function sendLobbyPlayerlistUpdate() { if (!g_IsController || !Engine.HasXmppClient()) return; // Extract the relevant player data and minimize packet load let minPlayerData = []; for (let playerID in g_GameAttributes.settings.PlayerData) { if (+playerID == 0) continue; let pData = g_GameAttributes.settings.PlayerData[playerID]; let minPData = { "Name": pData.Name, "Civ": pData.Civ }; if (g_GameAttributes.settings.LockTeams) minPData.Team = pData.Team; if (pData.AI) { minPData.AI = pData.AI; minPData.AIDiff = pData.AIDiff; minPData.AIBehavior = pData.AIBehavior; } if (g_Players[playerID].offline) minPData.Offline = true; // Whether the player has won or was defeated let state = g_Players[playerID].state; if (state != "active") minPData.State = state; minPlayerData.push(minPData); } // Add observers let connectedPlayers = 0; for (let guid in g_PlayerAssignments) { let pData = g_GameAttributes.settings.PlayerData[g_PlayerAssignments[guid].player]; if (pData) ++connectedPlayers; else minPlayerData.push({ "Name": g_PlayerAssignments[guid].name, "Team": "observer" }); } Engine.SendChangeStateGame(connectedPlayers, playerDataToStringifiedTeamList(minPlayerData)); } /** * Send a report on the gamestatus to the lobby. * Keep in sync with source/tools/XpartaMuPP/LobbyRanking.py */ function reportGame() { // Only 1v1 games are rated (and Gaia is part of g_Players) if (!Engine.HasXmppClient() || !Engine.IsRankedGame() || g_Players.length != 3 || Engine.GetPlayerID() == -1) return; let extendedSimState = Engine.GuiInterfaceCall("GetExtendedSimulationState"); let unitsClasses = [ "total", "Infantry", "Worker", "FemaleCitizen", "Cavalry", "Champion", "Hero", "Siege", "Ship", "Trader" ]; let unitsCountersTypes = [ "unitsTrained", "unitsLost", "enemyUnitsKilled" ]; let buildingsClasses = [ "total", "CivCentre", "House", "Economic", "Outpost", "Military", "Fortress", "Wonder" ]; let buildingsCountersTypes = [ "buildingsConstructed", "buildingsLost", "enemyBuildingsDestroyed" ]; let resourcesTypes = [ "wood", "food", "stone", "metal" ]; let resourcesCounterTypes = [ "resourcesGathered", "resourcesUsed", "resourcesSold", "resourcesBought" ]; let misc = [ "tradeIncome", "tributesSent", "tributesReceived", "treasuresCollected", "lootCollected", "percentMapExplored" ]; let playerStatistics = {}; // Unit Stats for (let unitCounterType of unitsCountersTypes) { if (!playerStatistics[unitCounterType]) playerStatistics[unitCounterType] = { }; for (let unitsClass of unitsClasses) playerStatistics[unitCounterType][unitsClass] = ""; } playerStatistics.unitsLostValue = ""; playerStatistics.unitsKilledValue = ""; // Building stats for (let buildingCounterType of buildingsCountersTypes) { if (!playerStatistics[buildingCounterType]) playerStatistics[buildingCounterType] = { }; for (let buildingsClass of buildingsClasses) playerStatistics[buildingCounterType][buildingsClass] = ""; } playerStatistics.buildingsLostValue = ""; playerStatistics.enemyBuildingsDestroyedValue = ""; // Resources for (let resourcesCounterType of resourcesCounterTypes) { if (!playerStatistics[resourcesCounterType]) playerStatistics[resourcesCounterType] = { }; for (let resourcesType of resourcesTypes) playerStatistics[resourcesCounterType][resourcesType] = ""; } playerStatistics.resourcesGathered.vegetarianFood = ""; for (let type of misc) playerStatistics[type] = ""; // Total playerStatistics.economyScore = ""; playerStatistics.militaryScore = ""; playerStatistics.totalScore = ""; let mapName = g_GameAttributes.settings.Name; let playerStates = ""; let playerCivs = ""; let teams = ""; let teamsLocked = true; // Serialize the statistics for each player into a comma-separated list. // Ignore gaia for (let i = 1; i < extendedSimState.players.length; ++i) { let player = extendedSimState.players[i]; let maxIndex = player.sequences.time.length - 1; playerStates += player.state + ","; playerCivs += player.civ + ","; teams += player.team + ","; teamsLocked = teamsLocked && player.teamsLocked; for (let resourcesCounterType of resourcesCounterTypes) for (let resourcesType of resourcesTypes) playerStatistics[resourcesCounterType][resourcesType] += player.sequences[resourcesCounterType][resourcesType][maxIndex] + ","; playerStatistics.resourcesGathered.vegetarianFood += player.sequences.resourcesGathered.vegetarianFood[maxIndex] + ","; for (let unitCounterType of unitsCountersTypes) for (let unitsClass of unitsClasses) playerStatistics[unitCounterType][unitsClass] += player.sequences[unitCounterType][unitsClass][maxIndex] + ","; for (let buildingCounterType of buildingsCountersTypes) for (let buildingsClass of buildingsClasses) playerStatistics[buildingCounterType][buildingsClass] += player.sequences[buildingCounterType][buildingsClass][maxIndex] + ","; let total = 0; for (let type in player.sequences.resourcesGathered) total += player.sequences.resourcesGathered[type][maxIndex]; playerStatistics.economyScore += total + ","; playerStatistics.militaryScore += Math.round((player.sequences.enemyUnitsKilledValue[maxIndex] + player.sequences.enemyBuildingsDestroyedValue[maxIndex]) / 10) + ","; playerStatistics.totalScore += (total + Math.round((player.sequences.enemyUnitsKilledValue[maxIndex] + player.sequences.enemyBuildingsDestroyedValue[maxIndex]) / 10)) + ","; for (let type of misc) playerStatistics[type] += player.sequences[type][maxIndex] + ","; } // Send the report with serialized data let reportObject = {}; reportObject.timeElapsed = extendedSimState.timeElapsed; reportObject.playerStates = playerStates; reportObject.playerID = Engine.GetPlayerID(); reportObject.matchID = g_GameAttributes.matchID; reportObject.civs = playerCivs; reportObject.teams = teams; reportObject.teamsLocked = String(teamsLocked); reportObject.ceasefireActive = String(extendedSimState.ceasefireActive); reportObject.ceasefireTimeRemaining = String(extendedSimState.ceasefireTimeRemaining); reportObject.mapName = mapName; reportObject.economyScore = playerStatistics.economyScore; reportObject.militaryScore = playerStatistics.militaryScore; reportObject.totalScore = playerStatistics.totalScore; for (let rct of resourcesCounterTypes) for (let rt of resourcesTypes) reportObject[rt + rct.substr(9)] = playerStatistics[rct][rt]; // eg. rt = food rct.substr = Gathered rct = resourcesGathered reportObject.vegetarianFoodGathered = playerStatistics.resourcesGathered.vegetarianFood; for (let type of unitsClasses) { // eg. type = Infantry (type.substr(0,1)).toLowerCase()+type.substr(1) = infantry reportObject[(type.substr(0, 1)).toLowerCase() + type.substr(1) + "UnitsTrained"] = playerStatistics.unitsTrained[type]; reportObject[(type.substr(0, 1)).toLowerCase() + type.substr(1) + "UnitsLost"] = playerStatistics.unitsLost[type]; reportObject["enemy" + type + "UnitsKilled"] = playerStatistics.enemyUnitsKilled[type]; } for (let type of buildingsClasses) { reportObject[(type.substr(0, 1)).toLowerCase() + type.substr(1) + "BuildingsConstructed"] = playerStatistics.buildingsConstructed[type]; reportObject[(type.substr(0, 1)).toLowerCase() + type.substr(1) + "BuildingsLost"] = playerStatistics.buildingsLost[type]; reportObject["enemy" + type + "BuildingsDestroyed"] = playerStatistics.enemyBuildingsDestroyed[type]; } for (let type of misc) reportObject[type] = playerStatistics[type]; Engine.SendGameReport(reportObject); } Index: ps/trunk/binaries/data/mods/public/gui/session/top_panel/button_diplomacy.xml =================================================================== --- ps/trunk/binaries/data/mods/public/gui/session/top_panel/button_diplomacy.xml (revision 22649) +++ ps/trunk/binaries/data/mods/public/gui/session/top_panel/button_diplomacy.xml (revision 22650) @@ -1,16 +1,14 @@ - Diplomacy toggleDiplomacy(); - - Index: ps/trunk/binaries/data/mods/public/gui/session/top_panel/button_objectives.xml =================================================================== --- ps/trunk/binaries/data/mods/public/gui/session/top_panel/button_objectives.xml (revision 22649) +++ ps/trunk/binaries/data/mods/public/gui/session/top_panel/button_objectives.xml (revision 22650) @@ -1,19 +1,19 @@ - Objectives toggleObjectives(); Index: ps/trunk/binaries/data/mods/public/gui/session/top_panel/button_trade.xml =================================================================== --- ps/trunk/binaries/data/mods/public/gui/session/top_panel/button_trade.xml (revision 22649) +++ ps/trunk/binaries/data/mods/public/gui/session/top_panel/button_trade.xml (revision 22650) @@ -1,13 +1,13 @@ - Barter & Trade toggleTrade();